Skip to content

Commit

Permalink
feat(i18n): Employ babel, add proper internationalization to dates
Browse files Browse the repository at this point in the history
  • Loading branch information
alexpovel committed Sep 4, 2022
1 parent 8b6404d commit 492ee55
Show file tree
Hide file tree
Showing 5 changed files with 488 additions and 244 deletions.
56 changes: 44 additions & 12 deletions ancv/visualization/templates.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import json
from abc import ABC, abstractmethod
from contextlib import redirect_stdout
from functools import singledispatchmethod
from datetime import date
from functools import lru_cache, singledispatchmethod
from pathlib import Path
from tempfile import SpooledTemporaryFile
from typing import MutableSequence, NamedTuple, Optional

from babel.core import Locale
from babel.dates import format_date
from rich.align import Align
from rich.console import Console, ConsoleOptions, Group, NewLine, RenderableType, group
from rich.padding import Padding
Expand Down Expand Up @@ -48,11 +51,13 @@ def __init__(
model: ResumeSchema,
theme: Theme,
translation: Translation,
locale: Locale,
ascii_only: bool,
) -> None:
self.model = model
self.theme = theme
self.translation = translation
self.locale = locale
self.ascii_only = ascii_only

# This is behavior:
Expand Down Expand Up @@ -86,6 +91,32 @@ def render(self) -> str:
console.print(self)
return capture.get().strip()

@lru_cache(maxsize=1_000)
def format_date(self, date: date) -> str:
return format_date(date, format=self.theme.datefmt, locale=self.locale)

@lru_cache(maxsize=1_000)
def date_range(
self,
start: Optional[date],
end: Optional[date],
sep: str = "-",
collapse: bool = True,
) -> str:
if start is None:
if end is None:
return ""
return f"{sep} {self.format_date(end)}"

if end is None:
return f"{self.format_date(start)} {sep} {self.translation.present}"

collapsible = start.month == end.month and start.year == end.year
if collapsible and collapse:
return self.format_date(end)

return f"{self.format_date(start)} {sep} {self.format_date(end)}"

@classmethod
# A property would be nicer but it's not supported from Python 3.11 on:
# https://docs.python.org/3.11/library/functions.html#classmethod
Expand All @@ -104,12 +135,12 @@ def from_model_config(cls, model: ResumeSchema) -> "Template":
except KeyError as e:
raise ResumeConfigError(f"Unknown theme: {theme_name}") from e

if (translation_name := config.language) is None:
translation_name = "en"
if (language := config.language) is None:
language = "en"
try:
translation = TRANSLATIONS[translation_name]
translation = TRANSLATIONS[language]
except KeyError as e:
raise ResumeConfigError(f"Unknown translation: {translation_name}") from e
raise ResumeConfigError(f"Unknown language: {language}") from e

if (template_name := config.template) is None:
template_name = Sequential.__name__
Expand All @@ -125,6 +156,7 @@ def from_model_config(cls, model: ResumeSchema) -> "Template":
model=model,
theme=theme,
translation=translation,
locale=Locale(language),
ascii_only=ascii_only,
)

Expand Down Expand Up @@ -263,7 +295,7 @@ def _(self, item: WorkItem, theme: Theme) -> RenderableGenerator:
)

yield from horizontal_fill(
tagline, theme.date_range(item.startDate, item.endDate, theme.datefmt)
tagline, self.date_range(item.startDate, item.endDate)
)

if position := item.position:
Expand Down Expand Up @@ -307,7 +339,7 @@ def _(self, item: Skill, theme: Theme) -> RenderableGenerator:
def _(self, item: VolunteerItem, theme: Theme) -> RenderableGenerator:
yield from horizontal_fill(
Text(item.organization or "", style=theme.emphasis[0]),
theme.date_range(item.startDate, item.endDate, theme.datefmt),
self.date_range(item.startDate, item.endDate),
)

