feat: more complete i18n infrastructure

This commit is contained in:
2026-04-24 06:46:44 +02:00
parent 624b698bb3
commit d04117abdc
10 changed files with 453 additions and 222 deletions

158
lib/bds/i18n.ex Normal file
View File

@@ -0,0 +1,158 @@
defmodule BDS.I18n do
@moduledoc false
@supported_languages [
%{code: "en", flag: "GB"},
%{code: "de", flag: "DE"},
%{code: "fr", flag: "FR"},
%{code: "it", flag: "IT"},
%{code: "es", flag: "ES"}
]
@supported_language_codes Enum.map(@supported_languages, & &1.code)
@format_locales %{
"en" => "en-US",
"de" => "de-DE",
"fr" => "fr-FR",
"it" => "it-IT",
"es" => "es-ES"
}
@flag_emoji %{
"GB" => "🇬🇧",
"DE" => "🇩🇪",
"FR" => "🇫🇷",
"IT" => "🇮🇹",
"ES" => "🇪🇸"
}
@default_language "en"
@default_format_locale "en-US"
@locale_files Path.expand("../../priv/i18n/locales/*.json", __DIR__)
|> Path.wildcard()
|> Enum.sort()
for file <- @locale_files do
@external_resource file
end
@catalogs Enum.into(@locale_files, %{}, fn file ->
locale = file |> Path.basename(".json") |> String.downcase()
{locale, Jason.decode!(File.read!(file))}
end)
def supported_languages, do: @supported_languages
def default_language, do: @default_language
def normalize_language(language) do
language
|> normalize_locale_prefix()
|> case do
value when value in @supported_language_codes -> value
_other -> @default_language
end
end
def resolve_ui_locale(locale), do: resolve_supported_locale(locale) || @default_language
def current_ui_locale do
(System.get_env("LC_ALL") || System.get_env("LC_MESSAGES") || System.get_env("LANG"))
|> resolve_ui_locale()
end
def resolve_render_locale(language), do: resolve_supported_locale(language) || @default_language
def format_locale(language) do
language
|> resolve_supported_locale()
|> case do
nil -> @default_format_locale
locale -> Map.get(@format_locales, locale, @default_format_locale)
end
end
def locale_mapping(language) do
ui_locale = resolve_ui_locale(language)
%{
ui_locale: ui_locale,
format_locale: format_locale(ui_locale)
}
end
def get_render_translations(language) do
language
|> resolve_render_locale()
|> catalog_for_locale()
end
def get_ui_translations(locale) do
locale
|> resolve_ui_locale()
|> catalog_for_locale()
end
def translate(language, key) do
key = key |> to_string() |> String.trim()
case resolve_supported_locale(language) do
nil ->
Map.get(catalog_for_locale(@default_language), key, key)
locale ->
Map.get(catalog_for_locale(locale), key, key)
end
end
def translate_render(language, key), do: translate(language, key)
def translate_ui(locale, key), do: translate(locale, key)
def flag(language) do
language
|> resolve_supported_locale()
|> case do
nil -> @default_language
locale -> locale
end
|> flag_code()
|> then(&Map.get(@flag_emoji, &1, &1))
end
def flag_code(language) do
language
|> resolve_supported_locale()
|> case do
nil -> @default_language
locale -> locale
end
|> then(fn locale ->
@supported_languages
|> Enum.find(hd(@supported_languages), &(&1.code == locale))
|> Map.fetch!(:flag)
end)
end
defp resolve_supported_locale(language) do
language
|> normalize_locale_prefix()
|> case do
value when value in @supported_language_codes -> value
_other -> nil
end
end
defp normalize_locale_prefix(language) do
language
|> to_string()
|> String.trim()
|> String.replace("_", "-")
|> String.downcase()
|> String.split("-", parts: 2)
|> List.first()
end
defp catalog_for_locale(locale) do
Map.get(@catalogs, locale, Map.get(@catalogs, @default_language, %{}))
end
end

View File

