From ba544174b73664cbe8fbd66818020999d44e7e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=BF=90=E5=AE=B6?= Date: Thu, 26 Sep 2024 16:42:23 +0800 Subject: [PATCH] =?UTF-8?q?i18n=E5=A2=9E=E5=8A=A0=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=EF=BC=8C=E6=A3=80=E6=9F=A5=E7=B3=BB=E7=BB=9F=E7=94=A8=E5=88=B0?= =?UTF-8?q?=E7=9A=84=E5=A4=9A=E8=AF=AD=E8=A8=80=E5=AD=97=E6=AE=B5=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E5=9C=A8csv=E6=96=87=E4=BB=B6=E4=B8=AD=E5=AE=9A?= =?UTF-8?q?=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 30 ++++++++++++++++++++++- Cargo.toml | 5 +++- domain/src/dto/account.rs | 6 ++--- domain/src/dto/feedback.rs | 2 +- domain/src/dto/pageable.rs | 4 +-- i18n.csv | 26 ++++++++++---------- i18n/Cargo.toml | 4 ++- i18n/src/lib.rs | 25 +++++++++++++------ i18n/src/message_ids.rs | 30 +++++++++++++---------- library/src/model/validator.rs | 10 +++++--- server/src/service/account_service.rs | 6 ++--- server/src/service/sys_account_service.rs | 8 +++--- src/main.rs | 1 + 13 files changed, 104 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f68f1ff..00e8dc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,6 +292,7 @@ dependencies = [ name = "chuanyue-service" version = "0.1.0" dependencies = [ + "i18n", "library", "mimalloc", "server", @@ -821,6 +822,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -992,6 +999,8 @@ version = "0.1.0" dependencies = [ "csv", "lazy_static", + "strum", + "strum_macros", "tracing", ] @@ -2250,7 +2259,7 @@ checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.4.1", "hex", "once_cell", "proc-macro2", @@ -2397,6 +2406,25 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.77", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index d02b462..6dbce02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ mimalloc = { workspace = true } server = { path = "server" } library = { path = "library" } +i18n = { path = "i18n" } [workspace.dependencies] tokio = "1.36" @@ -53,4 +54,6 @@ syn = "2.0.77" quote = "1.0.37" hyper = "1.4.1" tower = "0.5.1" -csv = "1.3.0" \ No newline at end of file +csv = "1.3.0" +strum = "0.26.3" +strum_macros = "0.26.3" \ No newline at end of file diff --git a/domain/src/dto/account.rs b/domain/src/dto/account.rs index b3719bd..971d783 100644 --- a/domain/src/dto/account.rs +++ b/domain/src/dto/account.rs @@ -3,15 +3,15 @@ use validator::Validate; #[derive(Debug, Validate, Deserialize, Serialize)] pub struct AuthenticateWithPassword { - #[validate(required(message = "VALIDATE_ACCOUNT_NAME_REQUIRED"), length(min = 1, message = "VALIDATE_ACCOUNT_NAME_REQUIRED"))] + #[validate(required(message = "ValidateAccountNameRequired"), length(min = 1, message = "ValidateAccountNameRequired"))] pub username: Option, - #[validate(required(message = "VALIDATE_ACCOUNT_PASSWORD_REQUIRED"), length(min = 1, message = "VALIDATE_ACCOUNT_PASSWORD_REQUIRED"))] + #[validate(required(message = "ValidateAccountPasswordRequired"), length(min = 1, message = "ValidateAccountPasswordRequired"))] pub password: Option, } #[derive(Debug, Validate, Deserialize, Serialize)] pub struct AuthenticateGooleAccountReq { - #[validate(required(message = "VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED"), length(min = 1, message = "VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED"))] + #[validate(required(message = "ValidateAccountIdTokenRequired"), length(min = 1, message = "ValidateAccountIdTokenRequired"))] pub id_token: Option, } diff --git a/domain/src/dto/feedback.rs b/domain/src/dto/feedback.rs index 3d71d2a..15ebeeb 100644 --- a/domain/src/dto/feedback.rs +++ b/domain/src/dto/feedback.rs @@ -3,6 +3,6 @@ use validator::Validate; #[derive(Debug, Validate, Deserialize, Serialize)] pub struct FeedbackAdd { - #[validate(required(message = "VALIDATE_FEEDBACK_CONTENT_REQUIRED"), length(min = 1, message = "VALIDATE_FEEDBACK_CONTENT_REQUIRED"))] + #[validate(required(message = "ValidateFeedbackContentRequired"), length(min = 1, message = "ValidateFeedbackContentRequired"))] pub content: Option } \ No newline at end of file diff --git a/domain/src/dto/pageable.rs b/domain/src/dto/pageable.rs index 4938ae0..0a686cf 100644 --- a/domain/src/dto/pageable.rs +++ b/domain/src/dto/pageable.rs @@ -3,9 +3,9 @@ use validator::Validate; #[derive(Deserialize, Validate)] pub struct PageParams { - #[validate(required(message = "VALIDATE_PAGEABLE_PAGE_REQUIRED"), range(min = 1, message = "VALIDATE_PAGEABLE_PAGE_REQUIRED"))] + #[validate(required(message = "ValidatePageablePageRequired"), range(min = 1, message = "ValidatePageablePageRequired"))] pub page: Option, #[serde(rename = "pageSize")] - #[validate(required(message = "VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED"), range(min = 1, message = "VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED"))] + #[validate(required(message = "ValidatePageablePageSizeRequired"), range(min = 1, message = "ValidatePageablePageSizeRequired"))] pub page_size: Option, } \ No newline at end of file diff --git a/i18n.csv b/i18n.csv index b5d59f0..32e3b75 100644 --- a/i18n.csv +++ b/i18n.csv @@ -1,14 +1,14 @@ id,en-US,zh-CN -SERVER_INTERNAL_ERROR,Internal server error,系统内部错误 -HELLO,hello {},你好 {} -ACCOUNT_DISABLED,account is disabled,账户已禁用 -ACCOUNT_NO_PERMISSION,account has no permission,账户无权限 -INCORRECT_USERNAME_OR_PASSWORD,incorrect username or password,用户名或密码错误 -INVALID_TOKEN,invalid token,无效令牌 -VALIDATE_FEEDBACK_CONTENT_REQUIRED,feedback content is required,反馈内容不能为空 -VALIDATE_ACCOUNT_NAME_REQUIRED,username is required,"用户名称不能为空" -VALIDATE_ACCOUNT_PASSWORD_REQUIRED,password is required,密码不能为空 -VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED,ID Token is required,用户ID Token不能为空 -VALIDATE_ACCOUNT_LANG_TAG_REQUIRED,lang tag is required,用户语言标识不能为空 -VALIDATE_PAGEABLE_PAGE_REQUIRED,invalid page number,页码无效 -VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED,invalid quantity per page,每页数量无效 \ No newline at end of file +ServerInternalError,Internal server error,系统内部错误 +Hello,hello {},你好 {} +AccountDisabled,account is disabled,账户已禁用 +AccountNoPermission,account has no permission,账户无权限 +IncorrectUsernameOrPassword,incorrect username or password,用户名或密码错误 +InvalidToken,invalid token,无效令牌 +ValidateFeedbackContentRequired,feedback content is required,反馈内容不能为空 +ValidateAccountNameRequired,username is required,"用户名称不能为空" +ValidateAccountPasswordRequired,password is required,密码不能为空 +ValidateAccountIdTokenRequired,ID Token is required,用户ID Token不能为空 +ValidateAccountLangTagRequired,lang tag is required,用户语言标识不能为空 +ValidatePageablePageRequired,invalid page number,页码无效 +ValidatePageablePageSizeRequired,invalid quantity per page,每页数量无效 \ No newline at end of file diff --git a/i18n/Cargo.toml b/i18n/Cargo.toml index 292d47c..8839a56 100644 --- a/i18n/Cargo.toml +++ b/i18n/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" [dependencies] lazy_static = { workspace = true } tracing = { workspace = true } -csv = { workspace = true } \ No newline at end of file +csv = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } \ No newline at end of file diff --git a/i18n/src/lib.rs b/i18n/src/lib.rs index 7300595..e727968 100644 --- a/i18n/src/lib.rs +++ b/i18n/src/lib.rs @@ -2,12 +2,15 @@ extern crate self as i18n; use std::{collections::HashMap, sync::OnceLock}; +use message_ids::MessageId; +use strum::IntoEnumIterator; + pub mod message_ids; const I18N_FILE: &str = "i18n.csv"; static I18N: OnceLock>> = OnceLock::new(); -fn init_i18n() -> HashMap<&'static str, HashMap<&'static str, &'static str>> { +fn init() -> HashMap<&'static str, HashMap<&'static str, &'static str>> { let mut i18n_map = HashMap::new(); let mut i18n_id_map = HashMap::new(); let mut rdr = csv::Reader::from_path(I18N_FILE).expect("读取多语言文件失败"); @@ -31,19 +34,27 @@ fn init_i18n() -> HashMap<&'static str, HashMap<&'static str, &'static str>> { .insert(message_id, message); } } - tracing::info!("多语言文件解析完成:{:?}", i18n_map); + + // 检查多语言文件是否完整 + for (lang_id, lang_map) in i18n_map.iter() { + MessageId::iter().for_each(|message_id| { + if !lang_map.contains_key(message_id.as_ref()) { + tracing::error!("多语言文件缺失消息:{},语言:{}", message_id, lang_id); + } + }); + } i18n_map } -fn get_i18n() -> &'static HashMap<&'static str, HashMap<&'static str, &'static str>> { - I18N.get_or_init(init_i18n) +pub fn init_i18n() -> &'static HashMap<&'static str, HashMap<&'static str, &'static str>> { + I18N.get_or_init(init) } /// 获取语言模板 -pub fn lang(lang_id: &str, message_id: &str) -> &'static str { - get_i18n() +pub fn lang(lang_id: &str, message_id: MessageId) -> &'static str { + init_i18n() .get(lang_id) - .and_then(|map| map.get(message_id)) + .and_then(|map| map.get(message_id.as_ref())) .unwrap_or_else(|| &"UNKNOWN") } diff --git a/i18n/src/message_ids.rs b/i18n/src/message_ids.rs index 61a8a5e..e2d3c93 100644 --- a/i18n/src/message_ids.rs +++ b/i18n/src/message_ids.rs @@ -1,15 +1,19 @@ -pub const SERVER_INTERNAL_ERROR: &str = "SERVER_INTERNAL_ERROR"; +use strum_macros::{AsRefStr, Display, EnumIter, EnumString}; -pub const HELLO: &str = "HELLO"; -pub const ACCOUNT_DISABLED: &str = "ACCOUNT_DISABLED"; -pub const ACCOUNT_NO_PERMISSION: &str = "ACCOUNT_NO_PERMISSION"; -pub const INCORRECT_USERNAME_OR_PASSWORD: &str = "INCORRECT_USERNAME_OR_PASSWORD"; -pub const INVALID_TOKEN: &str = "INVALID_TOKEN"; +#[derive(Debug, EnumIter, EnumString, Display, PartialEq, AsRefStr)] +pub enum MessageId { + ServerInternalError, + Hello, + AccountDisabled, + AccountNoPermission, + IncorrectUsernameOrPassword, + InvalidToken, -pub const VALIDATE_FEEDBACK_CONTENT_REQUIRED: &'static str = "VALIDATE_FEEDBACK_CONTENT_REQUIRED"; -pub const VALIDATE_ACCOUNT_NAME_REQUIRED: &'static str = "VALIDATE_ACCOUNT_NAME_REQUIRED"; -pub const VALIDATE_ACCOUNT_PASSWORD_REQUIRED: &'static str = "VALIDATE_ACCOUNT_PASSWORD_REQUIRED"; -pub const VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED: &'static str = "VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED"; -pub const VALIDATE_ACCOUNT_LANG_TAG_REQUIRED: &'static str = "VALIDATE_ACCOUNT_LANG_TAG_REQUIRED"; -pub const VALIDATE_PAGEABLE_PAGE_REQUIRED: &'static str = "VALIDATE_PAGEABLE_PAGE_REQUIRED"; -pub const VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED: &'static str = "VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED"; + ValidateFeedbackContentRequired, + ValidateAccountNameRequired, + ValidateAccountPasswordRequired, + ValidateAccountIdTokenRequired, + ValidateAccountLangTagRequired, + ValidatePageablePageRequired, + ValidatePageablePageSizeRequired, +} diff --git a/library/src/model/validator.rs b/library/src/model/validator.rs index cfccb7f..42e2536 100644 --- a/library/src/model/validator.rs +++ b/library/src/model/validator.rs @@ -1,4 +1,6 @@ -use i18n::message; +use std::str::FromStr; + +use i18n::{message, message_ids::MessageId}; use validator::Validate; use super::response::{ResData, ResErr, ResResult}; @@ -16,7 +18,7 @@ pub fn validate_params(params: &impl Validate, local: &str) -> ResResult ResResult ResResult { err.iter().for_each(|e| { let msg = e.message.clone().unwrap_or_default(); - errors.push(message!(local, msg.trim())) + errors.push(message!(local, MessageId::from_str(msg.trim()).unwrap())) }); }, }; diff --git a/server/src/service/account_service.rs b/server/src/service/account_service.rs index 2d8c6c6..4f9d4ef 100644 --- a/server/src/service/account_service.rs +++ b/server/src/service/account_service.rs @@ -5,7 +5,7 @@ use domain::dto::account::AuthenticateGooleAccountReq; use domain::entities::account::Account; use domain::vo::account::{LoginAccount, RefreshTokenResult}; use i18n::message; -use i18n::message_ids::{ACCOUNT_DISABLED, INVALID_TOKEN}; +use i18n::message_ids::MessageId; use library::cache::account_cache::{CacheAccount, LOGIN_CACHE}; use library::context::{Context, WhiteContext}; use library::model::response::ResErr::ErrPerm; @@ -48,7 +48,7 @@ pub async fn authenticate_google( if let Some(disable_time) = account.disable_time { if disable_time > Utc::now() { tracing::error!("账户已禁用"); - return Err(ResErr::service(message!(context.get_lang_tag(), ACCOUNT_DISABLED))); + return Err(ResErr::service(message!(context.get_lang_tag(), MessageId::AccountDisabled))); } } @@ -89,7 +89,7 @@ pub async fn refresh_token( let account = context.account.clone(); if token::verify_refresh_token(&refresh_token).is_err() { - return Err(ResErr::params(message!(context.get_lang_tag(), INVALID_TOKEN))); + return Err(ResErr::params(message!(context.get_lang_tag(), MessageId::InvalidToken))); } let refresh_token = RefreshTokenResult { diff --git a/server/src/service/sys_account_service.rs b/server/src/service/sys_account_service.rs index 7240f3e..4dd08bf 100644 --- a/server/src/service/sys_account_service.rs +++ b/server/src/service/sys_account_service.rs @@ -8,7 +8,7 @@ use domain::{ }; use i18n::{ message, - message_ids::{ACCOUNT_DISABLED, ACCOUNT_NO_PERMISSION, INCORRECT_USERNAME_OR_PASSWORD}, + message_ids::MessageId, }; use library::{ cache::account_cache::{CacheAccount, LOGIN_CACHE}, context::WhiteContext, db, model::response::{ResErr, ResResult}, token::{generate_refresh_token, generate_token} @@ -25,7 +25,7 @@ pub async fn authenticate_with_password( tracing::info!("登录用户失败,用户查询为空"); return Err(ResErr::params(message!( context.get_lang_tag(), - INCORRECT_USERNAME_OR_PASSWORD + MessageId::IncorrectUsernameOrPassword ))); } let account = account.unwrap(); @@ -34,7 +34,7 @@ pub async fn authenticate_with_password( tracing::error!("账户已禁用"); return Err(ResErr::auth(message!( context.get_lang_tag(), - ACCOUNT_DISABLED + MessageId::AccountDisabled ))); } } @@ -42,7 +42,7 @@ pub async fn authenticate_with_password( tracing::error!("账户不是管理员,无权限"); return Err(ResErr::perm(message!( context.get_lang_tag(), - ACCOUNT_NO_PERMISSION + MessageId::AccountNoPermission ))); } diff --git a/src/main.rs b/src/main.rs index 882748c..d4b8fdf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ static GLOBAL: MiMalloc = MiMalloc; #[tokio::main] async fn main() { let (_std_guard, _file_guard) = library::core::logger::init_log(config!()); + let _i18n = i18n::init_i18n(); library::core::db::init_database(config!()).await; server::serve().await;