Multilingual support using subdirectory-based language detection.
Content files placed in a language subdirectory (e.g., content/tr/articles/merhaba.md)
are detected as that language and output with a language prefix in the URL
(e.g., /tr/articles/merhaba/).
The default language has no URL prefix. Non-default languages get /<lang>/ prefix.
Configuration
config :sayfa, :site,
default_lang: :en,
languages: [
en: [name: "English"],
tr: [name: "Türkçe"]
]Translation Lookup
Translations are resolved in this order:
- Language-specific translations from user config (
languages: [tr: [translations: %{...}]]) - YAML translation file for the requested language (
priv/translations/{lang}.yml) - YAML translation file for the default language (
priv/translations/{default_lang}.yml) - The key itself as fallback
Sayfa ships pre-built YAML translations for 14 languages: en, tr, de, es, fr, it, pt, ja, ko, zh, ar, ru, nl, pl.
Examples
iex> config = %{default_lang: :en, languages: [en: [name: "English"], tr: [name: "Türkçe"]]}
iex> Sayfa.I18n.detect_language("tr/articles/merhaba.md", config)
{:tr, "articles/merhaba.md"}
iex> config = %{default_lang: :en, languages: [en: [name: "English"]]}
iex> Sayfa.I18n.detect_language("articles/hello.md", config)
{:en, "articles/hello.md"}
Summary
Functions
Clears the translation cache.
Returns the list of configured language codes.
Returns a default translation function that uses English YAML translations.
Detects the language from a relative file path.
Returns the URL prefix for a language.
Loads translations for a language from YAML files.
Resolves per-language site configuration overrides.
Returns whether the given language code is a right-to-left language.
Translates a UI string key for a given language.
Returns the text direction for a language code.
Returns a translation closure for a given language and config.
Functions
@spec clear_cache() :: :ok
Clears the translation cache.
Useful for testing or when translation files have been updated.
Returns the list of configured language codes.
Examples
iex> config = %{languages: [en: [name: "English"], tr: [name: "Türkçe"]]}
iex> Sayfa.I18n.configured_language_codes(config)
[:en, :tr]
Returns a default translation function that uses English YAML translations.
Used as a fallback when no language-specific translation function is available.
Examples
iex> t = Sayfa.I18n.default_translate_function()
iex> t.("next")
"Next"
Detects the language from a relative file path.
If the first path segment matches a configured language code (other than default),
returns {lang, remaining_path}. Otherwise returns {default_lang, path}.
Examples
iex> config = %{default_lang: :en, languages: [en: [name: "English"], tr: [name: "Türkçe"]]}
iex> Sayfa.I18n.detect_language("tr/articles/merhaba.md", config)
{:tr, "articles/merhaba.md"}
iex> config = %{default_lang: :en, languages: [en: [name: "English"], tr: [name: "Türkçe"]]}
iex> Sayfa.I18n.detect_language("articles/hello.md", config)
{:en, "articles/hello.md"}
iex> config = %{default_lang: :en, languages: [en: [name: "English"]]}
iex> Sayfa.I18n.detect_language("about.md", config)
{:en, "about.md"}
Returns the URL prefix for a language.
Default language returns "" (no prefix). Non-default languages return
the language code as a string (e.g., "tr").
Examples
iex> config = %{default_lang: :en}
iex> Sayfa.I18n.language_prefix(:en, config)
""
iex> config = %{default_lang: :en}
iex> Sayfa.I18n.language_prefix(:tr, config)
"tr"
Loads translations for a language from YAML files.
Results are cached in :persistent_term for fast subsequent lookups.
Returns an empty map if no YAML file exists for the language.
Examples
iex> translations = Sayfa.I18n.load_translations(:en)
iex> Map.get(translations, "next")
"Next"
Resolves per-language site configuration overrides.
Extracts language-specific config from the languages keyword list,
excluding :name and :translations, and merges onto the base config.
Examples
iex> config = %{title: "My Blog", default_lang: :en, languages: [en: [name: "English"], tr: [name: "Türkçe", title: "Blogum"]]}
iex> resolved = Sayfa.I18n.resolve_site_config(config, :tr, config)
iex> resolved.title
"Blogum"
iex> config = %{title: "My Blog", default_lang: :en, languages: [en: [name: "English"]]}
iex> resolved = Sayfa.I18n.resolve_site_config(config, :en, config)
iex> resolved.title
"My Blog"
Returns whether the given language code is a right-to-left language.
Examples
iex> Sayfa.I18n.rtl_language?(:ar)
true
iex> Sayfa.I18n.rtl_language?(:en)
false
Translates a UI string key for a given language.
Supports optional bindings for interpolation and pluralization.
When the translation value is a map with "one" and "other" keys,
the :count binding selects the plural form. All bindings are interpolated
as %{key} in the resulting string.
Lookup chain:
- Language-specific translations from config
- YAML translation file for the requested language
- YAML translation file for the default language
- The key itself as fallback
Examples
iex> config = %{default_lang: :en, languages: [en: [name: "English"], tr: [name: "Türkçe", translations: %{"next" => "Sonraki"}]]}
iex> Sayfa.I18n.t("next", :tr, config)
"Sonraki"
iex> config = %{default_lang: :en, languages: [en: [name: "English"]]}
iex> Sayfa.I18n.t("next", :en, config)
"Next"
iex> config = %{default_lang: :en, languages: [en: [name: "English"]]}
iex> Sayfa.I18n.t("unknown_key", :en, config)
"unknown_key"
Returns the text direction for a language code.
Returns "rtl" for right-to-left languages (Arabic, Hebrew, Farsi, Urdu)
and "ltr" for all others.
Examples
iex> Sayfa.I18n.text_direction(:ar)
"rtl"
iex> Sayfa.I18n.text_direction(:en)
"ltr"
Returns a translation closure for a given language and config.
The returned function takes a key and returns the translated string.
Examples
iex> config = %{default_lang: :en, languages: [en: [name: "English"]]}
iex> t = Sayfa.I18n.translate_function(:en, config)
iex> t.("next")
"Next"