@@ -10,7 +10,7 @@ defmodule BDS.Rendering do
alias BDS.Metadata alias BDS.Metadata
alias BDS.Projects alias BDS.Projects
alias BDS.Rendering.Filters alias BDS.Rendering.Filters
alias BDS.Rendering.I18n alias BDS.I18n
alias BDS.Repo alias BDS.Repo
alias BDS.Tags.Tag alias BDS.Tags.Tag
alias BDS.Posts.Post alias BDS.Posts.Post

View File

@@ -3,7 +3,7 @@ defmodule BDS.Rendering.Filters do
use Liquex.Filter use Liquex.Filter
alias BDS.Rendering.I18n alias BDS.I18n
def i18n(value, language, _context) do def i18n(value, language, _context) do
key = value |> to_string() |> String.trim() key = value |> to_string() |> String.trim()

View File

@@ -1,224 +1,8 @@
defmodule BDS.Rendering.I18n do defmodule BDS.Rendering.I18n do
@moduledoc false @moduledoc false
@supported_languages ~w(en de fr it es) defdelegate supported_languages(), to: BDS.I18n
defdelegate normalize_language(language), to: BDS.I18n
@flags %{ defdelegate translate(language, key), to: BDS.I18n
"en" => "🇬🇧", defdelegate flag(language), to: BDS.I18n
"de" => "🇩🇪",
"fr" => "🇫🇷",
"it" => "🇮🇹",
"es" => "🇪🇸"
}
@catalog %{
"en" => %{
"render.archive" => "Archive",
"render.pagination.label" => "Pagination",
"render.pagination.newer" => "newer",
"render.pagination.older" => "older",
"render.notFound.message" => "The requested preview page could not be found.",
"render.notFound.back" => "Back to preview home",
"render.photoArchive.empty" => "No photos found for this archive.",
"render.gallery.empty" => "No linked images found.",
"render.tagCloud.empty" => "No tags found.",
"render.tagCloud.ariaLabel" => "Tag cloud",
"render.calendar.open" => "Open calendar",
"render.calendar.close" => "Close calendar",
"render.calendar.title" => "Archive calendar",
"render.calendar.loading" => "Loading calendar…",
"render.calendar.error" => "Calendar data could not be loaded.",
"render.taxonomy.ariaLabel" => "Taxonomy",
"render.backlinks.label" => "Linked from",
"render.backlinks.ariaLabel" => "Backlinks",
"render.languageSwitcher.ariaLabel" => "Language",
"render.video.youtubeTitle" => "YouTube video",
"render.video.vimeoTitle" => "Vimeo video",
"render.search.placeholder" => "Search...",
"render.search.ariaLabel" => "Site search",
"render.month.1" => "January",
"render.month.2" => "February",
"render.month.3" => "March",
"render.month.4" => "April",
"render.month.5" => "May",
"render.month.6" => "June",
"render.month.7" => "July",
"render.month.8" => "August",
"render.month.9" => "September",
"render.month.10" => "October",
"render.month.11" => "November",
"render.month.12" => "December"
},
"de" => %{
"render.archive" => "Archiv",
"render.pagination.label" => "Seitennummerierung",
"render.pagination.newer" => "neuer",
"render.pagination.older" => "älter",
"render.notFound.message" => "Die angeforderte Vorschauseite konnte nicht gefunden werden.",
"render.notFound.back" => "Zurück zur Vorschau-Startseite",
"render.photoArchive.empty" => "Keine Fotos für dieses Archiv gefunden.",
"render.gallery.empty" => "Keine verknüpften Bilder gefunden.",
"render.tagCloud.empty" => "Keine Tags gefunden.",
"render.tagCloud.ariaLabel" => "Tag-Wolke",
"render.calendar.open" => "Kalender öffnen",
"render.calendar.close" => "Kalender schließen",
"render.calendar.title" => "Archivkalender",
"render.calendar.loading" => "Kalender wird geladen …",
"render.calendar.error" => "Kalenderdaten konnten nicht geladen werden.",
"render.taxonomy.ariaLabel" => "Taxonomie",
"render.backlinks.label" => "Verlinkt von",
"render.backlinks.ariaLabel" => "Rückverweise",
"render.languageSwitcher.ariaLabel" => "Sprache",
"render.video.youtubeTitle" => "YouTube-Video",
"render.video.vimeoTitle" => "Vimeo-Video",
"render.search.placeholder" => "Suchen...",
"render.search.ariaLabel" => "Seitensuche",
"render.month.1" => "Januar",
"render.month.2" => "Februar",
"render.month.3" => "März",
"render.month.4" => "Apr.",
"render.month.5" => "Mai",
"render.month.6" => "Juni",
"render.month.7" => "Juli",
"render.month.8" => "Aug.",
"render.month.9" => "Sept.",
"render.month.10" => "Oktober",
"render.month.11" => "Nov.",
"render.month.12" => "Dezember"
},
"fr" => %{
"render.archive" => "Archives",
"render.pagination.label" => "Navigation paginée",
"render.pagination.newer" => "plus récent",
"render.pagination.older" => "plus ancien",
"render.notFound.message" => "La page daperçu demandée est introuvable.",
"render.notFound.back" => "Retour à laccueil de laperçu",
"render.photoArchive.empty" => "Aucune photo trouvée pour cette archive.",
"render.gallery.empty" => "Aucune image liée trouvée.",
"render.tagCloud.empty" => "Aucun tag trouvé.",
"render.tagCloud.ariaLabel" => "Nuage de tags",
"render.calendar.open" => "Ouvrir le calendrier",
"render.calendar.close" => "Fermer le calendrier",
"render.calendar.title" => "Calendrier des archives",
"render.calendar.loading" => "Chargement du calendrier…",
"render.calendar.error" => "Impossible de charger les données du calendrier.",
"render.taxonomy.ariaLabel" => "Taxonomie",
"render.backlinks.label" => "Lié depuis",
"render.backlinks.ariaLabel" => "Rétroliens",
"render.languageSwitcher.ariaLabel" => "Langue",
"render.video.youtubeTitle" => "Vidéo YouTube",
"render.video.vimeoTitle" => "Vidéo Vimeo",
"render.search.placeholder" => "Rechercher...",
"render.search.ariaLabel" => "Recherche du site",
"render.month.1" => "janvier",
"render.month.2" => "février",
"render.month.3" => "mars",
"render.month.4" => "avril",
"render.month.5" => "mai",
"render.month.6" => "juin",
"render.month.7" => "juillet",
"render.month.8" => "août",
"render.month.9" => "septembre",
"render.month.10" => "octobre",
"render.month.11" => "novembre",
"render.month.12" => "décembre"
},
"it" => %{
"render.archive" => "Archivio",
"render.pagination.label" => "Paginazione",
"render.pagination.newer" => "più recente",
"render.pagination.older" => "più vecchio",
"render.notFound.message" => "La pagina di anteprima richiesta non è stata trovata.",
"render.notFound.back" => "Torna alla home di anteprima",
"render.photoArchive.empty" => "Nessuna foto trovata per questo archivio.",
"render.gallery.empty" => "Nessuna immagine collegata trovata.",
"render.tagCloud.empty" => "Nessun tag trovato.",
"render.tagCloud.ariaLabel" => "Nuvola di tag",
"render.calendar.open" => "Apri calendario",
"render.calendar.close" => "Chiudi calendario",
"render.calendar.title" => "Calendario archivio",
"render.calendar.loading" => "Caricamento calendario…",
"render.calendar.error" => "Impossibile caricare i dati del calendario.",
"render.taxonomy.ariaLabel" => "Tassonomia",
"render.backlinks.label" => "Collegato da",
"render.backlinks.ariaLabel" => "Retrocollegamenti",
"render.languageSwitcher.ariaLabel" => "Lingua",
"render.video.youtubeTitle" => "Video YouTube",
"render.video.vimeoTitle" => "Video Vimeo",
"render.search.placeholder" => "Cerca...",
"render.search.ariaLabel" => "Ricerca nel sito",
"render.month.1" => "gennaio",
"render.month.2" => "febbraio",
"render.month.3" => "marzo",
"render.month.4" => "aprile",
"render.month.5" => "maggio",
"render.month.6" => "giugno",
"render.month.7" => "luglio",
"render.month.8" => "agosto",
"render.month.9" => "settembre",
"render.month.10" => "ottobre",
"render.month.11" => "novembre",
"render.month.12" => "dicembre"
},
"es" => %{
"render.archive" => "Archivo",
"render.pagination.label" => "Paginación",
"render.pagination.newer" => "más reciente",
"render.pagination.older" => "más antiguo",
"render.notFound.message" => "No se pudo encontrar la página de vista previa solicitada.",
"render.notFound.back" => "Volver al inicio de vista previa",
"render.photoArchive.empty" => "No se encontraron fotos para este archivo.",
"render.gallery.empty" => "No se encontraron imágenes vinculadas.",
"render.tagCloud.empty" => "No se encontraron etiquetas.",
"render.tagCloud.ariaLabel" => "Nube de etiquetas",
"render.calendar.open" => "Abrir calendario",
"render.calendar.close" => "Cerrar calendario",
"render.calendar.title" => "Calendario de archivo",
"render.calendar.loading" => "Cargando calendario…",
"render.calendar.error" => "No se pudieron cargar los datos del calendario.",
"render.taxonomy.ariaLabel" => "Taxonomía",
"render.backlinks.label" => "Enlazado desde",
"render.backlinks.ariaLabel" => "Retroenlaces",
"render.languageSwitcher.ariaLabel" => "Idioma",
"render.video.youtubeTitle" => "Vídeo de YouTube",
"render.video.vimeoTitle" => "Vídeo de Vimeo",
"render.search.placeholder" => "Buscar...",
"render.search.ariaLabel" => "Buscar en el sitio",
"render.month.1" => "enero",
"render.month.2" => "febrero",
"render.month.3" => "marzo",
"render.month.4" => "abril",
"render.month.5" => "mayo",
"render.month.6" => "junio",
"render.month.7" => "julio",
"render.month.8" => "agosto",
"render.month.9" => "septiembre",
"render.month.10" => "octubre",
"render.month.11" => "noviembre",
"render.month.12" => "diciembre"
}
}
def normalize_language(language) do
language
|> to_string()
|> String.trim()
|> String.downcase()
|> String.split("-", parts: 2)
|> List.first()
|> case do
value when value in @supported_languages -> value
_other -> "en"
end
end
def translate(language, key) do
normalized_language = normalize_language(language)
@catalog[normalized_language][key] || @catalog["en"][key] || key
end
def flag(language) do
normalized_language = normalize_language(language)
Map.get(@flags, normalized_language, String.upcase(normalized_language))
end
end end

