From 27f242fe282e51d86b5daf9b8d2e16e591ff976b Mon Sep 17 00:00:00 2001 From: sjfhsjfh Date: Fri, 7 Feb 2025 00:04:09 +0800 Subject: [PATCH] refactor: finish l10n --- .github/workflows/cargo_test.yml | 2 +- Cargo.lock | 3 + phira/tests/integrated_test.rs | 81 +---------- prpr-l10n/Cargo.toml | 1 + prpr-l10n/src/global.rs | 42 ++++++ prpr-l10n/src/lib.rs | 227 +++++-------------------------- prpr-l10n/src/local.rs | 60 ++++++++ prpr-l10n/src/macros.rs | 99 ++++++++++++++ prpr-l10n/src/tools.rs | 71 ++++++++++ prpr-l10n/tests/langid.rs | 7 + prpr/src/core/chart.rs | 3 +- prpr/src/parse/extra.rs | 1 + prpr/tests/integrated_test.rs | 81 +---------- 13 files changed, 328 insertions(+), 350 deletions(-) create mode 100644 prpr-l10n/src/global.rs create mode 100644 prpr-l10n/src/local.rs create mode 100644 prpr-l10n/src/macros.rs create mode 100644 prpr-l10n/src/tools.rs create mode 100644 prpr-l10n/tests/langid.rs diff --git a/.github/workflows/cargo_test.yml b/.github/workflows/cargo_test.yml index d374fff1..47042d49 100644 --- a/.github/workflows/cargo_test.yml +++ b/.github/workflows/cargo_test.yml @@ -33,4 +33,4 @@ jobs: - name: Run tests run: | - cargo test -p phira --no-default-features -- --skip test_parse_chart + cargo test --workspace --exclude prpr-avc --exclude phira-monitor --no-default-features -- --skip test_parse_chart diff --git a/Cargo.lock b/Cargo.lock index a6d46db0..acfb9dae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2438,6 +2438,8 @@ dependencies = [ "cacache", "chrono", "dotenv-build", + "fluent", + "fluent-syntax", "futures-util", "hex", "image", @@ -2743,6 +2745,7 @@ dependencies = [ "sys-locale", "tracing", "unic-langid", + "walkdir", ] [[package]] diff --git a/phira/tests/integrated_test.rs b/phira/tests/integrated_test.rs index 30b74e22..bd45e911 100644 --- a/phira/tests/integrated_test.rs +++ b/phira/tests/integrated_test.rs @@ -1,80 +1,9 @@ -use once_cell::sync::Lazy; -use prpr::l10n::{LANGS, LANG_IDENTS}; -use std::collections::HashSet; -use std::error::Error; -use std::fmt::Display; -use std::path::Path; -use walkdir::WalkDir; +use prpr_l10n::tools::check_langfile; -#[derive(Debug)] -struct IllegalLanguages { - pub languages: Vec, -} - -impl Display for IllegalLanguages { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.languages)?; - Ok(()) - } -} - -impl Error for IllegalLanguages {} - -fn get_ftl_files(path: &Path) -> Result, Box> { - let mut files = HashSet::new(); - for entry in WalkDir::new(path) { - let entry = entry?; - if entry.file_type().is_file() { - let path = entry.path(); - if path.extension().map_or(false, |ext| ext == "ftl") { - let relative_path = path.strip_prefix(path.parent().unwrap())?; - let normalized = relative_path.to_string_lossy().replace('\\', "/"); - files.insert(normalized); - } - } - } - Ok(files) -} #[test] -fn check_langid() -> anyhow::Result<()> { - // Lang ID is illegal if panicked - Lazy::force(&LANG_IDENTS); - Ok(()) -} - -#[test] -fn check_langfile() -> Result<(), Box> { - let locales_dir = Path::new("locales"); - let zh_cn_dir = locales_dir.join("zh-CN"); - let all_locales: [std::path::PathBuf; 12] = LANGS.map(|x| locales_dir.join(x)); - let zh_cn_files = get_ftl_files(&zh_cn_dir)?; - let mut inconsistent_languages = Vec::new(); - let mut i = 0; - while i < LANGS.len() { - let path = all_locales[i].to_owned(); - if path.is_dir() { - let lang_code = LANGS[i]; - i += 1; - if lang_code == "zh-CN" { - continue; - } - - match get_ftl_files(&path) { - Ok(files) => { - if files != zh_cn_files { - inconsistent_languages.push(lang_code); - } - } - Err(_) => inconsistent_languages.push(lang_code), - } - } +fn check_all() { + match check_langfile(concat!(env!("CARGO_MANIFEST_DIR"), "/locales/")) { + Ok(_) => {} + Err(e) => panic!("Error: {}", e), } - - if !inconsistent_languages.is_empty() { - return Err(Box::new(IllegalLanguages { - languages: inconsistent_languages.iter().map(|x| x.to_string()).collect(), - })); - } - - Ok(()) } diff --git a/prpr-l10n/Cargo.toml b/prpr-l10n/Cargo.toml index fe546a69..0445cb48 100644 --- a/prpr-l10n/Cargo.toml +++ b/prpr-l10n/Cargo.toml @@ -11,3 +11,4 @@ once_cell = "1.16.0" sys-locale = "0.3.1" tracing = "0.1.37" unic-langid = { version = "0.9.1", features = ["macros"] } +walkdir = "2.3.3" diff --git a/prpr-l10n/src/global.rs b/prpr-l10n/src/global.rs new file mode 100644 index 00000000..1a0c3036 --- /dev/null +++ b/prpr-l10n/src/global.rs @@ -0,0 +1,42 @@ +use std::{collections::HashMap, sync::Mutex}; + +use tracing::warn; +use unic_langid::LanguageIdentifier; + +use crate::{fallback_langid, FALLBACK_LANG, LANG_IDENTS}; + +pub struct L10nGlobal { + pub lang_map: HashMap, + pub order: Mutex>, +} + +impl Default for L10nGlobal { + fn default() -> Self { + Self::new() + } +} + +impl L10nGlobal { + pub fn new() -> Self { + let mut lang_map = HashMap::new(); + let mut order = Vec::new(); + let locale_lang = sys_locale::get_locale().unwrap_or_else(|| String::from(FALLBACK_LANG)); + let locale_lang: LanguageIdentifier = locale_lang.parse().unwrap_or_else(|_| { + warn!("Invalid locale detected, defaulting to `{}`", FALLBACK_LANG); + // Debug log: send lang tag to log + warn!("Locale detected: {:?}", locale_lang); + fallback_langid!() + }); + for (id, lang) in LANG_IDENTS.iter().enumerate() { + lang_map.insert(lang.clone(), id); + if *lang == locale_lang { + order.push(id); + } + } + order.push(*lang_map.get(&fallback_langid!()).unwrap()); + Self { + lang_map, + order: order.into(), + } + } +} diff --git a/prpr-l10n/src/lib.rs b/prpr-l10n/src/lib.rs index d52be397..3511e96a 100644 --- a/prpr-l10n/src/lib.rs +++ b/prpr-l10n/src/lib.rs @@ -2,114 +2,44 @@ pub use fluent::{fluent_args, FluentBundle, FluentResource}; pub use once_cell::sync::Lazy; -pub use unic_langid::{langid, LanguageIdentifier}; - -use fluent::{FluentArgs, FluentError}; -use fluent_syntax::ast::Pattern; -use lru::LruCache; -use std::{ - borrow::Cow, - collections::HashMap, - sync::{ - atomic::{AtomicU8, Ordering}, - Mutex, - }, -}; -use sys_locale::get_locale; -use tracing::warn; - -pub static LANGS: [&str; 12] = [ - "en-US", "fr-FR", "id-ID", "ja-JP", "ko-KR", "pl-PL", "pt-BR", "ru-RU", "th-TH", "vi-VN", "zh-CN", "zh-TW", -]; // this should be consistent with the macro below (create_bundles) -pub static LANG_NAMES: [&str; 12] = [ - "English", - "Français", - "Bahasa Indonesia", - "日本語", - "한국어", - "Polski", - "Português", - "Русский", - "แบบไทย", - "Tiếng Việt", - "简体中文", - "繁體中文", -]; // this should be consistent with the macro below (create_bundles) -pub static LANG_IDENTS: Lazy<[LanguageIdentifier; 12]> = Lazy::new(|| LANGS.map(|lang| lang.parse().unwrap())); +pub use unic_langid::LanguageIdentifier; -#[macro_export] -macro_rules! create_bundle { - ($locale:literal, $file:literal) => {{ - let mut bundle = $crate::FluentBundle::new($crate::LANG_IDENTS.iter().cloned().collect()); - bundle - .add_resource( - $crate::FluentResource::try_new( - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/locales/", $locale, "/", $file, ".ftl")).to_owned(), - ) - .unwrap(), - ) - .unwrap(); - bundle.set_use_isolating(false); - bundle - }}; -} +use std::sync::atomic::{AtomicU8, Ordering}; -#[macro_export] -macro_rules! create_bundles { - ($file:literal) => {{ - let mut bundles = Vec::new(); - bundles.push($crate::create_bundle!("en-US", $file)); - bundles.push($crate::create_bundle!("fr-FR", $file)); - bundles.push($crate::create_bundle!("id-ID", $file)); - bundles.push($crate::create_bundle!("ja-JP", $file)); - bundles.push($crate::create_bundle!("ko-KR", $file)); - bundles.push($crate::create_bundle!("pl-PL", $file)); - bundles.push($crate::create_bundle!("pt-BR", $file)); - bundles.push($crate::create_bundle!("ru-RU", $file)); - bundles.push($crate::create_bundle!("th-TH", $file)); - bundles.push($crate::create_bundle!("vi-VN", $file)); - bundles.push($crate::create_bundle!("zh-CN", $file)); - bundles.push($crate::create_bundle!("zh-TW", $file)); - bundles - }}; -} +mod global; +pub use global::*; -pub struct L10nGlobal { - pub lang_map: HashMap, - pub order: Mutex>, -} +mod local; +pub use local::*; -impl Default for L10nGlobal { - fn default() -> Self { - Self::new() - } +mod macros; + +pub mod tools; + +langs! { + "en-US": "English", + "fr-FR": "Français", + "id-ID": "Bahasa Indonesia", + "ja-JP": "日本語", + "ko-KR": "한국어", + "pl-PL": "Polski", + "pt-BR": "Português", + "ru-RU": "Русский", + "th-TH": "แบบไทย", + "vi-VN": "Tiếng Việt", + "zh-CN": "简体中文", + "zh-TW": "繁體中文" } -impl L10nGlobal { - pub fn new() -> Self { - let mut lang_map = HashMap::new(); - let mut order = Vec::new(); - let locale_lang = get_locale().unwrap_or_else(|| String::from("en-US")); - let locale_lang: LanguageIdentifier = locale_lang.parse().unwrap_or_else(|_| { - warn!("Invalid locale detected, defaulting to en-US"); - // Debug log: send lang tag to log - warn!("Locale detected: {:?}", locale_lang); - langid!("en-US") - }); - for (id, lang) in LANG_IDENTS.iter().enumerate() { - lang_map.insert(lang.clone(), id); - if *lang == locale_lang { - order.push(id); - } - } - order.push(*lang_map.get(&langid!("en-US")).unwrap()); - Self { - lang_map, - order: order.into(), - } - } +#[macro_export] +macro_rules! fallback_langid { + () => { + unic_langid::langid!("en-US") + }; } +pub const FALLBACK_LANG: &str = "en-US"; + pub static GLOBAL: Lazy = Lazy::new(L10nGlobal::new); pub fn set_prefered_locale(locale: Option) { @@ -118,13 +48,13 @@ pub fn set_prefered_locale(locale: Option) { if let Some(lang) = locale.and_then(|it| map.get(&it)) { ids.push(*lang); } - if let Some(lang) = get_locale() + if let Some(lang) = sys_locale::get_locale() .and_then(|it| it.parse::().ok()) .and_then(|it| map.get(&it)) { ids.push(*lang); } - ids.push(*map.get(&langid!("en-US")).unwrap()); + ids.push(*map.get(&fallback_langid!()).unwrap()); *GLOBAL.order.lock().unwrap() = ids; GENERATION.fetch_add(1, Ordering::Relaxed); } @@ -147,96 +77,3 @@ unsafe impl Send for L10nBundles {} unsafe impl Sync for L10nBundles {} pub static GENERATION: AtomicU8 = AtomicU8::new(0); - -pub struct L10nLocal { - bundles: &'static L10nBundles, - cache: LruCache, (usize, &'static Pattern<&'static str>)>, - generation: u8, -} - -impl L10nLocal { - pub fn new(bundles: &'static L10nBundles) -> Self { - Self { - bundles, - cache: LruCache::new(16.try_into().unwrap()), - generation: 0, - } - } - - fn format_with_errors<'s>(&mut self, key: Cow<'static, str>, args: Option<&'s FluentArgs<'s>>, errors: &mut Vec) -> Cow<'s, str> { - let gen = GENERATION.load(Ordering::Relaxed); - if gen > self.generation { - self.generation = gen; - self.cache.clear(); - } - if let Some((id, pattern)) = { - let get_result = self.cache.get(&key); - if get_result.is_none() { - let guard = GLOBAL.order.lock().unwrap(); - guard - .iter() - .filter_map(|id| self.bundles.inner[*id].get_message(&key).map(|msg| (*id, msg))) - .next() - .map(|(id, message)| (id, message.value().unwrap())) - .map(|val| self.cache.get_or_insert(key.clone(), || val)) - } else { - get_result - } - } { - unsafe { std::mem::transmute(self.bundles.inner[*id].format_pattern(pattern, args, errors)) } - } else { - warn!("no translation found for {key}, returning key"); - key - } - } - - pub fn format<'s>(&mut self, key: impl Into>, args: Option<&'s FluentArgs<'s>>) -> Cow<'s, str> { - let mut errors = Vec::new(); - let key: Cow<'static, str> = key.into(); - let res = self.format_with_errors(key.clone(), args, &mut errors); - for error in errors { - warn!("l10n error {key}: {error:?}"); - } - res - } -} - -#[macro_export] -macro_rules! tl_file { - ($file:literal) => { - $crate::tl_file!($file tl); - }; - ($file:literal $macro_name:ident $($p:tt)*) => { - static L10N_BUNDLES: $crate::Lazy<$crate::L10nBundles> = $crate::Lazy::new(|| $crate::create_bundles!($file).into()); - - thread_local! { - pub static L10N_LOCAL: std::cell::RefCell<$crate::L10nLocal> = $crate::L10nLocal::new(&*L10N_BUNDLES).into(); - } - - macro_rules! __tl_builder { - ($d:tt) => { - macro_rules! $macro_name { - ($d key:expr) => { - $($p)* L10N_LOCAL.with(|it| it.borrow_mut().format($key, None)) - }; - ($d key:expr, $d args:expr) => { - $($p)* L10N_LOCAL.with(|it| it.borrow_mut().format($key, Some($args))) - }; - ($d key:expr, $d ($d name:expr => $d value:expr),+) => { - $($p)* L10N_LOCAL.with(|it| it.borrow_mut().format($key, Some(&$crate::fluent_args![$d($d name => $d value), *])).to_string()) - }; - (err $d ($d body:tt)*) => { - anyhow::Error::msg($macro_name!($d($d body)*)) - }; - (bail $d ($d body:tt)*) => { - return anyhow::Result::Err(anyhow::Error::msg($macro_name!($d($d body)*))) - }; - } - - pub(crate) use $macro_name; - } - } - - __tl_builder!($); - }; -} diff --git a/prpr-l10n/src/local.rs b/prpr-l10n/src/local.rs new file mode 100644 index 00000000..41a693b2 --- /dev/null +++ b/prpr-l10n/src/local.rs @@ -0,0 +1,60 @@ +use fluent::{FluentArgs, FluentError}; +use fluent_syntax::ast::Pattern; +use lru::LruCache; +use std::{borrow::Cow, sync::atomic::Ordering}; +use tracing::warn; + +use crate::{L10nBundles, GENERATION, GLOBAL}; + +pub struct L10nLocal { + bundles: &'static L10nBundles, + cache: LruCache, (usize, &'static Pattern<&'static str>)>, + generation: u8, +} + +impl L10nLocal { + pub fn new(bundles: &'static L10nBundles) -> Self { + Self { + bundles, + cache: LruCache::new(16.try_into().unwrap()), + generation: 0, + } + } + + fn format_with_errors<'s>(&mut self, key: Cow<'static, str>, args: Option<&'s FluentArgs<'s>>, errors: &mut Vec) -> Cow<'s, str> { + let gen = GENERATION.load(Ordering::Relaxed); + if gen > self.generation { + self.generation = gen; + self.cache.clear(); + } + if let Some((id, pattern)) = { + let get_result = self.cache.get(&key); + if get_result.is_none() { + let guard = GLOBAL.order.lock().unwrap(); + guard + .iter() + .filter_map(|id| self.bundles.inner[*id].get_message(&key).map(|msg| (*id, msg))) + .next() + .map(|(id, message)| (id, message.value().unwrap())) + .map(|val| self.cache.get_or_insert(key.clone(), || val)) + } else { + get_result + } + } { + unsafe { std::mem::transmute(self.bundles.inner[*id].format_pattern(pattern, args, errors)) } + } else { + warn!("no translation found for {key}, returning key"); + key + } + } + + pub fn format<'s>(&mut self, key: impl Into>, args: Option<&'s FluentArgs<'s>>) -> Cow<'s, str> { + let mut errors = Vec::new(); + let key: Cow<'static, str> = key.into(); + let res = self.format_with_errors(key.clone(), args, &mut errors); + for error in errors { + warn!("l10n error {key}: {error:?}"); + } + res + } +} diff --git a/prpr-l10n/src/macros.rs b/prpr-l10n/src/macros.rs new file mode 100644 index 00000000..3f67dc8c --- /dev/null +++ b/prpr-l10n/src/macros.rs @@ -0,0 +1,99 @@ +#[macro_export] +macro_rules! langs { + ($($lang_id:literal: $lang_name:expr),*) => { + macro_rules! __create_bundles_builder { + ($d:tt) => { + #[macro_export] + macro_rules! create_bundles { + ($d file:literal) => {{ + let mut bundles = Vec::new(); + $( + bundles.push($crate::create_bundle!($lang_id, $file)); + )* + bundles + }}; + } + } + } + + macro_rules! __count_builder { + ($d:tt) => { + macro_rules! count { + () => (0usize); + ($d _a:tt $d _b:tt $d _c:tt $d _d:tt $d _e:tt $d ($d tail:tt)*) => ( + 5usize + count!($d ($d tail)*) + ); + ($d _a:tt $d _b:tt $d ($d tail:tt)*) => (2usize + count!($d ($d tail)*)); + ($d _a:tt $d ($d tail:tt)*) => (1usize + count!($d ($d tail)*)); + } + } + } + + __create_bundles_builder!($); + + __count_builder!($); + + pub const LANG_COUNT: usize = count!($($lang_id)*); + + pub static LANGS: [&str; LANG_COUNT] = [$($lang_id,)*]; + pub static LANG_NAMES: [&str; LANG_COUNT] = [$($lang_name,)*]; + pub static LANG_IDENTS: Lazy<[LanguageIdentifier; LANG_COUNT]> = Lazy::new(|| LANGS.map(|lang| lang.parse().unwrap())); + }; +} + +#[macro_export] +macro_rules! create_bundle { + ($locale:literal, $file:literal) => {{ + let mut bundle = $crate::FluentBundle::new($crate::LANG_IDENTS.iter().cloned().collect()); + bundle + .add_resource( + $crate::FluentResource::try_new( + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/locales/", $locale, "/", $file, ".ftl")).to_owned(), + ) + .unwrap(), + ) + .unwrap(); + bundle.set_use_isolating(false); + bundle + }}; +} + +#[macro_export] +macro_rules! tl_file { + ($file:literal) => { + $crate::tl_file!($file tl); + }; + ($file:literal $macro_name:ident $($p:tt)*) => { + static L10N_BUNDLES: $crate::Lazy<$crate::L10nBundles> = $crate::Lazy::new(|| $crate::create_bundles!($file).into()); + + thread_local! { + pub static L10N_LOCAL: std::cell::RefCell<$crate::L10nLocal> = $crate::L10nLocal::new(&*L10N_BUNDLES).into(); + } + + macro_rules! __tl_builder { + ($d:tt) => { + macro_rules! $macro_name { + ($d key:expr) => { + $($p)* L10N_LOCAL.with(|it| it.borrow_mut().format($key, None)) + }; + ($d key:expr, $d args:expr) => { + $($p)* L10N_LOCAL.with(|it| it.borrow_mut().format($key, Some($args))) + }; + ($d key:expr, $d ($d name:expr => $d value:expr),+) => { + $($p)* L10N_LOCAL.with(|it| it.borrow_mut().format($key, Some(&$crate::fluent_args![$d($d name => $d value), *])).to_string()) + }; + (err $d ($d body:tt)*) => { + anyhow::Error::msg($macro_name!($d($d body)*)) + }; + (bail $d ($d body:tt)*) => { + return anyhow::Result::Err(anyhow::Error::msg($macro_name!($d($d body)*))) + }; + } + + pub(crate) use $macro_name; + } + } + + __tl_builder!($); + }; +} diff --git a/prpr-l10n/src/tools.rs b/prpr-l10n/src/tools.rs new file mode 100644 index 00000000..60cc8bfa --- /dev/null +++ b/prpr-l10n/src/tools.rs @@ -0,0 +1,71 @@ +use std::{collections::HashSet, error::Error, fmt::Display, path::Path}; + +use walkdir::WalkDir; + +use crate::LANGS; + +#[derive(Debug)] +struct IllegalLanguages { + pub languages: Vec, +} + +impl Display for IllegalLanguages { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.languages)?; + Ok(()) + } +} + +impl Error for IllegalLanguages {} + +fn get_ftl_files(path: &Path) -> Result, Box> { + let mut files = HashSet::new(); + for entry in WalkDir::new(path) { + let entry = entry?; + if entry.file_type().is_file() { + let path = entry.path(); + if path.extension().map_or(false, |ext| ext == "ftl") { + let relative_path = path.strip_prefix(path.parent().unwrap())?; + let normalized = relative_path.to_string_lossy().replace('\\', "/"); + files.insert(normalized); + } + } + } + Ok(files) +} + +pub fn check_langfile(path: &str) -> Result<(), Box> { + let locales_dir = Path::new(path); + let zh_cn_dir = locales_dir.join("zh-CN"); + let all_locales: [std::path::PathBuf; 12] = LANGS.map(|x| locales_dir.join(x)); + let zh_cn_files = get_ftl_files(&zh_cn_dir)?; + let mut inconsistent_languages = Vec::new(); + let mut i = 0; + while i < LANGS.len() { + let path = all_locales[i].to_owned(); + if path.is_dir() { + let lang_code = LANGS[i]; + i += 1; + if lang_code == "zh-CN" { + continue; + } + + match get_ftl_files(&path) { + Ok(files) => { + if files != zh_cn_files { + inconsistent_languages.push(lang_code); + } + } + Err(_) => inconsistent_languages.push(lang_code), + } + } + } + + if !inconsistent_languages.is_empty() { + return Err(Box::new(IllegalLanguages { + languages: inconsistent_languages.iter().map(|x| x.to_string()).collect(), + })); + } + + Ok(()) +} diff --git a/prpr-l10n/tests/langid.rs b/prpr-l10n/tests/langid.rs new file mode 100644 index 00000000..2ae393e9 --- /dev/null +++ b/prpr-l10n/tests/langid.rs @@ -0,0 +1,7 @@ +use prpr_l10n::Lazy; + +#[test] +fn check_langid() { + // Lang ID is illegal if panicked + Lazy::force(&prpr_l10n::LANG_IDENTS); +} diff --git a/prpr/src/core/chart.rs b/prpr/src/core/chart.rs index 35fe2bf7..bb9032d1 100644 --- a/prpr/src/core/chart.rs +++ b/prpr/src/core/chart.rs @@ -4,7 +4,6 @@ use anyhow::{Context, Result}; use macroquad::prelude::*; use sasa::AudioClip; use std::{cell::RefCell, collections::HashMap}; -use tracing::warn; #[derive(Default)] pub struct ChartExtra { @@ -116,7 +115,7 @@ impl Chart { #[cfg(feature = "video")] for video in &mut self.extra.videos { if let Err(err) = video.update(res.time) { - warn!("video error: {err:?}"); + tracing::warn!("video error: {err:?}"); } } } diff --git a/prpr/src/parse/extra.rs b/prpr/src/parse/extra.rs index bbf9d888..dca15419 100644 --- a/prpr/src/parse/extra.rs +++ b/prpr/src/parse/extra.rs @@ -124,6 +124,7 @@ struct ExtEffect { global: bool, } +#[allow(dead_code)] #[derive(Deserialize)] struct ExtVideo { path: String, diff --git a/prpr/tests/integrated_test.rs b/prpr/tests/integrated_test.rs index 30b74e22..bd45e911 100644 --- a/prpr/tests/integrated_test.rs +++ b/prpr/tests/integrated_test.rs @@ -1,80 +1,9 @@ -use once_cell::sync::Lazy; -use prpr::l10n::{LANGS, LANG_IDENTS}; -use std::collections::HashSet; -use std::error::Error; -use std::fmt::Display; -use std::path::Path; -use walkdir::WalkDir; +use prpr_l10n::tools::check_langfile; -#[derive(Debug)] -struct IllegalLanguages { - pub languages: Vec, -} - -impl Display for IllegalLanguages { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.languages)?; - Ok(()) - } -} - -impl Error for IllegalLanguages {} - -fn get_ftl_files(path: &Path) -> Result, Box> { - let mut files = HashSet::new(); - for entry in WalkDir::new(path) { - let entry = entry?; - if entry.file_type().is_file() { - let path = entry.path(); - if path.extension().map_or(false, |ext| ext == "ftl") { - let relative_path = path.strip_prefix(path.parent().unwrap())?; - let normalized = relative_path.to_string_lossy().replace('\\', "/"); - files.insert(normalized); - } - } - } - Ok(files) -} #[test] -fn check_langid() -> anyhow::Result<()> { - // Lang ID is illegal if panicked - Lazy::force(&LANG_IDENTS); - Ok(()) -} - -#[test] -fn check_langfile() -> Result<(), Box> { - let locales_dir = Path::new("locales"); - let zh_cn_dir = locales_dir.join("zh-CN"); - let all_locales: [std::path::PathBuf; 12] = LANGS.map(|x| locales_dir.join(x)); - let zh_cn_files = get_ftl_files(&zh_cn_dir)?; - let mut inconsistent_languages = Vec::new(); - let mut i = 0; - while i < LANGS.len() { - let path = all_locales[i].to_owned(); - if path.is_dir() { - let lang_code = LANGS[i]; - i += 1; - if lang_code == "zh-CN" { - continue; - } - - match get_ftl_files(&path) { - Ok(files) => { - if files != zh_cn_files { - inconsistent_languages.push(lang_code); - } - } - Err(_) => inconsistent_languages.push(lang_code), - } - } +fn check_all() { + match check_langfile(concat!(env!("CARGO_MANIFEST_DIR"), "/locales/")) { + Ok(_) => {} + Err(e) => panic!("Error: {}", e), } - - if !inconsistent_languages.is_empty() { - return Err(Box::new(IllegalLanguages { - languages: inconsistent_languages.iter().map(|x| x.to_string()).collect(), - })); - } - - Ok(()) }