[![Codecov](https://codecov.io/github/alexkazik/typed-i18n/coverage.svg?branch=main)](https://codecov.io/gh/alexkazik/typed-i18n) [![Dependency status](https://deps.rs/repo/github/alexkazik/typed-i18n/status.svg)](https://deps.rs/repo/github/alexkazik/typed-i18n) [![crates.io](https://img.shields.io/crates/v/typed-i18n.svg)](https://crates.io/crates/typed-i18n) [![Downloads](https://img.shields.io/crates/d/typed-i18n.svg)](https://crates.io/crates/typed-i18n) [![Github stars](https://img.shields.io/github/stars/alexkazik/typed-i18n.svg?logo=github)](https://github.com/alexkazik/typed-i18n/stargazers) [![License](https://img.shields.io/crates/l/typed-i18n.svg)](./LICENSE) # crate typed-i18n Convert a language file and an enum into a type safe i18n system. ## Basic Usage Yaml language file: (json and lrc is also supported, more below) ```yaml hello_world: en: Hello World de: Hallo Welt hello_you: en: Hello %{name} ``` Code: ```rust #[derive(Copy, Clone, TypedI18N)] #[typed_i18n(filename = "example.yaml")] #[typed_i18n(builder = "mixed_str", prefix = "str_")] enum Language { En, De } ``` Generated code: ```rust impl Language { fn str_hello_world(self) -> &'static str; fn str_hello_you(self, name: &str) -> String; } ``` Usage: ```rust fn print_hello(language: Language, name: Option<&str>){ if let Some(name) = name { println!("{}", language.str_hello_you(name)); } else { println!("{}", language.str_hello_world()); } } ``` A global stored language is also supported, see [global](#global) below. ### More builders Different generators add different code: ```rust #[typed_i18n(builder = "static_str", prefix = "sta_")] #[typed_i18n(builder = "String")] ``` Generated code: ```rust impl Language { fn sta_hello_world(self) -> &'static str; // The static_str builder skips all parameterized messages fn hello_world(self) -> String; fn hello_you(self, name: &str) -> String; } ``` ### Other output types Also types other than strings are possible: ```yaml #in addition to the messages above hello_with_icon: en: Hello %{name}*{icon} ``` ```rust #[typed_i18n(builder = "HtmlBuilder", input = "Html", prefix = "html_")] ``` Generated code: ```rust impl Language { fn html_hello_world(self) -> ::Output; fn html_hello_you(self, name: &str) -> ::Output; fn html_hello_with_icon>( self, name: &str, icon: T1, ) -> ::Output; } ``` See [examples](https://github.com/alexkazik/typed-i18n/blob/main/examples/src/html.rs) for a `HtmlBuilder` for `yew::Html` implementation. ## Input Fields: - `filename`: the path to the translations, relative to the crate root (required). - `separator`: used for combining paths of a tree, default: `_`. - `global`: used for a global stored language, see [global](#global) below, default: not used. Example: `#[typed_i18n(filename = "example.yaml", separator = "_")]` The messages can be supplied in - yaml (as seen in the examples above) - json - lrc json: ```json {"hello_world": {"en": "Hello World"}} ``` The yaml and json files may have a `_version: 2` entry, to be compatible with other i18n tools. (Other numbers are an error, omitting it is ok.) The yaml and json files may have a deeper tree. In that case the separator is used to join the segments into a function name. Example: ```yaml hello: world: en: Hello World you: en: Hello %{name} ``` With the separator `_` it will result in the same names as the examples above. This may help you structure the messages. Since the name is a function name a `.` as a separator is not allowed, but you can use `ยท` (U+00B7). lrc: ```text ; lines starting with `;` or `/` will be skipped #hello_world en Hello World ``` ## Output Fields: - `builder`: The name of an item which implements [`Builder`], or a special value. - `prefix`: Prefix for all functions generated by this builder, default: the empty string. - `str_conversion`: How to convert str parameters, default `ref`. - `input`: Type of the input (for typed inputs), default: no input. - `input_conversion`: How to convert the parameter into the input type, default: `into`. ### `builder` Must be either a special value or a type for which [`Builder`] is implemented. All builders without a input type will skip all messages for it. All the builders below are built-in. The `input` must not be set for these. Always available: * `static_str`: all messages without parameters have the return type `&'static str`. All others are skipped. * `mixed_str`: all messages without parameters have the return type `&'static str` all others will have the return type `String`. * `String` * `Cow<'static, str>` * `_`: The functions will be generic over the builder type. This is sometimes not helpful, e.g. when `.into()` or `.as_ref()` will be called on the result of the function. All except `static_str` and `_` require the feature `alloc` (enabled by default) and that `String` or `Cow` is in scope. To learn about custom type builder see the example above and in the [examples directory](https://github.com/alexkazik/typed-i18n/blob/main/examples). ### `prefix` All functions of this `builder` will be composed of the prefix and the message name. Using identical prefixes or overlapping names will result in functions with identical names and thus result in a compile error. This will not be checked by the derive macro. ### `str_conversion` The conversion for all string (normal) `%{param}` parameters: * `ref` (default): `fn str_hello_you(self, name: &str) -> String`. * `as_ref`: `fn str_hello_you>(self, name: S1) -> String`. ### `input` Type of the input for typed `*{param}` parameters. All builders without `input` will silently skip all messages with typed parameters. With `_` a generic function over the input type is created. May lead to the same problems as a generic builder type. ### `input_conversion` How to convert the types inputs: * `value`: The input type as parameter: `fn html_hello_with_icon(self, name: &str, icon: Input) -> ::Output` * `into` (default): Something that can be converted into the input type: `fn html_hello_with_icon>(self, name: &str, icon: T1) -> ::Output` * `ref`: A reference to the input type: `fn html_hello_with_icon(self, name: &str, icon: &Input) -> ::Output` * `as_ref`: Something that can be converted into a reference to the input type: `fn html_hello_with_icon>(self, name: &str, icon: T1) -> ::Output` For `value` and `into` to work the builder must also implement [`BuilderFromValue`]. For `ref` and `as_ref` to work the builder must also implement [`BuilderFromRef`]. ## Language The enum values can be annotated with: * `name`: The name of the language in the messages file, defaults to the name of the value in `snake_case`. * `fallback`: A space and/or comma separated list of language names which defines which language should be used when a message is missing. Default: all languages in listing order (not necessary in numerical order). * `default`: Is used for a [global](#global) storage. Only one language may be the default. Example: ```rust #[derive(Copy, Clone, TypedI18N)] #[typed_i18n(filename = "example.yaml")] #[typed_i18n(builder = "mixed_str", prefix = "str_")] enum Language { De, #[typed_i18n(name = "en")] English, #[typed_i18n(fallback = "en, de")] EnAu, } ``` ## Global It is possible to generate a global language storage. Code: ```rust #[derive(Copy, Clone, TypedI18N)] #[typed_i18n(filename = "example.yaml", global = "atomic")] #[typed_i18n(builder = "static_str")] enum Language { En, #[typed_i18n(default = "true")] De } ``` Generated code: ```rust impl Language { fn global() -> Self; fn set_global(self); } ``` Example usage: ```rust // de is the default assert_eq!(Language::global().hello_world(), "Hallo Welt"); Language::En.set_global(); assert_eq!(Language::global().hello_world(), "Hello world"); ``` The default language (either marked as such, see example above, or the first one) is initially stored as the global language. Currently `atomic` is the only implementation, which uses an `AtomicU8` to store the language. The conversion does not depend on the representation of the enum. In case of more than 256 languages an `AtomicUsize` is used. ## Features - `alloc`, enabled by default: Provide Builder implementations for `String` and `Cow<'static, str>`, also support `mixed_str`. The library is always `no_std`.