refactor(i18n): extract op-i18n crate (with Locale) from openpencil-shell-core

This commit is contained in:
Kayshen-X 2026-05-16 14:13:38 +08:00
parent 1ef513d5a6
commit 08686c3b95
23 changed files with 102 additions and 72 deletions

5
Cargo.lock generated
View file

@ -2274,6 +2274,10 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "op-i18n"
version = "0.1.0"
[[package]]
name = "openpencil-desktop"
version = "0.1.0"
@ -2307,6 +2311,7 @@ dependencies = [
"bitflags 2.11.1",
"glam",
"jian-core",
"op-i18n",
"serde",
"thiserror 1.0.69",
"tracing",

View file

@ -0,0 +1,9 @@
[package]
name = "op-i18n"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
description = "OpenPencil i18n — Locale enum + 15-locale string tables"
[dependencies]

View file

@ -11,7 +11,7 @@
//! (`common.untitled`, `rightPanel.design`, `layout.flexLayout`,
//! …) so cross-walking strings between TS and Rust is mechanical.
use crate::document::Locale;
use crate::Locale;
mod de;
mod en;

View file

@ -0,0 +1,5 @@
//! OpenPencil i18n — Locale + 15-locale string tables.
mod i18n;
mod locale;
pub use i18n::*;
pub use locale::Locale;

View file

@ -0,0 +1,70 @@
/// UI locale — full set of 15 mirrored from
/// `apps/web/src/i18n/locales/`. ZhCn + EnUs ship with complete
/// chrome translation tables; the rest fall through to EnUs
/// (visually obvious that translation is pending).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Locale {
EnUs,
ZhCn,
ZhTw,
Ja,
Ko,
Fr,
Es,
De,
Pt,
Ru,
Hi,
Tr,
Th,
Vi,
Id,
}
impl Locale {
/// All locales in TopBar dropdown order — matches TS app.
pub const ALL: [Locale; 15] = [
Locale::EnUs,
Locale::ZhCn,
Locale::ZhTw,
Locale::Ja,
Locale::Ko,
Locale::Fr,
Locale::Es,
Locale::De,
Locale::Pt,
Locale::Ru,
Locale::Hi,
Locale::Tr,
Locale::Th,
Locale::Vi,
Locale::Id,
];
/// Cycle to the next locale (round-trips through `ALL`).
pub fn next(self) -> Self {
let i = Self::ALL.iter().position(|&l| l == self).unwrap_or(0);
Self::ALL[(i + 1) % Self::ALL.len()]
}
/// Native-script display name (matches the TS dropdown).
pub fn display_name(self) -> &'static str {
match self {
Locale::EnUs => "English",
Locale::ZhCn => "简体中文",
Locale::ZhTw => "繁體中文",
Locale::Ja => "日本語",
Locale::Ko => "한국어",
Locale::Fr => "Français",
Locale::Es => "Español",
Locale::De => "Deutsch",
Locale::Pt => "Português",
Locale::Ru => "Русский",
Locale::Hi => "हिन्दी",
Locale::Tr => "Türkçe",
Locale::Th => "ไทย",
Locale::Vi => "Tiếng Việt",
Locale::Id => "Bahasa Indonesia",
}
}
}

View file

@ -31,6 +31,11 @@ accesskit = "0.24"
# cargo silently picking a wrong version under a different workspace context.
jian-core = { path = "../../vendor/jian/crates/jian-core", version = "0.0.1" }
# Phase 3 strangler reorg: i18n (Locale + 15-locale string tables) extracted
# into op-i18n; shell-core re-exports it as `i18n` so downstream callers keep
# resolving `crate::i18n::*` / `crate::document::Locale` unchanged.
op-i18n = { path = "../op-i18n" }
[features]
default = []
serde = ["dep:serde"]

View file

@ -677,76 +677,10 @@ impl ThemeMode {
}
}
/// UI locale — full set of 15 mirrored from
/// `apps/web/src/i18n/locales/`. ZhCn + EnUs ship with complete
/// chrome translation tables; the rest fall through to EnUs
/// (visually obvious that translation is pending).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Locale {
EnUs,
ZhCn,
ZhTw,
Ja,
Ko,
Fr,
Es,
De,
Pt,
Ru,
Hi,
Tr,
Th,
Vi,
Id,
}
impl Locale {
/// All locales in TopBar dropdown order — matches TS app.
pub const ALL: [Locale; 15] = [
Locale::EnUs,
Locale::ZhCn,
Locale::ZhTw,
Locale::Ja,
Locale::Ko,
Locale::Fr,
Locale::Es,
Locale::De,
Locale::Pt,
Locale::Ru,
Locale::Hi,
Locale::Tr,
Locale::Th,
Locale::Vi,
Locale::Id,
];
/// Cycle to the next locale (round-trips through `ALL`).
pub fn next(self) -> Self {
let i = Self::ALL.iter().position(|&l| l == self).unwrap_or(0);
Self::ALL[(i + 1) % Self::ALL.len()]
}
/// Native-script display name (matches the TS dropdown).
pub fn display_name(self) -> &'static str {
match self {
Locale::EnUs => "English",
Locale::ZhCn => "简体中文",
Locale::ZhTw => "繁體中文",
Locale::Ja => "日本語",
Locale::Ko => "한국어",
Locale::Fr => "Français",
Locale::Es => "Español",
Locale::De => "Deutsch",
Locale::Pt => "Português",
Locale::Ru => "Русский",
Locale::Hi => "हिन्दी",
Locale::Tr => "Türkçe",
Locale::Th => "ไทย",
Locale::Vi => "Tiếng Việt",
Locale::Id => "Bahasa Indonesia",
}
}
}
// Phase 3 strangler reorg: the `Locale` enum + its impls moved into the
// `op-i18n` crate (alongside the 15-locale string tables). Re-exported here
// so existing `document::Locale` / `crate::document::Locale` paths resolve.
pub use op_i18n::Locale;
/// Identifier for a property-panel input row.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -22,7 +22,9 @@ pub mod chat_provider;
pub mod codegen;
pub mod document;
pub mod figma;
pub mod i18n;
// Phase 3 strangler reorg: i18n extracted into the op-i18n crate. Re-exported
// as `i18n` so `crate::i18n::translate` / `crate::i18n::Locale` paths still resolve.
pub use op_i18n as i18n;
pub mod jian;
pub mod mcp;
#[cfg(test)] mod mcp_tests;