diff --git a/locales/es.json b/locales/es.json new file mode 100644 index 0000000..a16c8c1 --- /dev/null +++ b/locales/es.json @@ -0,0 +1,97 @@ +{ +"nav.dashboard": "Panel", +"nav.assets": "Activos", +"nav.lab": "Lab Dev", +"nav.contracts": "Contratos", +"nav.faucet": "Grifo", +"nav.wallet": "Cartera", +"nav.coinblast": "CoinBlast", +"nav.search": "buscar", +"nav.switch_to_mainnet": "Cambiar a Mainnet", +"nav.switch_to_testnet": "Cambiar a Testnet", + +"banner.testnet": "Sentrix Testnet — los activos no tienen valor real", + +"stats.tip_height": "Altura del bloque", +"stats.tps": "TPS · ventana 10b", +"stats.finality": "Finalidad media", +"stats.total_supply": "Suministro total", +"stats.circulating_locked": "Circulante · Bloqueado", +"stats.tokenomics_pending": "TBD · pendiente de integración tokenómica", + +"feed.latest_blocks": "ÚLTIMOS BLOQUES", +"feed.awaiting": "Esperando el primer bloque finalizado…", + +"mempool.pending": "TRANSACCIONES PENDIENTES", +"mempool.empty": "No hay transacciones pendientes en vista.", + +"rwa.heading": "ACTIVOS TOKENIZADOS", +"rwa.subheading": "Registro RWA de Sentrix · procedencia en cadena", +"rwa.col_asset": "Activo", +"rwa.col_valuation": "Valoración", +"rwa.col_location": "Ubicación", +"rwa.col_status": "Estado", +"rwa.status_verified": "Verificado", +"rwa.status_pending": "Pendiente", +"rwa.status_disputed": "Disputado", +"rwa.empty_title": "Registro esperando primer incorporación", +"rwa.empty_body": "Los activos del mundo real aparecerán aquí una vez que los registros de socios publiquen sus pruebas en cadena. Cada listado lleva su propio ancla de verificación.", + +"detail.block": "Bloque", +"detail.transaction": "Transacción", +"detail.cli_label": "CLI de Sentrix", +"detail.body_pending": "El cuerpo completo obtenido desde los endpoints gRPC GetBlock / GetTx llegará en la próxima iteración.", +"detail.hash": "Hash", +"detail.parent": "Padre", +"detail.state_root": "Raíz de estado", +"detail.proposer": "Proponente", +"detail.transactions": "Transacciones", +"detail.timestamp": "Marca de tiempo", +"detail.no_txs": "No hay transacciones en este bloque.", +"detail.action": "Acción", +"detail.from": "De", +"detail.to": "Para", +"detail.amount": "Cantidad", +"detail.fee": "Tarifa", +"detail.nonce": "Nonce", +"detail.payload_size": "Tamaño del payload", +"detail.evm_balance": "Saldo EVM", +"detail.native_balance": "Saldo nativo · sentri", +"detail.account_type": "Tipo de cuenta", +"detail.tx_outside_window": "Esta tx no está en la ventana de observación en vivo (últimas ~500 transacciones). El proto de la cadena aún no expone `GetTransaction` para búsqueda arbitraria, por lo que las txs antiguas requieren una consulta al indexador — disponible con la cadena v0.3.", + +"contracts.heading": "CONTRATOS DESPLEGADOS", +"contracts.subheading": "Historial local · solo este dispositivo", +"contracts.empty_title": "Aún no hay despliegues locales", +"contracts.empty_body": "Despliega un contrato desde /lab — los envíos exitosos se registran aquí para acceso rápido. El historial es por dispositivo (localStorage).", + +"not_found.title": "404", +"not_found.body": "Esta ruta no lleva a ningún lugar en el explorador.", +"not_found.back": "Volver al panel", + +"copy.copy": "Copiar CLI", +"copy.copied": "Copiado ✓", + +"palette.placeholder": "Buscar bloques · txs · direcciones · activos…", +"palette.detected": "detectado", +"palette.label_height": "Altura del bloque", +"palette.label_hash": "Hash (32 bytes)", +"palette.label_address": "Dirección de cartera (20 bytes)", +"palette.label_unknown": "—", + +"lab.title": "LAB DE DESARROLLADOR", +"lab.subtitle": "Editor Solidity · compilar · desplegar", +"lab.editor_pending": "Enlace del editor pendiente — la integración de Monaco/CodeMirror es el próximo envío.", +"lab.compile": "Compilar", +"lab.deploy": "Desplegar en Sentrix", +"lab.compiler_pending": "Puente del compilador pendiente — integración del worker solc.js en progreso.", + +"wallet.connect": "Conectar Cartera", +"wallet.connected": "Conectado", +"wallet.disconnect": "Desconectar", + +"error.node_offline": "Nodo Reconectando…", +"error.node_body": "El nodo de Sentrix está tardando más de lo esperado en responder. Los streams se reanudarán automáticamente.", + +"lang.label": "Idioma" +} diff --git a/src/components/lang_switcher.rs b/src/components/lang_switcher.rs index 90c7824..e5751c7 100644 --- a/src/components/lang_switcher.rs +++ b/src/components/lang_switcher.rs @@ -1,4 +1,4 @@ -//! 3-way language toggle. Click cycles En → Id → Zh → En; selection +//! 4-way language toggle. Click cycles En → Es → Id → Zh → En; selection //! persists to localStorage via `i18n::persist_lang`. use leptos::prelude::*; @@ -12,7 +12,8 @@ pub fn LanguageSwitcher() -> impl IntoView { let cycle = move |_| { lang.update(|l| { *l = match *l { - Lang::En => Lang::Id, + Lang::En => Lang::Es, + Lang::Es => Lang::Id, Lang::Id => Lang::Zh, Lang::Zh => Lang::En, }; diff --git a/src/i18n/mod.rs b/src/i18n/mod.rs index fe5237e..c0c5b91 100644 --- a/src/i18n/mod.rs +++ b/src/i18n/mod.rs @@ -1,13 +1,13 @@ -//! i18n — three-locale string lookup. +//! i18n — four-locale string lookup. //! //! ## Layout //! -//! - `locales/{en,id,zh}.json` — translation tables, hand-edited. +//! - `locales/{en,es,id,zh}.json` — translation tables, hand-edited. //! - `Lang` enum — exhaustive list of supported locales. //! - `t(lang, key)` — lookup with key-as-fallback for missing keys. //! - `provide_lang_signal()` — reads `localStorage["sentrix-lang"]`, //! provides an `RwSignal` via context. -//! - `LanguageSwitcher` (in `components::lang_switcher`) — three-way +//! - `LanguageSwitcher` (in `components::lang_switcher`) — four-way //! button that cycles + persists. //! //! ## Why JSON parsed at runtime @@ -29,16 +29,18 @@ use leptos::prelude::*; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Lang { En, + Es, Id, Zh, } impl Lang { - pub const ALL: [Lang; 3] = [Lang::En, Lang::Id, Lang::Zh]; + pub const ALL: [Lang; 4] = [Lang::En, Lang::Es, Lang::Id, Lang::Zh]; pub fn code(self) -> &'static str { match self { Self::En => "en", + Self::Es => "es", Self::Id => "id", Self::Zh => "zh", } @@ -47,6 +49,7 @@ impl Lang { pub fn label(self) -> &'static str { match self { Self::En => "EN", + Self::Es => "ES", Self::Id => "ID", Self::Zh => "中", } @@ -55,6 +58,7 @@ impl Lang { pub fn full_label(self) -> &'static str { match self { Self::En => "English", + Self::Es => "Español", Self::Id => "Bahasa Indonesia", Self::Zh => "中文", } @@ -62,6 +66,7 @@ impl Lang { pub fn from_code(s: &str) -> Self { match s { + "es" => Self::Es, "id" => Self::Id, "zh" => Self::Zh, _ => Self::En, @@ -71,6 +76,7 @@ impl Lang { fn raw(self) -> &'static str { match self { Self::En => include_str!("../../locales/en.json"), + Self::Es => include_str!("../../locales/es.json"), Self::Id => include_str!("../../locales/id.json"), Self::Zh => include_str!("../../locales/zh.json"), } @@ -78,12 +84,14 @@ impl Lang { } static EN_MAP: OnceLock> = OnceLock::new(); +static ES_MAP: OnceLock> = OnceLock::new(); static ID_MAP: OnceLock> = OnceLock::new(); static ZH_MAP: OnceLock> = OnceLock::new(); fn map_for(lang: Lang) -> &'static HashMap { let cell = match lang { Lang::En => &EN_MAP, + Lang::Es => &ES_MAP, Lang::Id => &ID_MAP, Lang::Zh => &ZH_MAP, };