feat(i18n): add localization infrastructure and English catalog

Refs #17
This commit is contained in:
NimBold
2026-07-18 01:06:21 +03:30
parent 62365f514e
commit c914eeb7b3
28 changed files with 1845 additions and 652 deletions
+8 -5
View File
@@ -14,6 +14,7 @@ import {
resolveCategoryDestination
} from '../utils/downloadLocations';
import { canPauseDownload, canStartDownload } from '../utils/downloadActions';
import i18n from '../i18n';
export type { DownloadCategory } from '../utils/downloads';
@@ -896,7 +897,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
if (!item) return;
if (item.status === 'downloading' || item.status === 'processing' || item.status === 'retrying') {
throw new Error("Cannot change properties while transfer is active. Pause it first.");
throw new Error(i18n.t($ => $.downloadTable.transferActive));
}
if (item.status === 'ready' || item.status === 'staged' || item.status === 'completed' || item.status === 'failed') {
@@ -1011,15 +1012,17 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
await waitForPendingStartupResume();
const targetItem = get().downloads.find(d => d.id === id);
if (!targetItem) {
throw new Error('Cannot redownload: download was not found.');
throw new Error(i18n.t($ => $.downloadTable.redownloadNotFound));
}
if (!['completed', 'failed', 'paused'].includes(targetItem.status)) {
throw new Error(`Cannot redownload a ${targetItem.status} download. Pause or wait for it to finish first.`);
throw new Error(i18n.t($ => $.downloadTable.redownloadActive, {
status: i18n.t($ => $.downloads.status[targetItem.status])
}));
}
const url = targetItem.url?.trim();
if (!url) throw new Error('Cannot redownload: original URL is missing.');
if (!url) throw new Error(i18n.t($ => $.downloadTable.originalUrlMissing));
setDownloadControlIntent(id, 'resume');
await invalidateAndWaitForDispatch(id);
@@ -1283,7 +1286,7 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
const selected = get().downloads.filter(item => selectedIds.has(item.id));
const locked = selected.find(item => isActiveDownloadStatus(item.status) && item.status !== 'queued');
if (locked) {
throw new Error(`Pause ${locked.fileName} before moving it to another queue.`);
throw new Error(i18n.t($ => $.downloadTable.pauseBeforeMove, { file: locked.fileName }));
}
const movableSelected = selected.filter(item => item.status !== 'completed');
+3 -2
View File
@@ -19,6 +19,7 @@ import {
normalizeDownloadLocationSettings
} from '../utils/downloadLocations';
import { normalizeSpeedLimitForBackend } from '../utils/downloads';
import i18n from '../i18n';
let settingsQueue: Promise<void> = Promise.resolve();
let pairingTokenHydrationRequest: Promise<PairingTokenHydration> | null = null;
@@ -425,11 +426,11 @@ export const useSettingsStore = create<SettingsState>()(
const current = get();
if (!current.keychainAccessReady && !current.isPairingTokenPersistent) {
set({ showKeychainModal: true });
throw new Error('Grant credential-store access before regenerating the pairing token.');
throw new Error(i18n.t($ => $.keychain.accessRequired));
}
const result = await invoke('regenerate_pairing_token');
if (!result.persistent) {
throw new Error(result.error || 'Credential store access is unavailable.');
throw new Error(result.error || i18n.t($ => $.keychain.storeUnavailable));
}
set({
extensionPairingToken: result.token,