leptos_i18n

repository·master·Indexed 41 days ago

https://github.com/baptistemontan/leptos_i18n

A translations integration helper for the Leptos web framework that provides compile-time loading of locales and validation for translation keys and interpolation. It supports CSR, SSR (with Actix or Axum), and hydration, allowing developers to interpolate variables and components within localized strings and organize translations using namespaces or YAML.

Tokens
34.3K
Snippets
142
Records
186
Agent score
51%

What's inside leptos_i18n

  1. Overview of Leptos_i18n

    master

    The leptos_i18n crate is designed to simplify internationalization (i18n) within Leptos applications. Its primary advantage is that it loads locales at compile time, which enables compile-time checks for:

    • Translation keys
    • Interpolation keys
    • The selected locale

    This ensures that missing translations or incorrect key usage are caught during the build process rather than at runtime.

  2. What is Locale Inheritance?

    master

    Locale inheritance allows you to create specialized locales that build upon more general ones. This reduces duplication by allowing a child locale to override only specific keys while falling back to its parent locale for all other keys.

    For example, an en-US locale can inherit from en. You only need to define the keys that differ (e.g., changing "colour" to "color"); all other keys will be automatically pulled from en.

  3. Reuse translations with Foreign Keys

    master

    Foreign keys allow you to reference and reuse existing translation keys within your JSON files using the $t() syntax. This helps reduce duplication.

    Syntax Rules:

    • Standard Keys: Use $t(key_name) to replace the placeholder with the value of key_name.
    • Subkeys: To reference a nested key, use dot notation: $t(key.subkey.subsubkey).
    • Namespaces: When using namespaces, you must specify the namespace followed by a colon: $t(namespace:key).
    • Limitations: You can point to any key except those that contain subkeys. You can point to explicitly defaulted keys, but not implicitly defaulted ones.
    {
      "hello_world": "Hello World!",
      "reuse": "message: $t(hello_world)"
    }
    // "reuse" becomes "message: Hello World!"
  4. Understand the Scoping Typestate mechanism

    master

    Scoping in leptos_i18n is implemented using a typestate pattern.

    When you scope an I18nContext<L>, the internal type changes from I18nContext<L> to I18nContext<L, S>, where S is a type representing the path to the keys. This allows the compiler to verify that the keys you are accessing via t! or td! actually exist within that specific scope.

    Key takeaways for developers:

    • Complexity: The generated scope types are deeply nested paths within your i18n module.
    • Abstraction: Use define_scope! to hide this complexity by creating manageable aliases for these types.
    • Performance: Despite the complex type system, the macros use a const closure pattern that is designed to be optimized away by the compiler, resulting in zero-cost abstractions similar to the un-scoped version.
  5. Understand error handling and diagnostics in Formatter parsing

    master

    The leptos_i18n formatter parser is designed to be resilient and provide comprehensive feedback:

    • Argument Name Parsing Errors: If an error occurs while parsing an argument name, the parser stops for that specific argument, emits an error, and continues to the next argument.
    • Value Parsing Errors: If parsing a value for an argument fails, an error is emitted and the parser continues to the next argument.
    • Build Failure: If any error is emitted during the parsing process, the final build method will not be called.
    • Customizing Diagnostics: By default, the parse_with_diagnostics function in the Formatter trait treats all problems as errors. You can override this method to implement custom logic for warnings, multiple errors, or specialized error types.
  6. How locale resolution works in leptos_i18n

    master

    The library detects the user's locale using a prioritized list of methods. The resolution order is as follows:

    1. URL Pathname Prefix: If using I18nRoute, the locale is extracted from the URL (e.g., /en/about).
    2. Cookies: A previously detected locale stored in a cookie.
    3. Accept-Language Header: Used during Server-Side Rendering (SSR) to match the browser's preferred language.
    4. navigator.languages API: Used during Client-Side Rendering (CSR) to match the browser's language settings.
    5. Default Locale: Used as a fallback if no other method succeeds.

    SSR vs CSR Behavior

    • In SSR: The server is responsible for resolving the locale. The client does not attempt to compute a locale upon loading; locale changes on the client can only occur if explicitly set in the context.
    • Caveat for I18nRoute: The URL pathname locale is only resolved when the I18nRoute component begins rendering. Any logic that relies on the resolved locale before this component renders may use an incorrect or default locale.
  7. Enable dynamic loading of translations to reduce binary size

    master

    By default, leptos_i18n bakes translations into the binary at compile time. For projects with many locales or large translation files, this significantly increases binary size.

    Enabling the "dynamic_load" feature allows the client to lazy-load translations as "translation units" (either one unit per locale or one unit per locale per namespace) via server functions. This reduces the initial binary size at the cost of some runtime overhead for requesting and parsing translations.

  8. Handle missing or surplus keys across locales

    master

    The leptos_i18n code generator expects keys to be consistent across all translation files. Discrepancies are measured against the default locale.

    Missing Keys

    If a key exists in the default locale but is missing in a secondary locale:

    1. The secondary locale will fall back to the value from the default locale.
    2. A warning will be emitted during code generation.

    To explicitly tell the generator to use the default locale's value without triggering a warning, set the value to null in the secondary locale:

    {
      "take_value_of_default": null
    }

    Surplus Keys

    If a key exists in a secondary locale but is not present in the default locale:

    1. The key will be ignored.
    2. A warning will be emitted during code generation.
  9. How locale resolution and redirection work in I18nRoute

    master

    Locale resolution follows a specific priority:

    1. URL Prefix: If a locale prefix is present in the URL (e.g., /en/*), that locale is used immediately.
    2. Fallback Resolution: If no prefix is present (e.g., /counter), the locale is resolved via cookies, the Accept-Language request header, or the navigator API.

    Redirection Behavior: If a locale is resolved (via cookies, etc.) that does not match the default locale, I18nRoute triggers an automatic navigation to the correct prefixed URL. For example, if the default is en but a cookie sets the locale to fr, accessing /counter will redirect the user to /fr/counter.

    Navigation & History: When using <A href=.. /> or leptos_router::use_navigate, you do not need to manually manage locale prefixes. The component handles the redirection, and it uses NavigateOptions.replace = true to ensure that intermediate redirection steps do not clutter the browser history.

  10. How to make custom formatters reactive with closures

    master

    By default, custom formatters might require values to implement Display. If you pass a Leptos Signal via a closure (e.g., t!(i18n, my_fmt, var = || signal.get())), the compiler may error because the closure itself does not implement Display.

    To support reactivity, you can define a custom trait in your crate that accepts any function returning a Display type, and use that trait in your view_bounds and to_view implementations.

    1. Define a helper trait:
    pub trait ToDisplayFn: 'static + Clone + Send + Sync {
        type Value: Display;
        fn to_value(&self) -> Self::Value;
    }
    
    impl<T: Display, F> ToDisplayFn for F
    where
        F: Fn() -> T + 'static + Clone + Send + Sync,
    {
        type Value = T;
        fn to_value(&self) -> Self::Value {
            self()
        }
    }
    1. Use it in view_bounds:
    fn view_bounds(&self) -> TokenStream {
        quote!(crate::ToDisplayFn)
    }
    1. Use it in to_view:
    fn to_view(&self, key: &syn::Ident, locale_field: &syn::Ident) -> TokenStream {
        quote! {
            move || {
                std::format!(#fmt_string, crate::ToDisplayFn::to_value(&#key))
            }
        }
    }
  11. Use Implicit (Automatic) Locale Inheritance

    master

    The crate automatically establishes inheritance based on the structure of the locale string. It follows a pattern where more specific locales inherit from more general ones.

    Matching Pattern: language[-region][-anything-else] inherits from language[-region].

    Note: Scripts, variants, and extensions are ignored during automatic matching. For example, en-Latn-US-Valencia-u-ca-buddhist will automatically inherit from en-US because the system only considers the language (en) and region (US) components.