mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 23:12:21 -04:00
plural rules and decimal separator should use bundle's language
Instead of providing the list of languages in preferred order, when creating a bundle we need to specify the bundle language as the first language, so that the correct plural rules are used. Fluent's docs are misleading here; I will submit a PR to fix them. The old behaviour caused: https://forums.ankiweb.net/t/bug-in-review-intervals-for-some-languages-in-number-of-cards/5744
This commit is contained in:
parent
6d596c8fc9
commit
77c9db5bba
2 changed files with 51 additions and 26 deletions
|
@ -36,15 +36,15 @@ pub use tr_strs;
|
||||||
/// If a fully qualified folder exists (eg, en_GB), return that.
|
/// If a fully qualified folder exists (eg, en_GB), return that.
|
||||||
/// Otherwise, try the language alone (eg en).
|
/// Otherwise, try the language alone (eg en).
|
||||||
/// If neither folder exists, return None.
|
/// If neither folder exists, return None.
|
||||||
fn lang_folder(lang: Option<&LanguageIdentifier>, ftl_folder: &Path) -> Option<PathBuf> {
|
fn lang_folder(lang: &Option<LanguageIdentifier>, ftl_root_folder: &Path) -> Option<PathBuf> {
|
||||||
if let Some(lang) = lang {
|
if let Some(lang) = lang {
|
||||||
if let Some(region) = lang.region {
|
if let Some(region) = lang.region {
|
||||||
let path = ftl_folder.join(format!("{}_{}", lang.language, region));
|
let path = ftl_root_folder.join(format!("{}_{}", lang.language, region));
|
||||||
if fs::metadata(&path).is_ok() {
|
if fs::metadata(&path).is_ok() {
|
||||||
return Some(path);
|
return Some(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let path = ftl_folder.join(lang.language.to_string());
|
let path = ftl_root_folder.join(lang.language.to_string());
|
||||||
if fs::metadata(&path).is_ok() {
|
if fs::metadata(&path).is_ok() {
|
||||||
Some(path)
|
Some(path)
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,7 +52,7 @@ fn lang_folder(lang: Option<&LanguageIdentifier>, ftl_folder: &Path) -> Option<P
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// fallback folder
|
// fallback folder
|
||||||
let path = ftl_folder.join("templates");
|
let path = ftl_root_folder.join("templates");
|
||||||
if fs::metadata(&path).is_ok() {
|
if fs::metadata(&path).is_ok() {
|
||||||
Some(path)
|
Some(path)
|
||||||
} else {
|
} else {
|
||||||
|
@ -193,6 +193,12 @@ two-args-key = {$one}と{$two}
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_pl_text() -> &'static str {
|
||||||
|
"
|
||||||
|
one-arg-key = fake Polish {$one}
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse resource text into an AST for inclusion in a bundle.
|
/// Parse resource text into an AST for inclusion in a bundle.
|
||||||
/// Returns None if text contains errors.
|
/// Returns None if text contains errors.
|
||||||
/// extra_text may contain resources loaded from the filesystem
|
/// extra_text may contain resources loaded from the filesystem
|
||||||
|
@ -239,12 +245,11 @@ fn get_bundle(
|
||||||
/// Get a bundle that includes any filesystem overrides.
|
/// Get a bundle that includes any filesystem overrides.
|
||||||
fn get_bundle_with_extra(
|
fn get_bundle_with_extra(
|
||||||
text: &str,
|
text: &str,
|
||||||
lang: Option<&LanguageIdentifier>,
|
lang: Option<LanguageIdentifier>,
|
||||||
ftl_folder: &Path,
|
ftl_root_folder: &Path,
|
||||||
locales: &[LanguageIdentifier],
|
|
||||||
log: &Logger,
|
log: &Logger,
|
||||||
) -> Option<FluentBundle<FluentResource>> {
|
) -> Option<FluentBundle<FluentResource>> {
|
||||||
let mut extra_text = if let Some(path) = lang_folder(lang, &ftl_folder) {
|
let mut extra_text = if let Some(path) = lang_folder(&lang, &ftl_root_folder) {
|
||||||
match ftl_external_text(&path) {
|
match ftl_external_text(&path) {
|
||||||
Ok(text) => text,
|
Ok(text) => text,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -258,18 +263,28 @@ fn get_bundle_with_extra(
|
||||||
|
|
||||||
if cfg!(test) {
|
if cfg!(test) {
|
||||||
// inject some test strings in test mode
|
// inject some test strings in test mode
|
||||||
match lang {
|
match &lang {
|
||||||
None => {
|
None => {
|
||||||
extra_text += test_en_text();
|
extra_text += test_en_text();
|
||||||
}
|
}
|
||||||
Some(lang) if lang.language == "ja" => {
|
Some(lang) if lang.language == "ja" => {
|
||||||
extra_text += test_jp_text();
|
extra_text += test_jp_text();
|
||||||
}
|
}
|
||||||
|
Some(lang) if lang.language == "pl" => {
|
||||||
|
extra_text += test_pl_text();
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_bundle(text, extra_text, locales, log)
|
let mut locales = if let Some(lang) = lang {
|
||||||
|
vec![lang]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
locales.push("en-US".parse().unwrap());
|
||||||
|
|
||||||
|
get_bundle(text, extra_text, &locales, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -281,18 +296,18 @@ pub struct I18n {
|
||||||
impl I18n {
|
impl I18n {
|
||||||
pub fn new<S: AsRef<str>, P: Into<PathBuf>>(
|
pub fn new<S: AsRef<str>, P: Into<PathBuf>>(
|
||||||
locale_codes: &[S],
|
locale_codes: &[S],
|
||||||
ftl_folder: P,
|
ftl_root_folder: P,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ftl_folder = ftl_folder.into();
|
let ftl_root_folder = ftl_root_folder.into();
|
||||||
let mut langs = vec![];
|
let mut input_langs = vec![];
|
||||||
let mut bundles = Vec::with_capacity(locale_codes.len() + 1);
|
let mut bundles = Vec::with_capacity(locale_codes.len() + 1);
|
||||||
let mut resource_text = vec![];
|
let mut resource_text = vec![];
|
||||||
|
|
||||||
for code in locale_codes {
|
for code in locale_codes {
|
||||||
let code = code.as_ref();
|
let code = code.as_ref();
|
||||||
if let Ok(lang) = code.parse::<LanguageIdentifier>() {
|
if let Ok(lang) = code.parse::<LanguageIdentifier>() {
|
||||||
langs.push(lang.clone());
|
input_langs.push(lang.clone());
|
||||||
if lang.language == "en" {
|
if lang.language == "en" {
|
||||||
// if English was listed, any further preferences are skipped,
|
// if English was listed, any further preferences are skipped,
|
||||||
// as the template has 100% coverage, and we need to ensure
|
// as the template has 100% coverage, and we need to ensure
|
||||||
|
@ -301,17 +316,17 @@ impl I18n {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add fallback date/time
|
|
||||||
langs.push("en_US".parse().unwrap());
|
|
||||||
|
|
||||||
for lang in &langs {
|
let mut output_langs = vec![];
|
||||||
|
for lang in input_langs {
|
||||||
// if the language is bundled in the binary
|
// if the language is bundled in the binary
|
||||||
if let Some(text) = ftl_localized_text(lang) {
|
if let Some(text) = ftl_localized_text(&lang) {
|
||||||
if let Some(bundle) =
|
if let Some(bundle) =
|
||||||
get_bundle_with_extra(text, Some(lang), &ftl_folder, &langs, &log)
|
get_bundle_with_extra(text, Some(lang.clone()), &ftl_root_folder, &log)
|
||||||
{
|
{
|
||||||
resource_text.push(text);
|
resource_text.push(text);
|
||||||
bundles.push(bundle);
|
bundles.push(bundle);
|
||||||
|
output_langs.push(lang);
|
||||||
} else {
|
} else {
|
||||||
error!(log, "Failed to create bundle for {:?}", lang.language)
|
error!(log, "Failed to create bundle for {:?}", lang.language)
|
||||||
}
|
}
|
||||||
|
@ -320,15 +335,17 @@ impl I18n {
|
||||||
|
|
||||||
// add English templates
|
// add English templates
|
||||||
let template_text = ftl_template_text();
|
let template_text = ftl_template_text();
|
||||||
|
let template_lang = "en-US".parse().unwrap();
|
||||||
let template_bundle =
|
let template_bundle =
|
||||||
get_bundle_with_extra(template_text, None, &ftl_folder, &langs, &log).unwrap();
|
get_bundle_with_extra(template_text, None, &ftl_root_folder, &log).unwrap();
|
||||||
resource_text.push(template_text);
|
resource_text.push(template_text);
|
||||||
bundles.push(template_bundle);
|
bundles.push(template_bundle);
|
||||||
|
output_langs.push(template_lang);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(Mutex::new(I18nInner {
|
inner: Arc::new(Mutex::new(I18nInner {
|
||||||
bundles,
|
bundles,
|
||||||
langs,
|
langs: output_langs,
|
||||||
resource_text,
|
resource_text,
|
||||||
})),
|
})),
|
||||||
log,
|
log,
|
||||||
|
@ -551,10 +568,16 @@ mod test {
|
||||||
|
|
||||||
// Decimal separator
|
// Decimal separator
|
||||||
let i18n = I18n::new(&["pl-PL"], &ftl_dir, log.clone());
|
let i18n = I18n::new(&["pl-PL"], &ftl_dir, log.clone());
|
||||||
// falls back on English, but with Polish separators
|
// Polish will use a comma if the string is translated
|
||||||
|
assert_eq!(
|
||||||
|
i18n.tr_("one-arg-key", Some(tr_args!["one"=>2.07])),
|
||||||
|
"fake Polish 2,07"
|
||||||
|
);
|
||||||
|
|
||||||
|
// but if it falls back on English, it will use an English separator
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
i18n.tr_("two-args-key", Some(tr_args!["one"=>1, "two"=>2.07])),
|
i18n.tr_("two-args-key", Some(tr_args!["one"=>1, "two"=>2.07])),
|
||||||
"two args: 1 and 2,07"
|
"two args: 1 and 2.07"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,9 +73,11 @@ export async function setupI18n(): Promise<I18n> {
|
||||||
}
|
}
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
|
|
||||||
for (const resourceText of json.resources) {
|
for (const i in json.resources) {
|
||||||
const bundle = new FluentBundle(json.langs);
|
const text = json.resources[i];
|
||||||
const resource = new FluentResource(resourceText);
|
const lang = json.langs[i];
|
||||||
|
const bundle = new FluentBundle([lang, "en-US"]);
|
||||||
|
const resource = new FluentResource(text);
|
||||||
bundle.addResource(resource);
|
bundle.addResource(resource);
|
||||||
i18n.bundles.push(bundle);
|
i18n.bundles.push(bundle);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue