Sayfa.I18n (Sayfa v0.5.0)

Copy Markdown View Source

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:

  1. Language-specific translations from user config (languages: [tr: [translations: %{...}]])
  2. YAML translation file for the requested language (priv/translations/{lang}.yml)
  3. YAML translation file for the default language (priv/translations/{default_lang}.yml)
  4. 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

clear_cache()

@spec clear_cache() :: :ok

Clears the translation cache.

Useful for testing or when translation files have been updated.

configured_language_codes(config)

@spec configured_language_codes(map()) :: [atom()]

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]

default_translate_function()

@spec default_translate_function() :: (String.t() -> String.t())

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"

detect_language(relative_path, config)

@spec detect_language(String.t(), map()) :: {atom(), String.t()}

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"}

language_prefix(lang, config)

@spec language_prefix(atom(), map()) :: String.t()

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"

load_translations(lang)

@spec load_translations(atom()) :: map()

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"

resolve_site_config(base_config, lang, config)

@spec resolve_site_config(map(), atom(), map()) :: map()

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"

rtl_language?(lang)

@spec rtl_language?(atom()) :: boolean()

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

t(key, lang, config, bindings \\ [])

@spec t(String.t(), atom(), map(), keyword()) :: String.t()

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:

  1. Language-specific translations from config
  2. YAML translation file for the requested language
  3. YAML translation file for the default language
  4. 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"

text_direction(lang)

@spec text_direction(atom()) :: String.t()

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"

translate_function(lang, config)

@spec translate_function(atom(), map()) :: (String.t() -> String.t())

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"