Skip to main content

orbok_ui/
i18n.rs

1//! Typed i18n message catalog (RFC-031).
2//!
3//! Compile-time completeness: each locale module implements one
4//! exhaustive `match` over [`MessageKey`]. Adding a key without adding
5//! every translation fails the build — there is no runtime fallback
6//! path to hide a missing string.
7//!
8//! Parameterized messages are plain functions (RFC-031 §5.3) so the
9//! compiler also checks their arguments.
10
11pub mod en;
12pub mod ja;
13
14use serde::{Deserialize, Serialize};
15
16/// Supported UI locales. Default English; persisted in the catalog
17/// under the `ui.locale` setting (read/written by `orbok-app`).
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum Locale {
21    #[default]
22    En,
23    Ja,
24}
25
26impl Locale {
27    pub const ALL: &'static [Locale] = &[Locale::En, Locale::Ja];
28
29    /// Setting string stored in `app_settings` (`"en"` / `"ja"`).
30    pub fn as_str(&self) -> &'static str {
31        match self {
32            Locale::En => "en",
33            Locale::Ja => "ja",
34        }
35    }
36
37    pub fn parse(s: &str) -> Option<Locale> {
38        match s {
39            "en" => Some(Locale::En),
40            "ja" => Some(Locale::Ja),
41            _ => None,
42        }
43    }
44
45    /// Self-described language name, shown in the language picker.
46    pub fn display_name(&self) -> &'static str {
47        match self {
48            Locale::En => "English",
49            Locale::Ja => "日本語",
50        }
51    }
52}
53
54/// Every fixed UI string. One variant per string; views never embed
55/// literals (RFC-031 §6 rule 1).
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum MessageKey {
58    // Application chrome
59    AppTitle,
60    LocalOnlyBadge,
61    // Navigation
62    NavSearch,
63    NavSources,
64    NavIndexing,
65    NavStorage,
66    NavModels,
67    NavSettings,
68    // Search view
69    SearchPlaceholder,
70    SearchButton,
71    SearchNoSourcesTitle,
72    SearchNoSourcesBody,
73    SearchAddSource,
74    SearchNoResults,
75    SearchKeywordOnlyNotice,
76    // Sources view
77    SourcesTitle,
78    SourcesEmptyTitle,
79    SourcesEmptyBody,
80    SourcesAddFolder,
81    SourcesStatusActive,
82    SourcesStatusPaused,
83    SourcesStatusMissing,
84    // Indexing view
85    IndexingTitle,
86    IndexingIdle,
87    IndexingHealthIndexed,
88    IndexingHealthStale,
89    IndexingHealthFailed,
90    IndexingHealthQueued,
91    // Storage view
92    StorageTitle,
93    StorageIntro,
94    StorageSafeCleanupHeading,
95    StorageClearSnippets,
96    StorageClearSearchCache,
97    StorageDangerHeading,
98    StorageResetCatalog,
99    StorageResetWarning,
100    // Models view
101    ModelsTitle,
102    ModelsEmbeddingRole,
103    ModelsRerankerRole,
104    ModelsStatusAvailable,
105    ModelsStatusMissing,
106    ModelsKeywordOnlyHint,
107    // Settings view
108    SettingsTitle,
109    SettingsLanguageHeading,
110    SettingsPrivacyHeading,
111    SettingsPrivacyLocalOnly,
112    // Search modes (RFC-009 §8)
113    SearchModeLabel,
114    SearchModeAuto,
115    SearchModeExact,
116    SearchModeConceptual,
117    SearchModeFast,
118    // Match badges
119    BadgeKeyword,
120    BadgeSemantic,
121    BadgeFused,
122    // Startup wizard (design §wizard)
123    WizardTitleNotConfigured,
124    WizardTitleFileMissing,
125    WizardTitleValidating,
126    WizardTitleReady,
127    WizardBodyNotConfigured,
128    WizardBodyFileMissing,
129    WizardFilesNeededLabel,
130    WizardDownloadHint,
131    WizardPathInputPlaceholder,
132    WizardActionLocate,
133    WizardActionValidate,
134    WizardActionUseModel,
135    WizardActionContinue,
136    WizardActionSkip,
137    WizardPreviousPathLabel,
138    WizardValidationOk,
139    WizardValidationFail,
140    WizardReadyBody,
141    // Common actions
142    Cancel,
143    Confirm,
144}
145
146/// Translate a fixed message. The per-locale functions are exhaustive
147/// matches — completeness is enforced by the compiler.
148pub fn tr(locale: Locale, key: MessageKey) -> &'static str {
149    match locale {
150        Locale::En => en::message(key),
151        Locale::Ja => ja::message(key),
152    }
153}
154
155/// Parameterized: "812 files indexed".
156pub fn files_indexed(locale: Locale, count: u64) -> String {
157    match locale {
158        Locale::En => format!("{count} files indexed"),
159        Locale::Ja => format!("{count} 件のファイルをインデックス済み"),
160    }
161}
162
163/// Parameterized: source card summary line.
164pub fn source_summary(locale: Locale, indexed: u64, stale: u64, failed: u64) -> String {
165    match locale {
166        Locale::En => format!("{indexed} indexed · {stale} stale · {failed} failed"),
167        Locale::Ja => format!("インデックス済み {indexed} · 要更新 {stale} · 失敗 {failed}"),
168    }
169}
170
171/// Parameterized: "3 results".
172pub fn search_result_count(locale: Locale, count: usize) -> String {
173    match locale {
174        Locale::En => format!("{count} result{}", if count == 1 { "" } else { "s" }),
175        Locale::Ja => format!("{count} 件の結果"),
176    }
177}