48
priv/i18n/locales/de.json Normal file
View File

@@ -0,0 +1,48 @@
{
"activity.aiAssistant": "KI-Assistent",
"activity.import": "Importieren",
"activity.media": "Medien",
"activity.pages": "Seiten",
"activity.posts": "Beiträge",
"activity.scripts": "Skripte",
"activity.sourceControl": "Quellcodeverwaltung",
"activity.tags": "Tags",
"activity.templates": "Vorlagen",
"common.settings": "Einstellungen",
"render.archive": "Archiv",
"render.backlinks.ariaLabel": "Rückverweise",
"render.backlinks.label": "Verlinkt von",
"render.calendar.close": "Kalender schließen",
"render.calendar.error": "Kalenderdaten konnten nicht geladen werden.",
"render.calendar.loading": "Kalender wird geladen …",
"render.calendar.open": "Kalender öffnen",
"render.calendar.title": "Archivkalender",
"render.gallery.empty": "Keine verknüpften Bilder gefunden.",
"render.languageSwitcher.ariaLabel": "Sprache",
"render.month.1": "Januar",
"render.month.10": "Oktober",
"render.month.11": "Nov.",
"render.month.12": "Dezember",
"render.month.2": "Februar",
"render.month.3": "März",
"render.month.4": "Apr.",
"render.month.5": "Mai",
"render.month.6": "Juni",
"render.month.7": "Juli",
"render.month.8": "Aug.",
"render.month.9": "Sept.",
"render.notFound.back": "Zurück zur Vorschau-Startseite",
"render.notFound.message": "Die angeforderte Vorschauseite konnte nicht gefunden werden.",
"render.pagination.label": "Seitennummerierung",
"render.pagination.newer": "neuer",
"render.pagination.older": "älter",
"render.photoArchive.empty": "Keine Fotos für dieses Archiv gefunden.",
"render.search.ariaLabel": "Seitensuche",
"render.search.placeholder": "Suchen...",
"render.tagCloud.ariaLabel": "Tag-Wolke",
"render.tagCloud.empty": "Keine Tags gefunden.",
"render.taxonomy.ariaLabel": "Taxonomie",
"render.video.vimeoTitle": "Vimeo-Video",
"render.video.youtubeTitle": "YouTube-Video",
"sidebar.chat.yesterday": "Gestern"
}

