feat: more complete i18n infrastructure
This commit is contained in:
158
lib/bds/i18n.ex
Normal file
158
lib/bds/i18n.ex
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 d’aperçu demandée est introuvable.",
|
|
||||||
"render.notFound.back" => "Retour à l’accueil de l’aperç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
48
priv/i18n/locales/de.json
Normal 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
48
priv/i18n/locales/en.json
Normal 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
48
priv/i18n/locales/es.json
Normal 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
48
priv/i18n/locales/fr.json
Normal 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 à l’accueil de l’aperçu",
|
||||||
|
"render.notFound.message": "La page d’aperç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
48
priv/i18n/locales/it.json
Normal 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
49
test/bds/i18n_test.exs
Normal 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
|
||||||
Reference in New Issue
Block a user