Compare commits

...
2 Commits
Author SHA1 Message Date
DomainmasteriandGitHub dd90d7b8b8 Add Finnish (fi) translation (#299)
* Add Finnish (fi) locale file for nodewarden web vault translation
2026-07-09 19:00:33 +08:00
rootphantomerandShuai 525b773cf4 fix: preserve dialog title during closing animation to prevent flash 2026-07-09 18:53:01 +08:00
4 changed files with 1456 additions and 5 deletions
+1
View File
@@ -13,6 +13,7 @@ const localeFiles = [
['zh-TW', 'zh-TW.ts', 'zhTW', 'Traditional Chinese'], ['zh-TW', 'zh-TW.ts', 'zhTW', 'Traditional Chinese'],
['ru', 'ru.ts', 'ru', 'Russian'], ['ru', 'ru.ts', 'ru', 'Russian'],
['es', 'es.ts', 'es', 'Spanish'], ['es', 'es.ts', 'es', 'Spanish'],
['fi', 'fi.ts', 'fi', 'Finnish'],
]; ];
function readLocale(fileName, variableName) { function readLocale(fileName, variableName) {
+3 -1
View File
@@ -87,6 +87,7 @@ export default function ConfirmDialog(props: ConfirmDialogProps) {
const cardRef = useRef<HTMLFormElement | null>(null); const cardRef = useRef<HTMLFormElement | null>(null);
const maskPointerStartedRef = useRef(false); const maskPointerStartedRef = useRef(false);
const restoreFocusRef = useRef<HTMLElement | null>(null); const restoreFocusRef = useRef<HTMLElement | null>(null);
const lastTitleRef = useRef<ComponentChildren>(props.title);
const dialogId = useMemo(() => `confirm-dialog-${++dialogIdCounter}`, []); const dialogId = useMemo(() => `confirm-dialog-${++dialogIdCounter}`, []);
const titleId = `${dialogId}-title`; const titleId = `${dialogId}-title`;
const messageId = `${dialogId}-message`; const messageId = `${dialogId}-message`;
@@ -95,6 +96,7 @@ export default function ConfirmDialog(props: ConfirmDialogProps) {
useEffect(() => { useEffect(() => {
if (props.open) { if (props.open) {
lastTitleRef.current = props.title;
setPresent(true); setPresent(true);
setClosing(false); setClosing(false);
return; return;
@@ -228,7 +230,7 @@ export default function ConfirmDialog(props: ConfirmDialogProps) {
<X size={18} /> <X size={18} />
</button> </button>
)} )}
<h3 id={titleId} className="dialog-title">{props.title}</h3> <h3 id={titleId} className="dialog-title">{props.open ? props.title : lastTitleRef.current}</h3>
{hasMessage && <div id={messageId} className={`dialog-message ${props.variant === 'warning' ? 'warning' : ''}`}>{props.message}</div>} {hasMessage && <div id={messageId} className={`dialog-message ${props.variant === 'warning' ? 'warning' : ''}`}>{props.message}</div>}
{props.children} {props.children}
{!props.hideConfirm && ( {!props.hideConfirm && (
+5 -4
View File
@@ -10,10 +10,10 @@ export type Locale =
| 'zh-CN' | 'zh-CN'
| 'zh-TW' | 'zh-TW'
| 'ru' | 'ru'
| 'es'; | 'es'
| 'fi';
import enMessages from './i18n/locales/en'; import enMessages from './i18n/locales/en';
const LOCALE_STORAGE_KEY = 'nodewarden.locale'; const LOCALE_STORAGE_KEY = 'nodewarden.locale';
type MessageTable = Record<string, string>; type MessageTable = Record<string, string>;
@@ -24,6 +24,7 @@ export const AVAILABLE_LOCALES: readonly { value: Locale; label: string }[] = [
{ value: 'zh-TW', label: '繁體中文' }, { value: 'zh-TW', label: '繁體中文' },
{ value: 'ru', label: 'Русский' }, { value: 'ru', label: 'Русский' },
{ value: 'es', label: 'Español' }, { value: 'es', label: 'Español' },
{ value: 'fi', label: 'Suomi' },
]; ];
let locale: Locale = resolveInitialLocale(); let locale: Locale = resolveInitialLocale();
@@ -49,6 +50,7 @@ function resolveInitialLocale(): Locale {
if (normalized.startsWith('zh')) return 'zh-CN'; if (normalized.startsWith('zh')) return 'zh-CN';
if (normalized.startsWith('ru')) return 'ru'; if (normalized.startsWith('ru')) return 'ru';
if (normalized.startsWith('es')) return 'es'; if (normalized.startsWith('es')) return 'es';
if (normalized.startsWith('fi')) return 'fi';
} }
} }
return 'en'; return 'en';
@@ -60,6 +62,7 @@ const localeLoaders: Record<Locale, () => Promise<{ default: MessageTable }>> =
'zh-TW': () => import('./i18n/locales/zh-TW'), 'zh-TW': () => import('./i18n/locales/zh-TW'),
ru: () => import('./i18n/locales/ru'), ru: () => import('./i18n/locales/ru'),
es: () => import('./i18n/locales/es'), es: () => import('./i18n/locales/es'),
fi: () => import('./i18n/locales/fi'),
}; };
function localeToHtmlLang(value: Locale): string { function localeToHtmlLang(value: Locale): string {
@@ -74,7 +77,6 @@ function syncDocumentLanguage(): void {
async function loadLocaleMessages(next: Locale): Promise<MessageTable> { async function loadLocaleMessages(next: Locale): Promise<MessageTable> {
const cached = loadedMessages.get(next); const cached = loadedMessages.get(next);
if (cached) return cached; if (cached) return cached;
const mod = await localeLoaders[next](); const mod = await localeLoaders[next]();
loadedMessages.set(next, mod.default); loadedMessages.set(next, mod.default);
return mod.default; return mod.default;
@@ -223,7 +225,6 @@ export function translateServerError(message: string | null | undefined, fallbac
'masterPasswordHash is required': 'txt_server_error_master_password_hash_required', 'masterPasswordHash is required': 'txt_server_error_master_password_hash_required',
'masterPasswordHash or userVerificationToken is required': 'txt_server_error_master_password_or_verification_required', 'masterPasswordHash or userVerificationToken is required': 'txt_server_error_master_password_or_verification_required',
}[normalized]; }[normalized];
return key ? t(key) : normalized; return key ? t(key) : normalized;
} }
File diff suppressed because it is too large Load Diff