48
priv/i18n/locales/en.json Normal file
View File

@@ -0,0 +1,48 @@
{
"activity.aiAssistant": "AI Assistant",
"activity.import": "Import",
"activity.media": "Media",
"activity.pages": "Pages",
"activity.posts": "Posts",
"activity.scripts": "Scripts",
"activity.sourceControl": "Source Control",
"activity.tags": "Tags",
"activity.templates": "Templates",
"common.settings": "Settings",
"render.archive": "Archive",
"render.backlinks.ariaLabel": "Backlinks",
"render.backlinks.label": "Linked from",
"render.calendar.close": "Close calendar",
"render.calendar.error": "Calendar data could not be loaded.",
"render.calendar.loading": "Loading calendar…",
"render.calendar.open": "Open calendar",
"render.calendar.title": "Archive calendar",
"render.gallery.empty": "No linked images found.",
"render.languageSwitcher.ariaLabel": "Language",
"render.month.1": "January",
"render.month.10": "October",
"render.month.11": "November",
"render.month.12": "December",
"render.month.2": "February",
"render.month.3": "March",
"render.month.4": "April",
"render.month.5": "May",
"render.month.6": "June",
"render.month.7": "July",
"render.month.8": "August",
"render.month.9": "September",
"render.notFound.back": "Back to preview home",
"render.notFound.message": "The requested preview page could not be found.",
"render.pagination.label": "Pagination",
"render.pagination.newer": "newer",
"render.pagination.older": "older",
"render.photoArchive.empty": "No photos found for this archive.",
"render.search.ariaLabel": "Site search",
"render.search.placeholder": "Search...",
"render.tagCloud.ariaLabel": "Tag cloud",
"render.tagCloud.empty": "No tags found.",
"render.taxonomy.ariaLabel": "Taxonomy",
"render.video.vimeoTitle": "Vimeo video",
"render.video.youtubeTitle": "YouTube video",
"sidebar.chat.yesterday": "Yesterday"
}

