Internationalization: 7 languages with live switching
Custom signal-based runtime (no i18n dependency): shared src/shared/i18n dictionaries, ui I18nService.t() reactive by construction so language switches live, and a backend i18n.ts for native menus + thrown errors. Every user-facing string keyed across templates, toasts, dialogs, file kinds, columns, macOS menu/context menus, and errors. Language picker in Options (auto-detects OS locale, falls back to English); switching relabels the webview and native chrome instantly. English/Spanish/French/ German fully drafted; Chinese/Japanese/Basque drafted, flagged for native review. Docs updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+32
-15
@@ -1,34 +1,51 @@
|
||||
const SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
export function formatSize(bytes: number): string {
|
||||
/** Locale-aware number formatting (decimal separator); cached per locale. */
|
||||
const NUM_FORMATS = new Map<string, Intl.NumberFormat>();
|
||||
function num(value: number, locale: string | undefined): string {
|
||||
const key = locale ?? '';
|
||||
let fmt = NUM_FORMATS.get(key);
|
||||
if (!fmt) {
|
||||
fmt = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 });
|
||||
NUM_FORMATS.set(key, fmt);
|
||||
}
|
||||
return fmt.format(value);
|
||||
}
|
||||
|
||||
export function formatSize(bytes: number, locale?: string): string {
|
||||
let value = bytes;
|
||||
let unit = 0;
|
||||
while (value >= 1024 && unit < SIZE_UNITS.length - 1) {
|
||||
value /= 1024;
|
||||
unit++;
|
||||
}
|
||||
return `${unit === 0 ? value : value.toFixed(1)} ${SIZE_UNITS[unit]}`;
|
||||
return `${unit === 0 ? num(value, locale) : num(Number(value.toFixed(1)), locale)} ${SIZE_UNITS[unit]}`;
|
||||
}
|
||||
|
||||
const DATE_FORMAT = new Intl.DateTimeFormat(undefined, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
||||
export function formatDate(epochMs: number): string {
|
||||
const DATE_FORMATS = new Map<string, Intl.DateTimeFormat>();
|
||||
export function formatDate(epochMs: number, locale?: string): string {
|
||||
if (!epochMs) return '';
|
||||
return DATE_FORMAT.format(new Date(epochMs));
|
||||
const key = locale ?? '';
|
||||
let fmt = DATE_FORMATS.get(key);
|
||||
if (!fmt) {
|
||||
fmt = new Intl.DateTimeFormat(locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
DATE_FORMATS.set(key, fmt);
|
||||
}
|
||||
return fmt.format(new Date(epochMs));
|
||||
}
|
||||
|
||||
/** bytes/sec -> "14.2 MB/s" or "113.8 Mbps" depending on the configured unit. */
|
||||
export function formatSpeed(bytesPerSec: number, unit: 'MBps' | 'Mbps'): string {
|
||||
export function formatSpeed(bytesPerSec: number, unit: 'MBps' | 'Mbps', locale?: string): string {
|
||||
if (unit === 'Mbps') {
|
||||
return `${((bytesPerSec * 8) / 1_000_000).toFixed(1)} Mbps`;
|
||||
return `${num(Number(((bytesPerSec * 8) / 1_000_000).toFixed(1)), locale)} Mbps`;
|
||||
}
|
||||
return `${formatSize(bytesPerSec)}/s`;
|
||||
return `${formatSize(bytesPerSec, locale)}/s`;
|
||||
}
|
||||
|
||||
/** Seconds -> "0:42" / "3:07" / "1:02:15". */
|
||||
|
||||
Reference in New Issue
Block a user