if position := item.position:
Expand Down Expand Up @@ -336,7 +368,7 @@ def _(self, item: EducationItem, theme: Theme) -> RenderableGenerator:
(item.area or "", theme.emphasis[1]),
f" ({item.studyType})" if item.studyType else "",
),
theme.date_range(item.startDate, item.endDate, theme.datefmt),
self.date_range(item.startDate, item.endDate),
)

if score := item.score:
Expand All @@ -359,7 +391,7 @@ def _(self, item: Award, theme: Theme) -> RenderableGenerator:
(item.title or "", theme.emphasis[0]),
(f" ({item.awarder})" if item.awarder else "", theme.emphasis[1]),
),
item.date.strftime(theme.datefmt) if item.date else "",
self.format_date(item.date) if item.date else "",
)

if summary := item.summary:
Expand All @@ -373,7 +405,7 @@ def _(self, item: Certificate, theme: Theme) -> RenderableGenerator:
(item.name or "", theme.emphasis[0]),
(f" ({item.issuer})" if item.issuer else "", theme.emphasis[1]),
),
item.date.strftime(theme.datefmt) if item.date else "",
self.format_date(item.date) if item.date else "",
)

if url := item.url:
Expand All @@ -387,7 +419,7 @@ def _(self, item: Publication, theme: Theme) -> RenderableGenerator:
(item.name or "", theme.emphasis[0]),
(f" ({item.publisher})" if item.publisher else "", theme.emphasis[1]),
),
item.releaseDate.strftime(theme.datefmt) if item.releaseDate else "",
self.format_date(item.releaseDate) if item.releaseDate else "",
)

if summary := item.summary:
Expand Down Expand Up @@ -433,7 +465,7 @@ def _(self, item: Project, theme: Theme) -> RenderableGenerator:
(item.name or "", theme.emphasis[0]),
(f" - {item.type}" if item.type else "", theme.emphasis[1]),
),
theme.date_range(item.startDate, item.endDate, theme.datefmt),
self.date_range(item.startDate, item.endDate),
)

if description := item.description:
Expand Down
30 changes: 2 additions & 28 deletions ancv/visualization/themes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from datetime import date
from typing import Optional

from pydantic import BaseModel
from rich.style import Style

Expand All @@ -15,37 +12,14 @@ class Theme(BaseModel):
class Config:
arbitrary_types_allowed = True # No validator for `Style` available

@staticmethod
def date_range(
start: Optional[date],
end: Optional[date],
fmt: str,
sep: str = "-",
ongoing: str = "present",
collapse: bool = True,
) -> str:
if start is None:
if end is None:
return ""
return f"{sep} {end.strftime(fmt)}"

if end is None:
return f"{start.strftime(fmt)} {sep} {ongoing}"

collapsible = start.month == end.month and start.year == end.year
if collapsible and collapse:
return f"{end.strftime(fmt)}"

return f"{start.strftime(fmt)} {sep} {end.strftime(fmt)}"


THEMES = {
"plain": Theme(
emphasis=[Style(), Style(), Style(), Style()],
headlines=[Style(), Style(), Style(), Style()],
bullet="*",
rulechar="─",
datefmt="%Y-%m",
datefmt="yyyy-MM",
),
"basic": Theme(
emphasis=[
Expand All @@ -62,6 +36,6 @@ def date_range(
],
bullet="*",
rulechar="─",
datefmt="%B %Y",
datefmt="MMMM yyyy",
),
}
3 changes: 3 additions & 0 deletions ancv/visualization/translations.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Translation(BaseModel):
references: str
interests: str
projects: str
present: str


TRANSLATIONS = {
Expand All @@ -36,6 +37,7 @@ class Translation(BaseModel):
references="References",
interests="Interests",
projects="Projects",
present="present",
),
"de": Translation(
score="Note",
Expand All @@ -53,5 +55,6 @@ class Translation(BaseModel):
references="Referenzen",
interests="Interessen",
projects="Projekte",
present="heute",
),
}
Loading

0 comments on commit 492ee55

Please sign in to comment.