48
priv/i18n/locales/es.json Normal file
View File

@@ -0,0 +1,48 @@
{
"activity.aiAssistant": "Asistente de IA",
"activity.import": "Importar",
"activity.media": "Medios",
"activity.pages": "Páginas",
"activity.posts": "Publicaciones",
"activity.scripts": "Scripts",
"activity.sourceControl": "Control de código fuente",
"activity.tags": "Etiquetas",
"activity.templates": "Plantillas",
"common.settings": "Configuración",
"render.archive": "Archivo",
"render.backlinks.ariaLabel": "Retroenlaces",
"render.backlinks.label": "Enlazado desde",
"render.calendar.close": "Cerrar calendario",
"render.calendar.error": "No se pudieron cargar los datos del calendario.",
"render.calendar.loading": "Cargando calendario…",
"render.calendar.open": "Abrir calendario",
"render.calendar.title": "Calendario de archivo",
"render.gallery.empty": "No se encontraron imágenes vinculadas.",
"render.languageSwitcher.ariaLabel": "Idioma",
"render.month.1": "enero",
"render.month.10": "octubre",
"render.month.11": "noviembre",
"render.month.12": "diciembre",
"render.month.2": "febrero",
"render.month.3": "marzo",
"render.month.4": "abril",
"render.month.5": "mayo",
"render.month.6": "junio",
"render.month.7": "julio",
"render.month.8": "agosto",
"render.month.9": "septiembre",
"render.notFound.back": "Volver al inicio de vista previa",
"render.notFound.message": "No se pudo encontrar la página de vista previa solicitada.",
"render.pagination.label": "Paginación",
"render.pagination.newer": "más reciente",
"render.pagination.older": "más antiguo",
"render.photoArchive.empty": "No se encontraron fotos para este archivo.",
"render.search.ariaLabel": "Buscar en el sitio",
"render.search.placeholder": "Buscar...",
"render.tagCloud.ariaLabel": "Nube de etiquetas",
"render.tagCloud.empty": "No se encontraron etiquetas.",
"render.taxonomy.ariaLabel": "Taxonomía",
"render.video.vimeoTitle": "Vídeo de Vimeo",
"render.video.youtubeTitle": "Vídeo de YouTube",
"sidebar.chat.yesterday": "Ayer"
}

