mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
feat(ui): add ja-JP locale and split i18n into per-language modules (#372)
The single en/zh tuple table becomes one module per language behind a `SUPPORTED_LANGUAGES` table, and Japanese joins English and Simplified Chinese. - `gui_language` accepts `ja-JP`; anything unrecognized still falls back to `en`. - The language picker and `refresh_locale_state` both read `SUPPORTED_LANGUAGES` instead of keeping their own copy of the code list. - Language names in the picker stay endonyms (English / 简体中文 / 日本語) in every locale, as English and Chinese already were. - The zh and ja key tables are exhaustive, so a new `L10nKey` fails the build until it is translated rather than silently rendering English. Co-authored-by: Chihiro WATANABE <chihiro.watanabe@live.jp>
This commit is contained in:
@@ -4,6 +4,8 @@ use std::sync::{Arc, OnceLock};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const SUPPORTED_GUI_LANGUAGES: &[&str] = &["en", "zh-CN", "ja-JP"];
|
||||
|
||||
#[derive(Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct FontFeatures(pub Arc<Vec<(String, u32)>>);
|
||||
|
||||
@@ -519,9 +521,8 @@ impl Config {
|
||||
.take()
|
||||
.map(|proxy| proxy.trim().to_string())
|
||||
.filter(|proxy| !proxy.is_empty());
|
||||
match self.gui_language.as_str() {
|
||||
"en" | "zh-CN" => {}
|
||||
_ => self.gui_language = default_gui_language(),
|
||||
if !SUPPORTED_GUI_LANGUAGES.contains(&self.gui_language.as_str()) {
|
||||
self.gui_language = default_gui_language();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1138,6 +1139,9 @@ mod tests {
|
||||
let cfg: Config = serde_json::from_str(r#"{"gui_language": "zh-CN"}"#).unwrap();
|
||||
assert_eq!(cfg.gui_language, "zh-CN");
|
||||
|
||||
let cfg: Config = serde_json::from_str(r#"{"gui_language": "ja-JP"}"#).unwrap();
|
||||
assert_eq!(cfg.gui_language, "ja-JP");
|
||||
|
||||
let mut cfg: Config = serde_json::from_str(r#"{"gui_language": "ko"}"#).unwrap();
|
||||
cfg.sanitize();
|
||||
assert_eq!(cfg.gui_language, "en");
|
||||
|
||||
+4
-3
@@ -143,13 +143,14 @@ whatever you ran in the pane, and you can revoke it under Privacy & Security.
|
||||
|
||||
## Localization
|
||||
|
||||
The GUI ships English and Simplified Chinese strings. Pick one in Settings →
|
||||
Appearance → Language, or in `config.json`:
|
||||
The GUI ships English, Simplified Chinese and Japanese strings. Pick one in
|
||||
Settings → Appearance → Language, or in `config.json`:
|
||||
|
||||
```json
|
||||
{ "gui_language": "zh-CN" }
|
||||
```
|
||||
|
||||
`en` and `zh-CN` are the only accepted values; anything else falls back to `en`.
|
||||
`en`, `zh-CN` and `ja-JP` are the only accepted values; anything else falls back
|
||||
to `en`.
|
||||
The choice is explicit — the system language is never inferred. CLI output stays
|
||||
English so agent and script integrations keep a stable, predictable surface.
|
||||
|
||||
@@ -137,12 +137,12 @@ Apple Events、系统管理),这样程序才能正常弹出一次性授权
|
||||
|
||||
## 本地化
|
||||
|
||||
GUI 目前提供英文和简体中文两套文案。在「设置 → 外观 → 语言」中选择,或直接改
|
||||
GUI 目前提供英文、简体中文和日文三套文案。在「设置 → 外观 → 语言」中选择,或直接改
|
||||
`config.json`:
|
||||
|
||||
```json
|
||||
{ "gui_language": "zh-CN" }
|
||||
```
|
||||
|
||||
只接受 `en` 和 `zh-CN` 两个值,其它值一律回落到 `en`。语言必须显式指定,不会
|
||||
只接受 `en`、`zh-CN` 和 `ja-JP` 三个值,其它值一律回落到 `en`。语言必须显式指定,不会
|
||||
去猜系统语言。CLI 输出保持英文,保证 agent、脚本和开发者工作流的输出稳定可预测。
|
||||
|
||||
+22
-17
@@ -3702,17 +3702,19 @@ impl Tty7App {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<SelectState<SearchableVec<String>>> {
|
||||
const CODES: &[&str] = &["en", "zh-CN"];
|
||||
let labels = || {
|
||||
vec![
|
||||
t(L10nKey::SettingsLanguageEnglish).to_string(),
|
||||
t(L10nKey::SettingsLanguageChinese).to_string(),
|
||||
]
|
||||
crate::ui::i18n::SUPPORTED_LANGUAGES
|
||||
.iter()
|
||||
.map(|lang| t(lang.label_key).to_string())
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let cfg = cx.global::<Config>();
|
||||
let current = Self::normalize_gui_language(&cfg.gui_language);
|
||||
let rows = labels();
|
||||
let selected = CODES.iter().position(|c| *c == current).unwrap_or(0);
|
||||
let selected = crate::ui::i18n::SUPPORTED_LANGUAGES
|
||||
.iter()
|
||||
.position(|lang| lang.code == current)
|
||||
.unwrap_or(0);
|
||||
let language_select = cx.new(|cx| {
|
||||
SelectState::new(
|
||||
SearchableVec::new(rows),
|
||||
@@ -3728,7 +3730,9 @@ impl Tty7App {
|
||||
if let SelectEvent::Confirm(Some(label)) = ev {
|
||||
let rows = labels();
|
||||
if let Some(idx) = rows.iter().position(|r| r == label) {
|
||||
this.set_gui_language(CODES[idx], window, cx);
|
||||
if let Some(lang) = crate::ui::i18n::SUPPORTED_LANGUAGES.get(idx) {
|
||||
this.set_gui_language(lang.code, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3737,10 +3741,9 @@ impl Tty7App {
|
||||
}
|
||||
|
||||
fn normalize_gui_language(code: &str) -> &'static str {
|
||||
match code {
|
||||
"zh-CN" => "zh-CN",
|
||||
_ => "en",
|
||||
}
|
||||
crate::ui::i18n::find_language(code)
|
||||
.map(|lang| lang.code)
|
||||
.unwrap_or_else(crate::ui::i18n::default_language_code)
|
||||
}
|
||||
|
||||
pub(crate) fn set_gui_language(
|
||||
@@ -3762,7 +3765,6 @@ impl Tty7App {
|
||||
}
|
||||
|
||||
pub(crate) fn refresh_locale_state(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
const CODES: &[&str] = &["en", "zh-CN"];
|
||||
self.sidebar_search.update(cx, |state, cx| {
|
||||
state.set_placeholder(t(L10nKey::SearchTabs), window, cx)
|
||||
});
|
||||
@@ -3770,14 +3772,17 @@ impl Tty7App {
|
||||
state.set_placeholder(t(L10nKey::SearchFiles), window, cx)
|
||||
});
|
||||
if let Some(s) = self.active_settings() {
|
||||
let rows = vec![
|
||||
t(L10nKey::SettingsLanguageEnglish).to_string(),
|
||||
t(L10nKey::SettingsLanguageChinese).to_string(),
|
||||
];
|
||||
let rows = crate::ui::i18n::SUPPORTED_LANGUAGES
|
||||
.iter()
|
||||
.map(|lang| t(lang.label_key).to_string())
|
||||
.collect::<Vec<_>>();
|
||||
s.language_select.update(cx, |state, cx| {
|
||||
state.set_items(SearchableVec::new(rows), window, cx);
|
||||
let code = Self::normalize_gui_language(&cx.global::<Config>().gui_language);
|
||||
let selected = CODES.iter().position(|c| *c == code).unwrap_or(0);
|
||||
let selected = crate::ui::i18n::SUPPORTED_LANGUAGES
|
||||
.iter()
|
||||
.position(|lang| lang.code == code)
|
||||
.unwrap_or(0);
|
||||
state.set_selected_index(Some(IndexPath::default().row(selected)), window, cx);
|
||||
});
|
||||
s.search.update(cx, |state, cx| {
|
||||
|
||||
-3909
File diff suppressed because it is too large
Load Diff
+1323
File diff suppressed because it is too large
Load Diff
+1352
File diff suppressed because it is too large
Load Diff
+2025
File diff suppressed because it is too large
Load Diff
+1241
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user