diff --git a/changelog.d/fixes/9245-hardcoded-web-ui-i18n.md b/changelog.d/fixes/9245-hardcoded-web-ui-i18n.md new file mode 100644 index 0000000000..a2c0157189 --- /dev/null +++ b/changelog.d/fixes/9245-hardcoded-web-ui-i18n.md @@ -0,0 +1 @@ +- **fix(i18n):** localized hardcoded web UI copy across public pages, dashboard views, and shared components, with complete French and Vietnamese coverage ([#9245](https://github.com/diegosouzapw/OmniRoute/pull/9245)) — thanks @alex-jordan547 diff --git a/src/app/(dashboard)/dashboard/BootstrapBanner.tsx b/src/app/(dashboard)/dashboard/BootstrapBanner.tsx index a819d82d1d..f771ae34a7 100644 --- a/src/app/(dashboard)/dashboard/BootstrapBanner.tsx +++ b/src/app/(dashboard)/dashboard/BootstrapBanner.tsx @@ -27,22 +27,17 @@ export default function BootstrapBanner() { ⚠️
- Running in zero-config mode + {t("zeroConfigBannerTitle")}
- OmniRoute auto-generated secure encryption keys on first launch. They are persisted to{" "}
-
- {dataDir}
-
- . No action is required — your data is encrypted and safe. To use custom keys, add{" "}
-
- JWT_SECRET
- {" "}
- and{" "}
-
- STORAGE_ENCRYPTION_KEY
- {" "}
- to that file.
+ {t.rich("zeroConfigBannerBody", {
+ dataDir,
+ code: (chunks) => (
+
+ {chunks}
+
+ ),
+ })}
- Update Available: v{versionInfo.latest} {isElectron && "(Desktop App)"} + {t("updateAvailableTitle", { + version: versionInfo.latest, + desktop: isElectron ? ` ${t("desktopAppLabel")}` : "", + })}
{isElectron ? ( <> - {electronUpdateStatus.status === "checking" && "Checking for updates..."} + {electronUpdateStatus.status === "checking" && t("checkingForUpdates")} {electronUpdateStatus.status === "available" && - `Version v${versionInfo.latest} is available for download.`} + t("versionAvailableForDownload", { version: versionInfo.latest })} {electronUpdateStatus.status === "downloading" && - `Downloading update... ${electronUpdateStatus.percent || 0}% complete.`} - {electronUpdateStatus.status === "downloaded" && - "Update downloaded successfully! Click Restart & Install to apply."} + t("downloadingUpdate", { percent: electronUpdateStatus.percent || 0 })} + {electronUpdateStatus.status === "downloaded" && t("updateDownloaded")} {electronUpdateStatus.status === "error" && - `Auto-update failed: ${electronUpdateStatus.message || "Unknown error"}.`} + t("autoUpdateFailed", { + reason: electronUpdateStatus.message || t("unknownUpdateError"), + })} {(electronUpdateStatus.status === "idle" || electronUpdateStatus.status === "not-available") && - `Version v${versionInfo.latest} is available for the desktop app.`} + t("versionAvailableDesktop", { version: versionInfo.latest })} > ) : versionInfo.autoUpdateSupported ? ( - t("updateAvailableDesc") || - `You are currently using v${versionInfo.current}. Update to access the latest features and bug fixes.` + t("updateAvailableDesc") ) : ( - versionInfo.autoUpdateError || - "Manual update required for this installation type." + versionInfo.autoUpdateError || t("manualUpdateRequired") )}
- Or download the respective installer format directly: -
+{t("directDownloadHint")}
- Exploration Rate + {getI18nOrFallback(t, "explorationRateLabel", "Exploration Rate")}
{Math.round(normalizedConfig.explorationRate * 100)}% diff --git a/src/app/(dashboard)/dashboard/combos/error.tsx b/src/app/(dashboard)/dashboard/combos/error.tsx index 4071cb243d..4f7fa2a903 100644 --- a/src/app/(dashboard)/dashboard/combos/error.tsx +++ b/src/app/(dashboard)/dashboard/combos/error.tsx @@ -1,5 +1,7 @@ "use client"; +import { useTranslations } from "next-intl"; + export default function CombosError({ error: _error, reset, @@ -7,6 +9,8 @@ export default function CombosError({ error: Error & { digest?: string }; reset: () => void; }) { + const t = useTranslations("combos"); + return (
- We could not load combo data right now. Check your connection and try again. -
+{t("errorDescription")}
{_error?.digest && ( -Error ID: {_error.digest}
+{t("errorId", { id: _error.digest })}
)} {process.env.NODE_ENV === "development" && _error?.message && ({_error.message}
@@ -30,7 +30,7 @@ export default function CombosError({ onClick={reset} className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-hover transition-colors focus:outline-2 focus:outline-offset-2 focus:outline-primary" > - Try Again + {t("errorRetry")}- Search, read, query, and write to Notion through routed AI models + {translateOrFallback( + "notionDescription", + "Search, read, query, and write to Notion through routed AI models" + )}
- Create an Internal Integration at{" "}
+ {translateOrFallback(
+ "notionIntegrationHelp",
+ "Create an Internal Integration at"
+ )}{" "}
https://www.notion.so/profile/integrations
@@ -156,7 +200,10 @@ export default function NotionSourceCard() {
) : (
- These connections returned a 429 (rate-limit) on their last request. OmniRoute will skip - them until the timer expires — no manual disable required. + {providerText( + t, + "coolingConnectionsDescription", + "These connections returned a 429 (rate-limit) on their last request. OmniRoute will skip them until the timer expires — no manual disable required." + )}
{t("apiKeySecure")}
- {t("compatBlockedParamsHint") ?? "Blocked params (stripped from requests)"} + {providerText( + t, + "compatBlockedParamsHint", + "Blocked params (stripped from requests)" + )} {paramSaving && ` ● ${t("compatSaving")}`}
- {t("compatAllowedParamsHint") ?? "Allowed params (re-added after deny)"} + {providerText( + t, + "compatAllowedParamsHint", + "Allowed params (re-added after deny)" + )}
- We could not load provider data right now. Check your connection and try again. + {providerText( + t, + "pageLoadErrorDescription", + "We could not load provider data right now. Check your connection and try again." + )}
{_error?.digest && ( -Error ID: {_error.digest}
++ {providerText(t, "pageLoadErrorId", "Error ID: {id}", { id: _error.digest })} +
)} {process.env.NODE_ENV === "development" && _error?.message && ({_error.message}
@@ -30,7 +60,7 @@ export default function ProvidersError({ onClick={reset} className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-hover transition-colors focus:outline-2 focus:outline-offset-2 focus:outline-primary" > - Try Again + {providerText(t, "pageLoadErrorRetry", "Try Again")}- {t("backgroundDegradationDesc") || - "Auto-redirect background requests (titles, summaries) to cheaper models"} -
+{t("backgroundDegradationDesc")}
- {t("enableDegradation") || "Enable Background Degradation"} -
+{t("enableDegradation")}
{t("enableDegradationHint") || "Automatically use cheaper models for background utility tasks"} @@ -124,7 +116,7 @@ export default function BackgroundDegradationTab() { checked={config.enabled} onChange={(enabled) => save({ enabled })} disabled={loading || saving} - ariaLabel={t("enableDegradation") || "Enable Background Degradation"} + ariaLabel={t("enableDegradation")} />
- {t("degradationMap") || "Model Degradation Map"} + {t("degradationMap")}
{/* Add new mapping */} @@ -157,23 +147,19 @@ export default function BackgroundDegradationTab() {- When enabled, failed requests are retried through CLIProxyAPI (localhost:8317) -
+{t("cliproxyapiFallbackDescription")}
{t("cliproxyapiVersion")}
- {toolState.installedVersion ? `v${toolState.installedVersion}` : "Not installed"} + {toolState.installedVersion ? `v${toolState.installedVersion}` : t("notInstalled")}
- Connect to SkillsMP to discover and install skills from the marketplace. -
+{t("memorySkillsSkillsmpDescription")}
- Get your API key from skillsmp.com. Rate limit: - 500 requests/day. + {t("skillsmpApiKeyHintBefore")} skillsmp.com + {t("skillsmpApiKeyHintAfter", { limit: 500 })}
@@ -836,9 +834,7 @@ export default function MemorySkillsTab() {- Choose which provider the Skills page uses for search and install. -
+{t("memorySkillsActiveProviderDescription")}
- SkillsMP Marketplace + {t("memorySkillsSkillsmpProviderTitle")}
- Authenticated marketplace (uses your SkillsMP API key). + {t("memorySkillsSkillsmpProviderDescription")}
@@ -887,10 +883,10 @@ export default function MemorySkillsTab() {- skills.sh Directory + {t("memorySkillsSkillsshProviderTitle")}
- Public directory provider (no API key required). + {t("memorySkillsSkillsshProviderDescription")}
diff --git a/src/app/(dashboard)/dashboard/settings/components/ModelAliasesTab.tsx b/src/app/(dashboard)/dashboard/settings/components/ModelAliasesTab.tsx index 126704f1f2..68177a313a 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ModelAliasesTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ModelAliasesTab.tsx @@ -82,26 +82,23 @@ export default function ModelAliasesTab() {- {t("modelAliasesDesc") || "Auto-forward deprecated model IDs to their replacements"} -
+{t("modelAliasesDesc")}
{t("addCustomAlias") || "Add Custom Alias"}
+{t("addCustomAlias")}
- {t("customAliases") || "Custom Aliases"} + {t("customAliases")}
- Fetch and rotate free validated proxies from the 1proxy community platform -
+{t("oneproxyDescription")}
| {t("oneproxyAnonymity")} | -+ {t("oneproxyGoogle")} + | {t("oneproxyActions")} | @@ -303,7 +310,7 @@ export default function OneproxyTab() { onClick={() => handleDelete(proxy.id)} className="text-red-500 hover:text-red-700 text-xs" > - Delete + {t("oneproxyDelete")} diff --git a/src/app/(dashboard)/dashboard/settings/components/ProxyStatusBadge.tsx b/src/app/(dashboard)/dashboard/settings/components/ProxyStatusBadge.tsx index 4f1287bf4f..58a39d8519 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ProxyStatusBadge.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ProxyStatusBadge.tsx @@ -1,10 +1,13 @@ "use client"; +import { useTranslations } from "next-intl"; + interface ProxyStatusBadgeProps { status?: string; } export function ProxyStatusBadge({ status }: ProxyStatusBadgeProps) { + const t = useTranslations("settings"); const isInactive = status === "inactive"; return ( - {isInactive ? "Inactive" : "Active"} + {isInactive ? t("proxyStatusInactive") : t("proxyStatusActive")} ); } diff --git a/src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx b/src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx index f96d479654..f2c8c7a114 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx @@ -702,7 +702,7 @@ function ComboCooldownWaitCard({ setDraft(value); }, [value]); - const title = t("resilienceComboCooldownWaitTitle") || "Combo cooldown wait"; + const title = t("resilienceComboCooldownWaitTitle"); const desc = t("resilienceComboCooldownWaitDesc") || "For all combo strategies: wait out a short transient cooldown and re-dispatch instead of returning a 429 immediately. Never waits on quota_exhausted."; @@ -735,7 +735,7 @@ function ComboCooldownWaitCard({ {editing ? ( <>{proxy.source} | @@ -64,16 +67,16 @@ export default function FreeProxyRow({
{proxy.inPool ? (
- in pool
+ {t("proxyFreePoolInPool")}
) : (
|
diff --git a/src/app/(dashboard)/dashboard/settings/error.tsx b/src/app/(dashboard)/dashboard/settings/error.tsx
index 4449ef5cb3..4c0adcd7f6 100644
--- a/src/app/(dashboard)/dashboard/settings/error.tsx
+++ b/src/app/(dashboard)/dashboard/settings/error.tsx
@@ -1,5 +1,7 @@
"use client";
+import { useTranslations } from "next-intl";
+
export default function SettingsError({
error: _error,
reset,
@@ -7,6 +9,8 @@ export default function SettingsError({
error: Error & { digest?: string };
reset: () => void;
}) {
+ const t = useTranslations("settings");
+
return (
|---|