48
priv/i18n/locales/fr.json Normal file
View File

@@ -0,0 +1,48 @@
{
"activity.aiAssistant": "Assistant IA",
"activity.import": "Importer",
"activity.media": "Médias",
"activity.pages": "Pages",
"activity.posts": "Articles",
"activity.scripts": "Scripts",
"activity.sourceControl": "Contrôle de source",
"activity.tags": "Tags",
"activity.templates": "Modèles",
"common.settings": "Paramètres",
"render.archive": "Archives",
"render.backlinks.ariaLabel": "Rétroliens",
"render.backlinks.label": "Lié depuis",
"render.calendar.close": "Fermer le calendrier",
"render.calendar.error": "Impossible de charger les données du calendrier.",
"render.calendar.loading": "Chargement du calendrier…",
"render.calendar.open": "Ouvrir le calendrier",
"render.calendar.title": "Calendrier des archives",
"render.gallery.empty": "Aucune image liée trouvée.",
"render.languageSwitcher.ariaLabel": "Langue",
"render.month.1": "janvier",
"render.month.10": "octobre",
"render.month.11": "novembre",
"render.month.12": "décembre",
"render.month.2": "février",
"render.month.3": "mars",
"render.month.4": "avril",
"render.month.5": "mai",
"render.month.6": "juin",
"render.month.7": "juillet",
"render.month.8": "août",
"render.month.9": "septembre",
"render.notFound.back": "Retour à laccueil de laperçu",
"render.notFound.message": "La page daperçu demandée est introuvable.",
"render.pagination.label": "Navigation paginée",
"render.pagination.newer": "plus récent",
"render.pagination.older": "plus ancien",
"render.photoArchive.empty": "Aucune photo trouvée pour cette archive.",
"render.search.ariaLabel": "Recherche du site",
"render.search.placeholder": "Rechercher...",
"render.tagCloud.ariaLabel": "Nuage de tags",
"render.tagCloud.empty": "Aucun tag trouvé.",
"render.taxonomy.ariaLabel": "Taxonomie",
"render.video.vimeoTitle": "Vidéo Vimeo",
"render.video.youtubeTitle": "Vidéo YouTube",
"sidebar.chat.yesterday": "Hier"
}

48
priv/i18n/locales/it.json Normal file
View File

