zng/l10n.rs
1//! Localization service, sources and other types.
2//!
3//! Localized text is declared using the [`l10n!`] macro, it provides a read-only text variable that automatically
4//! updates to be best localized text available given the current loaded localization and the app language.
5//!
6//! ```
7//! use zng::prelude::*;
8//! # fn example() {
9//!
10//! let click_count = var(0u32);
11//! # let _ =
12//! Window! {
13//! title = l10n!("window-title", "Window Title");
14//! child = Button! {
15//! on_click = hn!(click_count, |_| click_count.set(click_count.get() + 1));
16//! child = Text!(l10n!("click-count", "Clicked {$n} times", n = click_count.clone()));
17//! };
18//! }
19//! # ; }
20//! ```
21//!
22//! In the example above declares two localization messages, "window.title" and "btn.click_count", if
23//! these messages are localized for the current language the localized text is used, otherwise the provided
24//! fallback is used.
25//!
26//! The [`L10N`] service can be used to set the app language and load localization resources. The example below
27//! sets the language to en-US and loads localization from a directory using [`L10N.load_dir`].
28//!
29//! ```no_run
30//! use zng::prelude::*;
31//!
32//! APP.defaults().run_window("main", async {
33//! // start loading localization resources
34//! L10N.load_dir(zng::env::res("l10n"));
35//! // set the app language, by default is the system language
36//! L10N.app_lang().set(lang!("en-US"));
37//! // preload the localization resources for a language
38//! L10N.wait_first(lang!("en-US")).await;
39//!
40//! Window! {
41//! // ..
42//! }
43//! });
44//! ```
45//!
46//! The service also supports embedded localization resources in the `.tar` and `.tar.gz` formats using
47//! [`L10N.load_tar`], see the [localize example] for more details. You can also implement more container formats using [`L10N.load`].
48//!
49//! [`L10N.load_dir`]: crate::l10n::L10N::load_dir
50//! [`L10N.load_tar`]: crate::l10n::L10N::load_tar
51//! [`L10N.load`]: crate::l10n::L10N::load
52//! [localize example]: https://github.com/zng-ui/zng/blob/main/examples/localize/build.rs
53//!
54//! # Fluent
55//!
56//! The localization files are in the [Fluent](https://projectfluent.org/) format. Fluent empowers translators to
57//! script things like plural forms, for this reason a localization file should be provided even for the same
58//! language the `l10n!` fallback text is written in.
59//!
60//! ```ftl
61//! click-count = {$n ->
62//! [one] Clicked {$n} time
63//! *[other] Clicked {$n} times
64//! }
65//! ```
66//!
67//! The example above demonstrates a localized message that provides plural alternatives for the English language.
68//!
69//! # Scraper
70//!
71//! The `cargo zng l10n` tool can be used to generate a Fluent file from source code, the Fluent file can be
72//! used as a template for translators, it will include the fallback text and comments written close the key
73//! declaration.
74//!
75//! ```
76//! use zng::prelude::*;
77//! # fn example() {
78//!
79//! // l10n-### This standalone comment is added to the scraped template file.
80//!
81//! let click_count = var(0u32);
82//! # let _ =
83//! Window! {
84//! title = l10n!("window-title", "Window Title");
85//! child = Button! {
86//! on_click = hn!(click_count, |_| click_count.set(click_count.get() + 1));
87//! // l10n-# This comment is added to the `"click-count"` entry.
88//! child = Text!(l10n!("click-count", "Clicked {$n} times", n = click_count.clone()));
89//! };
90//! }
91//! # ; }
92//! ```
93//!
94//! When the example above is scrapped it generates:
95//!
96//! ```ftl
97//! ### This standalone comment is added to all scraped template files.
98//!
99//! # This comment is added to the `"click-count"` entry.
100//! click-count = Clicked {$n} times
101//! ```
102//!
103//! See the [`l10n!`] documentation for a full explanation of how the Scraper converts comments and the
104//! `l10n!` calls into Fluent files.
105//!
106//! [`l10n!`]: crate::l10n::l10n
107//!
108//! # Commands
109//!
110//! Commands metadata can be localized and scrapped, to enable this set `l10n!:` on the [`command!`](zng::event::command) declarations.
111//!
112//! If the first metadata is `l10n!:` the command init will attempt to localize the other string metadata. The `cargo zng l10n`
113//! command line tool scraps commands that set this special metadata.
114//!
115//! ```
116//! # use zng_app::{event::{command, CommandNameExt, CommandInfoExt}, shortcut::{CommandShortcutExt, shortcut}};
117//! command! {
118//! pub static FOO_CMD {
119//! l10n!: true,
120//! name: "Foo!",
121//! info: "Does the foo thing",
122//! };
123//! }
124//! ```
125//!
126//! The example above will be scrapped as:
127//!
128//! ```ftl
129//! FOO_CMD =
130//! .name = Foo!
131//! .info = Does the foo thing.
132//! ```
133//!
134//! The `l10n!:` meta can also be set to a localization file name:
135//!
136//! ```
137//! # use zng_app::{event::{command, CommandNameExt, CommandInfoExt}, shortcut::{CommandShortcutExt, shortcut}};
138//! command! {
139//! pub static FOO_CMD {
140//! l10n!: "file",
141//! name: "Foo!",
142//! };
143//! }
144//! ```
145//!
146//! The example above is scrapped to `{l10n-dir}/{lang}/file.ftl` files.
147//!
148//! ## Limitations
149//!
150//! Interpolation is not supported in command localization strings.
151//!
152//! The `l10n!:` value must be a *textual* literal, that is, it can be only a string literal or a `bool` literal, and it cannot be
153//! inside a macro expansion.
154//!
155//! # Dependency Localization
156//!
157//! The `cargo zng l10n` tool copies all localization files from dependency crates by default. The Fluent files
158//! are placed in `dir/{lang}/deps/*/*/*.ftl`, the [`L10N`] service knows to search these directories structure for localization.
159//!
160//! Note that dependency crates are **not scrapped**, the library crate authors are expected to scrap (and translate) text
161//! to a `{crate}/l10n` directory, beside the `{crate}/Cargo.toml`.
162//!
163//! The copied dependency resources are excluded from Git source control by default, the recommended workflow is to rerun `cargo zng l10n`
164//! after each `cargo update`. Projects created using `cargo zng new` can just run `cargo do update`, that is a shorthand for:
165//!
166//! ```console
167//! cargo update
168//! cargo zng l10n --no-local --no-pkg --output res/l10n
169//! ```
170//!
171//! ## Packaging
172//!
173//! When packaging a release build for publish you want to only include the dependency resources for the locales supported by your
174//! application. The easiest way to do this is `cargo zng res` with a `.zr-l10n` tool call, it will automatically filter out
175//! languages that only have dependency resources and also. Call `cargo zng res --tool sh` to read detailed help.
176//!
177//! ### Subsetting
178//!
179//! Your app may not use all localization resources from dependencies, with some work you can collect a *subset* allow list that
180//! can be used by `.zr-l10n` to only package the entries used, in this mode the dependency Fluent files are edited down to
181//! include only the text actually used by the app.
182//!
183//! The easiest way to get started is to build with the `"l10n_usage_recorder"` Cargo feature, run the app and visit every
184//! screen and state that uses localization text. The subset profile is saved to `res/optimization-profiles/zng-ext-l10n.rec.subset` by default,
185//! you can change the location by setting the `ZNG_L10N_PROFILE_FILE` env var.
186//!
187//! The profile is a text file with format:
188//!
189//! ```txt
190//! # comments
191//!
192//! {dependency}//{file}/{id}.{attribute}
193//! ```
194//!
195//! The dependency is the crate package name, `{file}/{id}.{attribute}` is the same key syntax used by [`l10n!`].
196//!
197//! The generated file will has a comment header with instruction on how to manually add icons.
198//!
199//! The profile file can be added to source control, the recorded entries are sorted so changes are stable.
200//!
201//! # Full API
202//!
203//! See [`zng_ext_l10n`] for the full localization API.
204
205pub use zng_ext_l10n::{
206 IntoL10nVar, L10N, L10nArgument, L10nDir, L10nMessageBuilder, L10nSource, L10nTar, LANG_VAR, Lang, LangFilePath, LangMap, LangResource,
207 LangResourceStatus, LangResources, Langs, NilL10nSource, SwapL10nSource, l10n, lang,
208};