Skip to content

Commit 4522b14

Browse files
committed
fix(i18n): address review feedback
1 parent 1c7280a commit 4522b14

17 files changed

Lines changed: 118 additions & 36 deletions

emain/emain-menu.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ async function makeFullAppMenu(callbacks: AppMenuCallbacks, workspaceOrBuilderId
345345
const isBuilderWindowFocused = focusedBuilderWindow != null;
346346
let fullscreenOnLaunch = false;
347347
let fullConfig: FullConfigType = null;
348+
setI18nLocale(resolveLocale(undefined, electron.app.getLocale()));
348349
try {
349350
fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient);
350351
setI18nLocale(resolveLocale(fullConfig?.settings?.["app:locale"], electron.app.getLocale()));

frontend/app/aipanel/aimode.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ function computeWaveCloudSections(
121121

122122
if (waveProviderConfigs.length > 0) {
123123
sections.push({
124-
sectionName: "Wave AI Cloud",
124+
sectionName: t("Wave AI Cloud"),
125125
configs: waveProviderConfigs,
126126
noTelemetry: !telemetryEnabled,
127127
});

frontend/app/aipanel/aipanel.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,14 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps
469469
const rejectedFiles = files.filter((f) => !isAcceptableFile(f));
470470
const fileNames = rejectedFiles.map((f) => f.name).join(", ");
471471
model.setError(
472-
`${rejectedCount} file${rejectedCount > 1 ? "s" : ""} rejected (unsupported type): ${fileNames}. Supported: images, PDFs, and text/code files.`
472+
t(
473+
"{count} file{plural} rejected (unsupported type): {fileNames}. Supported: images, PDFs, and text/code files.",
474+
{
475+
count: rejectedCount,
476+
plural: rejectedCount > 1 ? "s" : "",
477+
fileNames,
478+
}
479+
)
473480
);
474481
}
475482
};

frontend/app/aipanel/airatelimitstrip.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ function formatTimeRemaining(expirationEpoch: number): string {
5454
const minutes = Math.floor((secondsRemaining % 3600) / 60);
5555

5656
if (hours > 0) {
57-
return `${hours}h`;
57+
return t("{hours}h", { hours });
5858
}
59-
return `${minutes}m`;
59+
return t("{minutes}m", { minutes });
6060
}
6161

6262
const AIRateLimitStripComponent = memo(() => {

frontend/app/aipanel/restorebackupmodal.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ export const RestoreBackupModal = memo(({ part }: RestoreBackupModalProps) => {
4545
<div className="flex flex-col gap-4 pt-4 pb-4 max-w-xl">
4646
<div className="font-semibold text-lg text-green-500">{t("Backup Successfully Restored")}</div>
4747
<div className="text-sm text-gray-300 leading-relaxed">
48-
{t("The file")} <span className="font-mono text-white break-all">{toolData.inputfilename}</span>{" "}
49-
{t("has been restored to its previous state.")}
48+
{t("The file {filename} has been restored to its previous state.", {
49+
filename: toolData.inputfilename,
50+
})}
5051
</div>
5152
</div>
5253
</Modal>
@@ -83,10 +84,10 @@ export const RestoreBackupModal = memo(({ part }: RestoreBackupModalProps) => {
8384
<div className="flex flex-col gap-4 pt-4 pb-4 max-w-xl">
8485
<div className="font-semibold text-lg">{t("Restore File Backup")}</div>
8586
<div className="text-sm text-gray-300 leading-relaxed">
86-
{t("This will restore")}{" "}
87-
<span className="font-mono text-white break-all">{toolData.inputfilename}</span>{" "}
88-
{t("to its state before this edit was made")}
89-
{toolData.runts && <span> ({formatTimestamp(toolData.runts)})</span>}.
87+
{t("This will restore {filename} to its state before this edit was made{timestamp}.", {
88+
filename: toolData.inputfilename,
89+
timestamp: toolData.runts ? ` (${formatTimestamp(toolData.runts)})` : "",
90+
})}
9091
</div>
9192
<div className="text-sm text-gray-300 leading-relaxed">
9293
{t("Any changes made by this edit and subsequent edits will be lost.")}

frontend/app/aipanel/telemetryrequired.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ const TelemetryRequiredMessage = ({ className }: TelemetryRequiredMessageProps)
5050
<div className="text-blue-400 font-medium mb-1">{t("Telemetry keeps Wave AI free")}</div>
5151
<div className="text-secondary text-sm mb-3">
5252
<p className="mb-2">
53-
{t("To keep Wave AI free for everyone, we require a small amount of")}{" "}
54-
<i>{t("anonymous")}</i>{" "}
55-
{t("usage data (app version, feature usage, system info).")}
53+
{t(
54+
"To keep Wave AI free for everyone, we require a small amount of anonymous usage data (app version, feature usage, system info)."
55+
)}
5656
</p>
5757
<p className="mb-2">
5858
{t("This helps us block abuse by automated systems and ensure it's used by real people like you.")}

frontend/app/i18n/i18n.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { afterEach, describe, expect, it } from "vitest";
5-
import { getI18nLocale, resolveLocale, setI18nLocale, supportedLocales, t } from "./index";
5+
import { getI18nLocale, normalizeLocale, resolveLocale, setI18nLocale, supportedLocales, t } from "./index";
66

77
describe("i18n", () => {
88
afterEach(() => {
@@ -46,6 +46,22 @@ describe("i18n", () => {
4646
setI18nLocale("zh-CN");
4747
expect(t("Client Version {version}", { version: "0.14.5" })).toBe("客户端版本 0.14.5");
4848
expect(t("Open Clipboard URL ({host})", { host: "example.com" })).toBe("打开剪贴板 URL(example.com)");
49+
expect(t('Secret "{name}" already exists', { name: "MY_TOKEN" })).toBe("密钥“MY_TOKEN”已存在");
50+
expect(t("Go to {label} ({path})", { label: "桌面", path: "~/Desktop" })).toBe("转到 桌面(~/Desktop)");
51+
});
52+
53+
it("translates review feedback strings", () => {
54+
setI18nLocale("zh-CN");
55+
expect(t("The current secret value is not shown by default for security purposes.")).toBe(
56+
"出于安全考虑,当前密钥值默认不会显示。"
57+
);
58+
expect(t("{minutes}m", { minutes: 5 })).toBe("5 分钟");
59+
expect(
60+
t(
61+
"{count} file{plural} rejected (unsupported type): {fileNames}. Supported: images, PDFs, and text/code files.",
62+
{ count: 2, plural: "s", fileNames: "demo.exe" }
63+
)
64+
).toBe("已拒绝 2 个文件(不支持的类型):demo.exe。支持图片、PDF、文本和代码文件。");
4965
});
5066

5167
it("falls back to the original key when a translation is missing", () => {
@@ -58,5 +74,7 @@ describe("i18n", () => {
5874
expect(resolveLocale(null, "ja-JP")).toBe("ja-JP");
5975
expect(resolveLocale("en", "zh-CN")).toBe("en-US");
6076
expect(resolveLocale("fr-FR", "fr-FR")).toBe("en-US");
77+
expect(normalizeLocale("en")).toBe("en-US");
78+
expect(normalizeLocale("zh_Hans_CN")).toBe("zh-CN");
6179
});
6280
});

frontend/app/i18n/ja-JP.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,28 @@ export const jaJP: I18nCatalog = {
360360
"Tab Switching ({modifier})": "タブ切り替え({modifier})",
361361
Tabs: "タブ",
362362
"Web View": "Web ビュー",
363+
"Wave AI Cloud": "Wave AI Cloud",
364+
"{count} file{plural} rejected (unsupported type): {fileNames}. Supported: images, PDFs, and text/code files.":
365+
"{count} 個のファイルが拒否されました(未対応の種類): {fileNames}。画像、PDF、テキスト/コードファイルに対応しています。",
366+
"{hours}h": "{hours} 時間",
367+
"{minutes}m": "{minutes} 分",
368+
"The file {filename} has been restored to its previous state.":
369+
"ファイル {filename} は以前の状態に復元されました。",
370+
"This will restore {filename} to its state before this edit was made{timestamp}.":
371+
"{filename} をこの編集前の状態に復元します{timestamp}。",
372+
"To keep Wave AI free for everyone, we require a small amount of anonymous usage data (app version, feature usage, system info).":
373+
"Wave AI をすべてのユーザーに無料で提供し続けるため、少量の匿名利用データ(アプリバージョン、機能利用状況、システム情報)が必要です。",
374+
"Deleting a directory requires the recursive flag. Proceed?":
375+
"ディレクトリを削除するには再帰フラグが必要です。続行しますか?",
376+
"Go to {label} ({path})": "{label} ({path}) に移動",
377+
"Wave AI integration disabled while you're inside {command}.":
378+
"{command} の内部では Wave AI 連携が無効です。",
379+
"Must start with a letter and contain only letters, numbers, and underscores":
380+
"英字で始まり、英字、数字、アンダースコアのみを含める必要があります",
381+
"The current secret value is not shown by default for security purposes.":
382+
"セキュリティ上の理由により、現在のシークレット値は既定では表示されません。",
383+
"Deleting...": "削除中...",
384+
'Secret "{name}" already exists': 'シークレット「{name}」は既に存在します',
363385
"wsh commands": "wsh コマンド",
364386
anonymous: "匿名",
365387
apps: "アプリ",

frontend/app/i18n/zh-CN.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,5 +537,25 @@ export const zhCN: Record<string, string> = {
537537
"Tab Switching ({modifier})": "标签切换({modifier})",
538538
Tabs: "标签",
539539
"Web View": "网页视图",
540+
"Wave AI Cloud": "Wave AI Cloud",
541+
"{count} file{plural} rejected (unsupported type): {fileNames}. Supported: images, PDFs, and text/code files.":
542+
"已拒绝 {count} 个文件(不支持的类型):{fileNames}。支持图片、PDF、文本和代码文件。",
543+
"{hours}h": "{hours} 小时",
544+
"{minutes}m": "{minutes} 分钟",
545+
"The file {filename} has been restored to its previous state.": "文件 {filename} 已恢复到之前的状态。",
546+
"This will restore {filename} to its state before this edit was made{timestamp}.":
547+
"这会将 {filename} 恢复到本次编辑前的状态{timestamp}。",
548+
"To keep Wave AI free for everyone, we require a small amount of anonymous usage data (app version, feature usage, system info).":
549+
"为了让 Wave AI 对所有人保持免费,我们需要少量匿名使用数据(应用版本、功能使用情况、系统信息)。",
550+
"Deleting a directory requires the recursive flag. Proceed?": "删除目录需要递归标志。是否继续?",
551+
"Go to {label} ({path})": "转到 {label}({path})",
552+
"Wave AI integration disabled while you're inside {command}.":
553+
"当你位于 {command} 内部时,Wave AI 集成已禁用。",
554+
"Must start with a letter and contain only letters, numbers, and underscores":
555+
"必须以字母开头,且只能包含字母、数字和下划线",
556+
"The current secret value is not shown by default for security purposes.":
557+
"出于安全考虑,当前密钥值默认不会显示。",
558+
"Deleting...": "正在删除...",
559+
'Secret "{name}" already exists': '密钥“{name}”已存在',
540560
"wsh commands": "wsh 命令",
541561
};

frontend/app/view/launcher/launcher.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,22 @@ export class LauncherViewModel implements ViewModel {
4343
}
4444

4545
filteredWidgetsAtom = atom((get) => {
46-
const searchTerm = get(this.searchTerm);
46+
const searchTerm = get(this.searchTerm).trim().toLowerCase();
4747
const widgets = sortByDisplayOrder(get(atoms.fullConfigAtom)?.widgets || {});
48-
return widgets.filter(
49-
(widget) =>
50-
!widget["display:hidden"] &&
51-
(!searchTerm || widget.label?.toLowerCase().includes(searchTerm.toLowerCase()))
52-
);
48+
return widgets.filter((widget) => {
49+
if (widget["display:hidden"]) {
50+
return false;
51+
}
52+
if (!searchTerm) {
53+
return true;
54+
}
55+
const searchableText = [widget.label, widget.description]
56+
.filter(Boolean)
57+
.flatMap((value) => [value, t(value)])
58+
.join(" ")
59+
.toLowerCase();
60+
return searchableText.includes(searchTerm);
61+
});
5362
});
5463

5564
giveFocus(): boolean {

0 commit comments

Comments
 (0)