@@ -0,0 +1,48 @@
{
"activity.aiAssistant": "Assistente IA",
"activity.import": "Importa",
"activity.media": "Media",
"activity.pages": "Pagine",
"activity.posts": "Post",
"activity.scripts": "Script",
"activity.sourceControl": "Controllo del codice sorgente",
"activity.tags": "Tag",
"activity.templates": "Template",
"common.settings": "Impostazioni",
"render.archive": "Archivio",
"render.backlinks.ariaLabel": "Retrocollegamenti",
"render.backlinks.label": "Collegato da",
"render.calendar.close": "Chiudi calendario",
"render.calendar.error": "Impossibile caricare i dati del calendario.",
"render.calendar.loading": "Caricamento calendario…",
"render.calendar.open": "Apri calendario",
"render.calendar.title": "Calendario archivio",
"render.gallery.empty": "Nessuna immagine collegata trovata.",
"render.languageSwitcher.ariaLabel": "Lingua",
"render.month.1": "gennaio",
"render.month.10": "ottobre",
"render.month.11": "novembre",
"render.month.12": "dicembre",
"render.month.2": "febbraio",
"render.month.3": "marzo",
"render.month.4": "aprile",
"render.month.5": "maggio",
"render.month.6": "giugno",
"render.month.7": "luglio",
"render.month.8": "agosto",
"render.month.9": "settembre",
"render.notFound.back": "Torna alla home di anteprima",
"render.notFound.message": "La pagina di anteprima richiesta non è stata trovata.",
"render.pagination.label": "Paginazione",
"render.pagination.newer": "più recente",
"render.pagination.older": "più vecchio",
"render.photoArchive.empty": "Nessuna foto trovata per questo archivio.",
"render.search.ariaLabel": "Ricerca nel sito",
"render.search.placeholder": "Cerca...",
"render.tagCloud.ariaLabel": "Nuvola di tag",
"render.tagCloud.empty": "Nessun tag trovato.",
"render.taxonomy.ariaLabel": "Tassonomia",
"render.video.vimeoTitle": "Video Vimeo",
"render.video.youtubeTitle": "Video YouTube",
"sidebar.chat.yesterday": "Ieri"
}

49
test/bds/i18n_test.exs Normal file
View File

@@ -0,0 +1,49 @@
defmodule BDS.I18nTest do
use ExUnit.Case, async: true
test "supported languages, normalization, and UI locale resolution follow the spec" do
assert Enum.map(BDS.I18n.supported_languages(), & &1.code) == ["en", "de", "fr", "it", "es"]
assert BDS.I18n.normalize_language("de-DE") == "de"
assert BDS.I18n.normalize_language("FR_ca") == "fr"
assert BDS.I18n.normalize_language("pt-BR") == "en"
assert BDS.I18n.resolve_ui_locale("it-IT") == "it"
assert BDS.I18n.resolve_ui_locale("es_ES.UTF-8") == "es"
assert BDS.I18n.resolve_ui_locale(nil) == "en"
assert BDS.I18n.resolve_render_locale("fr-FR") == "fr"
assert BDS.I18n.resolve_render_locale("unknown") == "en"
end
test "format locale mapping uses the spec locale table" do
assert BDS.I18n.format_locale("en") == "en-US"
assert BDS.I18n.format_locale("de-DE") == "de-DE"
assert BDS.I18n.format_locale("fr") == "fr-FR"
assert BDS.I18n.format_locale("it-CH") == "it-IT"
assert BDS.I18n.format_locale("es_MX") == "es-ES"
assert BDS.I18n.format_locale("pt-BR") == "en-US"
end
test "render translations are loaded from locale json files with unsupported-locale fallback" do
locale_dir = Application.app_dir(:bds, "priv/i18n/locales")
assert File.exists?(Path.join(locale_dir, "en.json"))
assert File.exists?(Path.join(locale_dir, "de.json"))
assert File.exists?(Path.join(locale_dir, "fr.json"))
assert File.exists?(Path.join(locale_dir, "it.json"))
assert File.exists?(Path.join(locale_dir, "es.json"))
assert BDS.I18n.get_render_translations("de") ==
Jason.decode!(File.read!(Path.join(locale_dir, "de.json")))
assert BDS.I18n.translate("de", "render.notFound.back") ==
"Zurück zur Vorschau-Startseite"
assert BDS.I18n.translate("pt-BR", "render.notFound.back") == "Back to preview home"
end
test "supported locales do not silently fall back to english per key" do
assert BDS.I18n.translate("de", "missing.key") == "missing.key"
end
end