mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-03 18:23:51 +08:00
feat(i18n): add i18n support
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: i18n
|
||||
description: Implement and review Cursor BYOK desktop localization, including t() usage, locale catalogs, system-language selection, translation completeness, and language settings under apps/desktop.
|
||||
---
|
||||
|
||||
# Desktop i18n
|
||||
|
||||
Keep localization organized as:
|
||||
|
||||
```text
|
||||
apps/desktop/
|
||||
├── plugins/static-i18n-plugin.ts
|
||||
└── src/i18n/
|
||||
├── I18nRoot.tsx
|
||||
├── runtime.ts
|
||||
├── store.ts
|
||||
├── generated/catalog.json
|
||||
└── locales/
|
||||
├── zh-CN.json
|
||||
└── en-US.json
|
||||
```
|
||||
|
||||
## Author messages
|
||||
|
||||
- Use the global `t()` function with a static Simplified Chinese string literal: `t("保存")`.
|
||||
- Do not import or locally declare `t`.
|
||||
- Keep calls inside render paths when the result must update after a language change. Do not translate module-level UI constants.
|
||||
- Use named placeholders with a static object: `t("第 {page} 页", { page })`.
|
||||
- Preserve every placeholder name exactly in all translations.
|
||||
- Wrap all user-visible labels, descriptions, tooltips, empty states, validation messages, and accessibility labels. Do not wrap protocol tokens, model IDs, URLs, or product names that should remain unchanged.
|
||||
|
||||
## Locale behavior
|
||||
|
||||
- `system` is the default preference.
|
||||
- Resolve supported operating-system languages to their locale and fall back to `en-US` for every unsupported language.
|
||||
- Persist only explicit locale selections. Removing the preference restores system-language behavior.
|
||||
- Apply changes immediately and update the document `lang` attribute.
|
||||
|
||||
## Update catalogs
|
||||
|
||||
From `apps/desktop`:
|
||||
|
||||
1. Run `npm run i18n:scan` after adding or changing `t()` sources.
|
||||
2. Translate every empty entry in `src/i18n/locales/en-US.json`.
|
||||
3. Never edit `src/i18n/generated/catalog.json` or `zh-CN.json` manually; scanning owns them.
|
||||
4. Run `npm run check`. A normal build must fail on a missing translation or mismatched placeholder.
|
||||
|
||||
When adding another locale, add it to the plugin's supported locales, runtime locale type and messages, locale resolver, settings options, and provide a complete locale JSON file.
|
||||
@@ -295,7 +295,8 @@ function mergeLocaleMessages(
|
||||
|
||||
function validateTranslations(
|
||||
sourceRoot: string,
|
||||
entries: Record<string, MergedEntry>
|
||||
entries: Record<string, MergedEntry>,
|
||||
requireComplete: boolean
|
||||
): void {
|
||||
for (const locale of SUPPORTED_LOCALES) {
|
||||
if (locale === SOURCE_LOCALE) continue
|
||||
@@ -303,7 +304,14 @@ function validateTranslations(
|
||||
const messages = readJSONFile(localePath)
|
||||
for (const [id, entry] of Object.entries(entries)) {
|
||||
const translation = messages[id]
|
||||
if (!translation) continue
|
||||
if (!translation) {
|
||||
if (requireComplete) {
|
||||
throw new Error(
|
||||
`[static-i18n] ${localePath}:${id} 缺少译文:${entry.source}`
|
||||
)
|
||||
}
|
||||
continue
|
||||
}
|
||||
const actual = placeholderNames(translation)
|
||||
assertUniquePlaceholders(actual, `${localePath}:${id}`, translation)
|
||||
const missing = entry.placeholders.filter((name) => !actual.includes(name))
|
||||
@@ -446,8 +454,8 @@ export function staticI18nPlugin(): Plugin {
|
||||
},
|
||||
buildStart() {
|
||||
const catalog = collectCatalog(sourceRoot)
|
||||
validateTranslations(sourceRoot, catalog.entries)
|
||||
if (shouldScan) syncCatalogFiles(sourceRoot, catalog)
|
||||
validateTranslations(sourceRoot, catalog.entries, !shouldScan)
|
||||
},
|
||||
transform(code, id) {
|
||||
if (!isSourceFile(id) || isExcludedFile(sourceRoot, id)) return null
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AdRuntime } from "./components/ads/types";
|
||||
import type { Locale } from "./i18n/runtime";
|
||||
|
||||
export type ProviderType = "openai-chat" | "openai-responses" | "anthropic";
|
||||
|
||||
@@ -221,9 +222,14 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
}
|
||||
|
||||
export const api = {
|
||||
ads: (disabledAdIds: Iterable<string>) => {
|
||||
ads: (disabledAdIds: Iterable<string>, locale: Locale) => {
|
||||
const value = [...disabledAdIds].join(",");
|
||||
return request<AdRuntime>("/ads", value ? { headers: { "disable-ad-ids": value } } : undefined);
|
||||
return request<AdRuntime>("/ads", {
|
||||
headers: {
|
||||
"accept-language": locale,
|
||||
...(value ? { "disable-ad-ids": value } : {}),
|
||||
},
|
||||
});
|
||||
},
|
||||
dismissAd: (id: string, reason: string) => request<void>(`/ads/${encodeURIComponent(id)}/dismissals`, { method: "POST", body: JSON.stringify({ reason }) }),
|
||||
providers: () => request<Provider[]>("/providers"),
|
||||
|
||||
@@ -11,14 +11,6 @@ import styles from "./OverviewTimeRangeFilter.module.scss";
|
||||
|
||||
export type OverviewRangePreset = "ten-minutes" | "hour" | "today" | "week" | "month" | "custom";
|
||||
|
||||
const presets: Array<{ value: Exclude<OverviewRangePreset, "custom">; label: string }> = [
|
||||
{ value: "hour", label: "近1小时" },
|
||||
{ value: "today", label: "近1自然日" },
|
||||
{ value: "ten-minutes", label: "近10分钟" },
|
||||
{ value: "week", label: "近一周" },
|
||||
{ value: "month", label: "近一个月" },
|
||||
];
|
||||
|
||||
export function OverviewTimeRangeFilter({ value, customOpen, customStart, customEnd, modelOptions, providerOptions, selectedModels, selectedProviders, busy, onSelect, onCustomOpenChange, onCustomStartChange, onCustomEndChange, onSelectedModelsChange, onSelectedProvidersChange, onCustomApply, onRefresh }: {
|
||||
value: OverviewRangePreset;
|
||||
customOpen: boolean;
|
||||
@@ -38,6 +30,13 @@ export function OverviewTimeRangeFilter({ value, customOpen, customStart, custom
|
||||
onCustomApply: () => void;
|
||||
onRefresh: () => void;
|
||||
}) {
|
||||
const presets: Array<{ value: Exclude<OverviewRangePreset, "custom">; label: string }> = [
|
||||
{ value: "hour", label: t("近1小时") },
|
||||
{ value: "today", label: t("近1自然日") },
|
||||
{ value: "ten-minutes", label: t("近10分钟") },
|
||||
{ value: "week", label: t("近一周") },
|
||||
{ value: "month", label: t("近一个月") },
|
||||
];
|
||||
const customButton = useRef<HTMLButtonElement>(null);
|
||||
const popover = useRef<HTMLDivElement>(null);
|
||||
const popoverId = useId();
|
||||
|
||||
@@ -6,7 +6,7 @@ const icon = (body: string, width = 24, height = 24): IconifyIcon => ({ body, wi
|
||||
|
||||
export const flatColorAreaChartIcon = icon('<path fill="#3F51B5" d="M42 37H6V25l10-15l14 7L42 6z"/><path fill="#00BCD4" d="M42 42H6V32l10-8l14 2l12-9z"/>', 48, 48);
|
||||
export const flatColorSalesPerformanceIcon = icon('<g fill="#FFA000"><path d="M32 8h12v32c0 1.1-2.7 2-6 2s-6-.9-6-2zM4 14h12v26c0 1.1-2.7 2-6 2s-6-.9-6-2zM18 23h12v17c0 1.1-2.7 2-6 2s-6-.9-6-2z"/></g><g fill="#FFC107"><ellipse cx="38" cy="8" rx="6" ry="2"/><ellipse cx="10" cy="14" rx="6" ry="2"/><ellipse cx="24" cy="23" rx="6" ry="2"/></g><g fill="none" stroke="#FFC107" stroke-width="2"><path d="M32 11c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2M4 17c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2M18 26c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2m-12 3c0 1.1 2.7 2 6 2s6-.9 6-2"/></g>', 48, 48);
|
||||
export const flatColorLinkIcon = icon('<path fill="#1976D2" d="M38 13h-3c-5.5 0-10 4.5-10 10s4.5 10 10 10h3c5.5 0 10-4.5 10-10s-4.5-10-10-10m0 16h-3c-3.3 0-6-2.7-6-6s2.7-6 6-6h3c3.3 0 6 2.7 6 6s-2.7 6-6 6M13 13h-3C4.5 13 0 17.5 0 23s4.5 10 10 10h3c5.5 0 10-4.5 10-10s-4.5-10-10-10m0 16h-3c-3.3 0-6-2.7-6-6s2.7-6 6-6h3c3.3 0 6 2.7 6 6s-2.7 6-6 6"/><path fill="#42A5F5" d="M33 21H15c-1.1 0-2 .9-2 2s.9 2 2 2h18c1.1 0 2-.9 2-2s-.9-2-2-2"/>', 48, 48);
|
||||
export const flatColorAboutIcon = icon('<path fill="#2196F3" d="M37 40H11l-6 6V12c0-3.3 2.7-6 6-6h26c3.3 0 6 2.7 6 6v22c0 3.3-2.7 6-6 6"/><g fill="#fff"><path d="M22 20h4v11h-4z"/><circle cx="24" cy="15" r="2"/></g>', 48, 48);
|
||||
export const flatColorOrganizationIcon = icon('<path fill="#90CAF9" d="M42 42H6V10c0-2.2 1.8-4 4-4h28c2.2 0 4 1.8 4 4z"/><path fill="#64B5F6" d="M6 42h36v2H6z"/><path fill="#1565C0" d="M31 27h6v5h-6zm-10 0h6v5h-6zm-10 0h6v5h-6zm20 8h6v5h-6zm-20 0h6v5h-6zm20-16h6v5h-6zm-10 0h6v5h-6zm-10 0h6v5h-6zm20-8h6v5h-6zm-10 0h6v5h-6zm-10 0h6v5h-6zm10 24h6v9h-6z"/>', 48, 48);
|
||||
export const flatColorBarChartIcon = icon('<path fill="#00BCD4" d="M19 22h10v20H19zM6 12h10v30H6zm26-6h10v36H32z"/>', 48, 48);
|
||||
export const flatColorComboChartIcon = icon('<path fill="#00BCD4" d="M37 18h6v24h-6zm-8 8h6v16h-6zm-8-4h6v20h-6zm-8 10h6v10h-6zm-8-4h6v14H5z"/><g fill="#3F51B5"><circle cx="8" cy="16" r="3"/><circle cx="16" cy="18" r="3"/><circle cx="24" cy="11" r="3"/><circle cx="32" cy="13" r="3"/><circle cx="40" cy="9" r="3"/><path d="m39.1 7.2l-7.3 3.7l-8.3-2.1l-8 7l-7-1.7l-1 3.8l9 2.3l8-7l7.7 1.9l8.7-4.3z"/></g>', 48, 48);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { App } from "../App";
|
||||
import { useI18n } from "./store";
|
||||
|
||||
export function I18nRoot() {
|
||||
useI18n();
|
||||
return <App />;
|
||||
}
|
||||
@@ -87,7 +87,7 @@
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 145,
|
||||
"line": 148,
|
||||
"column": 82
|
||||
}
|
||||
]
|
||||
@@ -111,7 +111,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 231,
|
||||
"line": 254,
|
||||
"column": 14
|
||||
}
|
||||
]
|
||||
@@ -163,7 +163,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 222,
|
||||
"line": 245,
|
||||
"column": 16
|
||||
}
|
||||
]
|
||||
@@ -312,7 +312,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 205,
|
||||
"line": 228,
|
||||
"column": 26
|
||||
}
|
||||
]
|
||||
@@ -595,7 +595,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 209,
|
||||
"line": 232,
|
||||
"column": 60
|
||||
}
|
||||
]
|
||||
@@ -621,17 +621,17 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 52,
|
||||
"line": 55,
|
||||
"column": 42
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 150,
|
||||
"line": 153,
|
||||
"column": 22
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 177,
|
||||
"line": 180,
|
||||
"column": 58
|
||||
}
|
||||
]
|
||||
@@ -707,6 +707,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"217cfe7db1e3d10a": {
|
||||
"source": "跟随系统",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 206,
|
||||
"column": 43
|
||||
}
|
||||
]
|
||||
},
|
||||
"22c6b4eb4caee6ae": {
|
||||
"source": "代理设置已保存",
|
||||
"kind": "text",
|
||||
@@ -714,7 +726,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 107,
|
||||
"line": 110,
|
||||
"column": 15
|
||||
}
|
||||
]
|
||||
@@ -747,6 +759,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"29fbbef32a6eb58b": {
|
||||
"source": "不再显示此广告",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 216,
|
||||
"column": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
"2a2773134a829016": {
|
||||
"source": "按历史 LLM 调用记录汇总,进行中的调用不计入。",
|
||||
"kind": "text",
|
||||
@@ -766,7 +790,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 115,
|
||||
"line": 114,
|
||||
"column": 105
|
||||
},
|
||||
{
|
||||
@@ -777,7 +801,12 @@
|
||||
{
|
||||
"file": "components/ui/Modal.tsx",
|
||||
"line": 7,
|
||||
"column": 92
|
||||
"column": 96
|
||||
},
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 217,
|
||||
"column": 19
|
||||
},
|
||||
{
|
||||
"file": "pages/CursorSettingsPage.tsx",
|
||||
@@ -786,12 +815,12 @@
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 141,
|
||||
"line": 144,
|
||||
"column": 81
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 224,
|
||||
"line": 247,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -908,6 +937,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"36f33adaf0942634": {
|
||||
"source": "确认",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 218,
|
||||
"column": 20
|
||||
}
|
||||
]
|
||||
},
|
||||
"378bb0eec39fa8a2": {
|
||||
"source": "最后一页",
|
||||
"kind": "text",
|
||||
@@ -1012,7 +1053,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 216,
|
||||
"line": 239,
|
||||
"column": 14
|
||||
}
|
||||
]
|
||||
@@ -1067,6 +1108,23 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"3d13868593ae4eeb": {
|
||||
"source": "界面语言",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 198,
|
||||
"column": 22
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 204,
|
||||
"column": 26
|
||||
}
|
||||
]
|
||||
},
|
||||
"3da0bf1610ff5db5": {
|
||||
"source": "推荐内容",
|
||||
"kind": "text",
|
||||
@@ -1074,7 +1132,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/ads/AdMenu.tsx",
|
||||
"line": 15,
|
||||
"line": 18,
|
||||
"column": 56
|
||||
}
|
||||
]
|
||||
@@ -1091,6 +1149,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"4125fc7ba333524c": {
|
||||
"source": "默认亮色",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 223,
|
||||
"column": 52
|
||||
}
|
||||
]
|
||||
},
|
||||
"42655ed8e4108ae2": {
|
||||
"source": "输入(非缓存)",
|
||||
"kind": "text",
|
||||
@@ -1149,6 +1219,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"4510b63fdcf7451f": {
|
||||
"source": "Cursor 助手 v0.1.0",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppHeader.tsx",
|
||||
"line": 20,
|
||||
"column": 18
|
||||
}
|
||||
]
|
||||
},
|
||||
"470049252e54de6a": {
|
||||
"source": "成功占比:{rate}",
|
||||
"kind": "template",
|
||||
@@ -1215,6 +1297,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"493e097fac6bd217": {
|
||||
"source": "上游配置",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 71,
|
||||
"column": 48
|
||||
}
|
||||
]
|
||||
},
|
||||
"497c85690c4cc0fc": {
|
||||
"source": "暂无数据",
|
||||
"kind": "text",
|
||||
@@ -1239,8 +1333,13 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/ads/AdMenu.tsx",
|
||||
"line": 25,
|
||||
"column": 97
|
||||
"line": 28,
|
||||
"column": 99
|
||||
},
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 197,
|
||||
"column": 84
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1251,7 +1350,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 90,
|
||||
"line": 89,
|
||||
"column": 9
|
||||
},
|
||||
{
|
||||
@@ -1266,6 +1365,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"51d04bc3d286f018": {
|
||||
"source": "近1自然日",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 35,
|
||||
"column": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
"5228358a6db59fe7": {
|
||||
"source": "如:现在、2026-08-23 18:00",
|
||||
"kind": "text",
|
||||
@@ -1273,7 +1384,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 111,
|
||||
"line": 110,
|
||||
"column": 70
|
||||
}
|
||||
]
|
||||
@@ -1285,7 +1396,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 139,
|
||||
"line": 142,
|
||||
"column": 26
|
||||
}
|
||||
]
|
||||
@@ -1384,6 +1495,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"5f9acfb945229062": {
|
||||
"source": "你确认不想再看到此广告吗?",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 222,
|
||||
"column": 11
|
||||
}
|
||||
]
|
||||
},
|
||||
"5fd2ec5a6e9b654c": {
|
||||
"source": "合计:{cost}",
|
||||
"kind": "template",
|
||||
@@ -1479,7 +1602,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 116,
|
||||
"line": 115,
|
||||
"column": 109
|
||||
}
|
||||
]
|
||||
@@ -1491,7 +1614,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "api.ts",
|
||||
"line": 255,
|
||||
"line": 265,
|
||||
"column": 43
|
||||
}
|
||||
]
|
||||
@@ -1575,6 +1698,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"68ea5dd4d7af20e6": {
|
||||
"source": "系统设置",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 74,
|
||||
"column": 47
|
||||
}
|
||||
]
|
||||
},
|
||||
"6a9906c79f26c0ba": {
|
||||
"source": "开始时间",
|
||||
"kind": "text",
|
||||
@@ -1582,7 +1717,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 110,
|
||||
"line": 109,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -1693,7 +1828,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 129,
|
||||
"line": 132,
|
||||
"column": 16
|
||||
}
|
||||
]
|
||||
@@ -1717,7 +1852,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 192,
|
||||
"line": 215,
|
||||
"column": 26
|
||||
}
|
||||
]
|
||||
@@ -1741,7 +1876,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "api.ts",
|
||||
"line": 250,
|
||||
"line": 260,
|
||||
"column": 43
|
||||
}
|
||||
]
|
||||
@@ -1765,7 +1900,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 225,
|
||||
"line": 248,
|
||||
"column": 22
|
||||
}
|
||||
]
|
||||
@@ -1808,7 +1943,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 82,
|
||||
"line": 85,
|
||||
"column": 15
|
||||
}
|
||||
]
|
||||
@@ -1868,7 +2003,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "api.ts",
|
||||
"line": 213,
|
||||
"line": 214,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -2016,6 +2151,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"8df48894086d6fbd": {
|
||||
"source": "原因(可选)",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 224,
|
||||
"column": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
"90800c48a1dd0655": {
|
||||
"source": "{label} 必须是 JSON 对象",
|
||||
"kind": "template",
|
||||
@@ -2047,6 +2194,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"91aaf184cfc17ffd": {
|
||||
"source": "数据概览",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 69,
|
||||
"column": 39
|
||||
}
|
||||
]
|
||||
},
|
||||
"91ee087c3d4f3220": {
|
||||
"source": "支持完整 HTTP(S) 地址或以 / 开头、与上游地址组合的相对路径。",
|
||||
"kind": "text",
|
||||
@@ -2193,11 +2352,35 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 110,
|
||||
"line": 109,
|
||||
"column": 70
|
||||
}
|
||||
]
|
||||
},
|
||||
"9f6fee1aba17a565": {
|
||||
"source": "语言",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 195,
|
||||
"column": 26
|
||||
}
|
||||
]
|
||||
},
|
||||
"9fb48101d237ff96": {
|
||||
"source": "近一周",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 37,
|
||||
"column": 29
|
||||
}
|
||||
]
|
||||
},
|
||||
"a026f37e613cf48b": {
|
||||
"source": "输出 Token",
|
||||
"kind": "text",
|
||||
@@ -2217,7 +2400,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 45,
|
||||
"line": 48,
|
||||
"column": 34
|
||||
}
|
||||
]
|
||||
@@ -2259,11 +2442,11 @@
|
||||
{
|
||||
"file": "components/ui/Modal.tsx",
|
||||
"line": 7,
|
||||
"column": 115
|
||||
"column": 119
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 142,
|
||||
"line": 145,
|
||||
"column": 133
|
||||
}
|
||||
]
|
||||
@@ -2372,7 +2555,7 @@
|
||||
},
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 113,
|
||||
"line": 112,
|
||||
"column": 61
|
||||
},
|
||||
{
|
||||
@@ -2487,22 +2670,22 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 92,
|
||||
"line": 91,
|
||||
"column": 28
|
||||
},
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 92,
|
||||
"line": 91,
|
||||
"column": 89
|
||||
},
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 140,
|
||||
"line": 237,
|
||||
"column": 64
|
||||
},
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 140,
|
||||
"line": 237,
|
||||
"column": 125
|
||||
}
|
||||
]
|
||||
@@ -2593,7 +2776,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 208,
|
||||
"line": 231,
|
||||
"column": 22
|
||||
}
|
||||
]
|
||||
@@ -2610,6 +2793,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"b8c9b486c83b5778": {
|
||||
"source": "不再显示广告",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/ads/AdMenu.tsx",
|
||||
"line": 44,
|
||||
"column": 24
|
||||
}
|
||||
]
|
||||
},
|
||||
"b9670c85a4ab939e": {
|
||||
"source": "路由",
|
||||
"kind": "text",
|
||||
@@ -2678,6 +2873,30 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"bb7efdcb6af6e805": {
|
||||
"source": "默认暗色",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 223,
|
||||
"column": 40
|
||||
}
|
||||
]
|
||||
},
|
||||
"bda62ce1d5e4ace9": {
|
||||
"source": "可以告诉我们原因",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 229,
|
||||
"column": 24
|
||||
}
|
||||
]
|
||||
},
|
||||
"bf57afd709694b55": {
|
||||
"source": "概览时间范围",
|
||||
"kind": "text",
|
||||
@@ -2685,7 +2904,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 74,
|
||||
"line": 73,
|
||||
"column": 51
|
||||
}
|
||||
]
|
||||
@@ -2815,7 +3034,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 112,
|
||||
"line": 111,
|
||||
"column": 61
|
||||
}
|
||||
]
|
||||
@@ -2892,12 +3111,12 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 127,
|
||||
"line": 130,
|
||||
"column": 22
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 133,
|
||||
"line": 136,
|
||||
"column": 20
|
||||
}
|
||||
]
|
||||
@@ -2909,7 +3128,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 164,
|
||||
"line": 167,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -2926,6 +3145,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"d0bfccc77315d887": {
|
||||
"source": "近一个月",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 38,
|
||||
"column": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
"d15a909c3490a7e0": {
|
||||
"source": "端点类型",
|
||||
"kind": "text",
|
||||
@@ -3049,7 +3280,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 114,
|
||||
"line": 180,
|
||||
"column": 54
|
||||
}
|
||||
]
|
||||
@@ -3066,6 +3297,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"da7ae985487c38e6": {
|
||||
"source": "近1小时",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 34,
|
||||
"column": 29
|
||||
}
|
||||
]
|
||||
},
|
||||
"db0a1393f05aa4bd": {
|
||||
"source": "默认继承上游,可为当前模型单独修改。",
|
||||
"kind": "text",
|
||||
@@ -3121,7 +3364,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 239,
|
||||
"line": 262,
|
||||
"column": 30
|
||||
}
|
||||
]
|
||||
@@ -3227,6 +3470,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"e25bf3f419bb68f0": {
|
||||
"source": "调用详细",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 70,
|
||||
"column": 44
|
||||
}
|
||||
]
|
||||
},
|
||||
"e3fee05f688708b4": {
|
||||
"source": "LLM 调用",
|
||||
"kind": "text",
|
||||
@@ -3239,6 +3494,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"e5043c7a2b408271": {
|
||||
"source": "近10分钟",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 36,
|
||||
"column": 36
|
||||
}
|
||||
]
|
||||
},
|
||||
"e59ae97924d62f01": {
|
||||
"source": "第一页",
|
||||
"kind": "text",
|
||||
@@ -3263,7 +3530,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 58,
|
||||
"line": 61,
|
||||
"column": 17
|
||||
}
|
||||
]
|
||||
@@ -3316,7 +3583,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 124,
|
||||
"line": 127,
|
||||
"column": 26
|
||||
}
|
||||
]
|
||||
@@ -3391,6 +3658,11 @@
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 75,
|
||||
"column": 53
|
||||
},
|
||||
{
|
||||
"file": "pages/CursorSettingsPage.tsx",
|
||||
"line": 122,
|
||||
@@ -3417,7 +3689,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 183,
|
||||
"line": 186,
|
||||
"column": 16
|
||||
}
|
||||
]
|
||||
@@ -3509,7 +3781,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 111,
|
||||
"line": 110,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -3526,6 +3798,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"f78413c36d36f090": {
|
||||
"source": "默认跟随操作系统;不支持的系统语言使用英文。当前:{language}",
|
||||
"kind": "template",
|
||||
"placeholders": [
|
||||
"language"
|
||||
],
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 199,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
},
|
||||
"f85537d1fd2ef6f6": {
|
||||
"source": "自定义概览筛选",
|
||||
"kind": "text",
|
||||
@@ -3533,7 +3819,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 100,
|
||||
"line": 99,
|
||||
"column": 19
|
||||
}
|
||||
]
|
||||
@@ -3595,7 +3881,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 151,
|
||||
"line": 154,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -3692,6 +3978,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"fec45092945f8790": {
|
||||
"source": "使用教程",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "layouts/AppLayout.tsx",
|
||||
"line": 72,
|
||||
"column": 48
|
||||
}
|
||||
]
|
||||
},
|
||||
"ff509c9ba052a21c": {
|
||||
"source": "保存中…",
|
||||
"kind": "text",
|
||||
@@ -3704,7 +4002,7 @@
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 142,
|
||||
"line": 145,
|
||||
"column": 121
|
||||
}
|
||||
]
|
||||
@@ -3728,17 +4026,17 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 53,
|
||||
"line": 56,
|
||||
"column": 46
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 163,
|
||||
"line": 166,
|
||||
"column": 22
|
||||
},
|
||||
{
|
||||
"file": "pages/SettingsPage.tsx",
|
||||
"line": 178,
|
||||
"line": 181,
|
||||
"column": 58
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,264 +1,287 @@
|
||||
{
|
||||
"00929f23850e4ff0": "",
|
||||
"01f3e69a5a9b2c9b": "",
|
||||
"025b70bde54ed1ef": "",
|
||||
"028a4de61bff743d": "",
|
||||
"03ff62ab4b818492": "",
|
||||
"051836569928a9f9": "",
|
||||
"0580e0a99a6f1afc": "",
|
||||
"05912a17829faacc": "",
|
||||
"076832c1b2de22c3": "",
|
||||
"07879e064ae16542": "",
|
||||
"07ec86e0f1d44f91": "",
|
||||
"08d742566ffc9fa3": "",
|
||||
"099008ea7a42ebd1": "",
|
||||
"09ebc2643631ba25": "",
|
||||
"0a5f9a892960a00a": "",
|
||||
"0b96da34f6fbdd3b": "",
|
||||
"0bd20614caf56647": "",
|
||||
"0c2fee621d406ea7": "",
|
||||
"0c70665b6eb65f1a": "",
|
||||
"0c72229b7db0e1a9": "",
|
||||
"0d5e2bdb15579fc4": "",
|
||||
"0e41f8e3d59ec47b": "",
|
||||
"0ec1e85b0c3cfa65": "",
|
||||
"124be3f86f197802": "",
|
||||
"12ae77e6202d063e": "",
|
||||
"12b86e89701b0241": "",
|
||||
"13a9ac7a68c5fd96": "",
|
||||
"13b61c5f697b6700": "",
|
||||
"146da2e2a991493e": "",
|
||||
"15d76f1c211cc140": "",
|
||||
"168e845a86bc3703": "",
|
||||
"16aa4ae1427fccce": "",
|
||||
"16d0d7e2b332af72": "",
|
||||
"1813d362a82fd437": "",
|
||||
"18d25065b3f7db39": "",
|
||||
"00929f23850e4ff0": "Successful calls: {count}",
|
||||
"01f3e69a5a9b2c9b": "The key required to access the model service.",
|
||||
"025b70bde54ed1ef": "Whether Cursor can select and use this model.",
|
||||
"028a4de61bff743d": "Regular input: {tokens} × ${price}/1M = {cost}",
|
||||
"03ff62ab4b818492": "Cache write: {tokens} × ${price}/1M = {cost}",
|
||||
"051836569928a9f9": "Edit",
|
||||
"0580e0a99a6f1afc": "Artifacts",
|
||||
"05912a17829faacc": "All call records and detailed traces will be deleted. Providers, models, CA, and application settings are unaffected. This action cannot be undone.",
|
||||
"076832c1b2de22c3": "Cache write: {tokens}",
|
||||
"07879e064ae16542": "Estimated output: {tokens}",
|
||||
"07ec86e0f1d44f91": "Created",
|
||||
"08d742566ffc9fa3": "Clear all statistics?",
|
||||
"099008ea7a42ebd1": "All custom Header values must be strings",
|
||||
"09ebc2643631ba25": "Estimated value",
|
||||
"0a5f9a892960a00a": "Updated",
|
||||
"0b96da34f6fbdd3b": "Cache read: {tokens} × ${price}/1M = {cost}",
|
||||
"0bd20614caf56647": "Select or enter multiple models. When adding in bulk, each display name defaults to its model name.",
|
||||
"0c2fee621d406ea7": "Select an existing provider or create a new one.",
|
||||
"0c70665b6eb65f1a": "No",
|
||||
"0c72229b7db0e1a9": "Model output",
|
||||
"0d5e2bdb15579fc4": "Messages",
|
||||
"0e41f8e3d59ec47b": "Storage management",
|
||||
"0ec1e85b0c3cfa65": "Call details",
|
||||
"124be3f86f197802": "Token usage",
|
||||
"12ae77e6202d063e": "Custom Headers",
|
||||
"12b86e89701b0241": "Enter a model name and press Enter to add it",
|
||||
"13a9ac7a68c5fd96": "The CA is stored only on this device and is used to securely inspect Cursor HTTPS requests.",
|
||||
"13b61c5f697b6700": "Cache hit rate",
|
||||
"146da2e2a991493e": "Fetching…",
|
||||
"15d76f1c211cc140": "Leave blank to keep the current API Key.",
|
||||
"168e845a86bc3703": "Add model",
|
||||
"16aa4ae1427fccce": "Custom Headers JSON",
|
||||
"16d0d7e2b332af72": "Total calls: {count}",
|
||||
"1813d362a82fd437": "Maximize window",
|
||||
"18d25065b3f7db39": "The full request URL is required",
|
||||
"19658d9fa9aa8de4": "Installing…",
|
||||
"1a3f0617d6de8e52": "",
|
||||
"1ae6b0a0f8266382": "",
|
||||
"1b5932b8946d2d68": "",
|
||||
"1b7d5b1a9315fc64": "",
|
||||
"1c6926877b1bbfb2": "",
|
||||
"1cb7e646ac883fb2": "",
|
||||
"1cdde778cfe6a130": "",
|
||||
"1cef73a53d7ad0f2": "",
|
||||
"1f9b46e61efcccb5": "",
|
||||
"21296ab18ad9af25": "",
|
||||
"22c6b4eb4caee6ae": "",
|
||||
"1a3f0617d6de8e52": "Username",
|
||||
"1ae6b0a0f8266382": "Close window",
|
||||
"1b5932b8946d2d68": "Delete model",
|
||||
"1b7d5b1a9315fc64": "Calculating…",
|
||||
"1c6926877b1bbfb2": "Total requests: {tokens}",
|
||||
"1cb7e646ac883fb2": "Proxy port",
|
||||
"1cdde778cfe6a130": "Formula: cache read / (cache read + non-cached input)",
|
||||
"1cef73a53d7ad0f2": "For example: OpenAI",
|
||||
"1f9b46e61efcccb5": "Filter: {label}, {summary}",
|
||||
"21296ab18ad9af25": "Extra parameters JSON",
|
||||
"217cfe7db1e3d10a": "Use system language",
|
||||
"22c6b4eb4caee6ae": "Proxy settings saved",
|
||||
"23e49479e15e6770": "Version {version} is available",
|
||||
"29585d7193539200": "Current version {version}",
|
||||
"2a2773134a829016": "",
|
||||
"2cd0f3be8738a86c": "",
|
||||
"2d30c2a98ebb5278": "",
|
||||
"2eb2bf7c6597ab9a": "",
|
||||
"2f1b67cdaa23351a": "",
|
||||
"2f4a361f878176d1": "",
|
||||
"2f5f1d6fbfb061ed": "",
|
||||
"2f6416a2c424856b": "",
|
||||
"2f7dec3be28d7597": "",
|
||||
"2f9daa828907b93f": "",
|
||||
"378bb0eec39fa8a2": "",
|
||||
"37cb98ff4d5dcfcc": "",
|
||||
"393df9bb13ea4900": "",
|
||||
"393e1241552b1870": "",
|
||||
"398f8e6c6f0a0b97": "",
|
||||
"39f52eee100131d7": "",
|
||||
"3a0fb74abe2460b6": "",
|
||||
"3a3f595df70ec8ff": "",
|
||||
"3a5040b68abf75f9": "",
|
||||
"3bd5b3f538e3270a": "",
|
||||
"3c94b4c75940c178": "",
|
||||
"3cfae5728b92b334": "",
|
||||
"3da0bf1610ff5db5": "",
|
||||
"3fd47edce45b3603": "",
|
||||
"42655ed8e4108ae2": "",
|
||||
"42a1d9e5b037c210": "",
|
||||
"42aa8e01e98c0d8c": "",
|
||||
"43cb41d62de2d179": "",
|
||||
"470049252e54de6a": "",
|
||||
"48b970b568a7f8f9": "",
|
||||
"48d8db17bae06246": "",
|
||||
"29fbbef32a6eb58b": "Do not show this ad again",
|
||||
"2a2773134a829016": "Aggregated from historical LLM calls; in-progress calls are excluded.",
|
||||
"2cd0f3be8738a86c": "Cancel",
|
||||
"2d30c2a98ebb5278": "Current: {rate}",
|
||||
"2eb2bf7c6597ab9a": "Detailed records",
|
||||
"2f1b67cdaa23351a": "Custom full request URL",
|
||||
"2f4a361f878176d1": "{label} must be valid JSON",
|
||||
"2f5f1d6fbfb061ed": "Not set",
|
||||
"2f6416a2c424856b": "Final request URL",
|
||||
"2f7dec3be28d7597": "{count} selected",
|
||||
"2f9daa828907b93f": "Delete",
|
||||
"36f33adaf0942634": "Confirm",
|
||||
"378bb0eec39fa8a2": "Last page",
|
||||
"37cb98ff4d5dcfcc": "Successful {successful} / failed {failed}",
|
||||
"393df9bb13ea4900": "Hit",
|
||||
"393e1241552b1870": "Request",
|
||||
"398f8e6c6f0a0b97": "Continue selecting or typing",
|
||||
"39f52eee100131d7": "Cached input",
|
||||
"3a0fb74abe2460b6": "Chunks",
|
||||
"3a3f595df70ec8ff": "Clear storage",
|
||||
"3a5040b68abf75f9": "Select all",
|
||||
"3bd5b3f538e3270a": "New provider",
|
||||
"3c94b4c75940c178": "Input Tokens",
|
||||
"3cfae5728b92b334": "Token usage: {tokens}",
|
||||
"3d13868593ae4eeb": "Display language",
|
||||
"3da0bf1610ff5db5": "Recommended",
|
||||
"3fd47edce45b3603": "Close",
|
||||
"4125fc7ba333524c": "Default light",
|
||||
"42655ed8e4108ae2": "Input (non-cached)",
|
||||
"42a1d9e5b037c210": "Bytes",
|
||||
"42aa8e01e98c0d8c": "Total duration",
|
||||
"43cb41d62de2d179": "Proxy requires authentication",
|
||||
"4510b63fdcf7451f": "Cursor Assistant v0.1.0",
|
||||
"470049252e54de6a": "Success rate: {rate}",
|
||||
"48b970b568a7f8f9": "Proxy settings",
|
||||
"48d8db17bae06246": "{count} total",
|
||||
"492042ed1fdc29ed": "Version {version} is ready to install",
|
||||
"4927a53bcc886afb": "",
|
||||
"497c85690c4cc0fc": "",
|
||||
"4d0680f9efaef147": "",
|
||||
"4eafa9e925b30bcd": "",
|
||||
"5228358a6db59fe7": "",
|
||||
"54644705e9c61009": "",
|
||||
"54c53e5fe791d1f3": "",
|
||||
"550eddc3c7fefa99": "",
|
||||
"555737734a6371e6": "",
|
||||
"56432ba297009bdc": "",
|
||||
"5ae715656ffbc35d": "",
|
||||
"5c55a67935af8f45": "",
|
||||
"4927a53bcc886afb": "Loading…",
|
||||
"493e097fac6bd217": "Providers",
|
||||
"497c85690c4cc0fc": "No data",
|
||||
"4d0680f9efaef147": "Unread",
|
||||
"4eafa9e925b30bcd": "Custom",
|
||||
"51d04bc3d286f018": "Last calendar day",
|
||||
"5228358a6db59fe7": "For example: now, 2026-08-23 18:00",
|
||||
"54644705e9c61009": "Port settings",
|
||||
"54c53e5fe791d1f3": "Initialize CA",
|
||||
"550eddc3c7fefa99": "Sponsored",
|
||||
"555737734a6371e6": "Delete provider",
|
||||
"56432ba297009bdc": "Initialize the CA first",
|
||||
"5ae715656ffbc35d": "Merge into the request body for every model from this provider.",
|
||||
"5c55a67935af8f45": "All",
|
||||
"5f8d556a9c47da3c": "Launch at login disabled",
|
||||
"5fd2ec5a6e9b654c": "",
|
||||
"6078a681a306930d": "",
|
||||
"61a4c7bac12dc125": "",
|
||||
"61c7d1f758b647d5": "",
|
||||
"621f63a5f08384ac": "",
|
||||
"5f9acfb945229062": "Are you sure you no longer want to see this ad?",
|
||||
"5fd2ec5a6e9b654c": "Total: {cost}",
|
||||
"6078a681a306930d": "Cache write",
|
||||
"61a4c7bac12dc125": "Failed calls: {count}",
|
||||
"61c7d1f758b647d5": "Call information",
|
||||
"621f63a5f08384ac": "Cache read: {tokens}",
|
||||
"6320b4a8722a851f": "Status",
|
||||
"63c73c4730f4473e": "",
|
||||
"6478a5f1218c484e": "",
|
||||
"63c73c4730f4473e": "Apply",
|
||||
"6478a5f1218c484e": "Use the desktop app to copy to the system clipboard",
|
||||
"651f274470153a05": "Software updates",
|
||||
"653b123c956d3bcb": "Calls",
|
||||
"656ab25e264cc4e4": "",
|
||||
"65a6318e07ec1e07": "",
|
||||
"65cb9a7b4f620b6b": "",
|
||||
"68152165b3348852": "",
|
||||
"6a9906c79f26c0ba": "",
|
||||
"6ae80538c2b2572d": "",
|
||||
"6d1876364ac6457d": "",
|
||||
"656ab25e264cc4e4": "No models are available to Cursor yet",
|
||||
"65a6318e07ec1e07": "Tools",
|
||||
"65cb9a7b4f620b6b": "Prompt {tokens}",
|
||||
"68152165b3348852": "Select the request protocol used by this provider.",
|
||||
"68ea5dd4d7af20e6": "System settings",
|
||||
"6a9906c79f26c0ba": "Start time",
|
||||
"6ae80538c2b2572d": "Minimize window",
|
||||
"6d1876364ac6457d": "Proxy mode",
|
||||
"6e86570183c3cdd0": "You're up to date",
|
||||
"7005693f4f050bce": "",
|
||||
"71eb2c6fce991a29": "",
|
||||
"722e156c20650a3f": "",
|
||||
"7005693f4f050bce": "Cache I/O {cost}",
|
||||
"71eb2c6fce991a29": "Add provider",
|
||||
"722e156c20650a3f": "Use macOS or Windows.",
|
||||
"7392e20d61abaa07": "Also store complete requests and streamed responses; by default only timing, status, and usage are stored.",
|
||||
"75844712d53a24de": "",
|
||||
"75844712d53a24de": "Values must be strings; when editing, null keeps the original value of the corresponding sensitive Header.",
|
||||
"788db1cfec2a3db5": "Theme",
|
||||
"7995087e5a3dfe66": "",
|
||||
"7a2229f6a6d330a5": "",
|
||||
"7a3cec4ca715de80": "",
|
||||
"7ba2d6728fe2531b": "",
|
||||
"7995087e5a3dfe66": "Restore window",
|
||||
"7a2229f6a6d330a5": "Open a terminal from the desktop app to install the CA",
|
||||
"7a3cec4ca715de80": "Call statistics",
|
||||
"7ba2d6728fe2531b": "Confirm clear",
|
||||
"7d9f043f8f7ab45c": "Version {version} is available in Settings",
|
||||
"7e1845870b528392": "",
|
||||
"7e1f06318e80c3af": "",
|
||||
"7e9ab9ada2cbf2cb": "",
|
||||
"7f3c8312816fe26a": "",
|
||||
"7e1845870b528392": "Enable model",
|
||||
"7e1f06318e80c3af": "Statistics cleared",
|
||||
"7e9ab9ada2cbf2cb": "Name and Base URL are required",
|
||||
"7f3c8312816fe26a": "Refreshing…",
|
||||
"7f68ebad19ba6bcd": "Check for updates",
|
||||
"80a57e03f0717f91": "",
|
||||
"811a3b22a5a7f2d5": "",
|
||||
"83fcfb4c1f2c1641": "",
|
||||
"80a57e03f0717f91": "Not configured",
|
||||
"811a3b22a5a7f2d5": "Unable to connect to the local management service",
|
||||
"83fcfb4c1f2c1641": "Fetch models",
|
||||
"842b9f11cdd96bda": "Launch at login",
|
||||
"84924374710e03bd": "",
|
||||
"8716e1344b0daddb": "",
|
||||
"878a8ab176429a86": "",
|
||||
"883cc47637fe70f3": "",
|
||||
"8a8542f6964852dc": "",
|
||||
"84924374710e03bd": "Base URL must be a valid URL",
|
||||
"8716e1344b0daddb": "Cursor official",
|
||||
"878a8ab176429a86": "View instructions",
|
||||
"883cc47637fe70f3": "Custom request headers appended to every request for this provider. Values must be strings.",
|
||||
"8a8542f6964852dc": "Next page",
|
||||
"8b6ff498515bcc2f": "Time",
|
||||
"8d0c47eb9eac2d34": "",
|
||||
"90800c48a1dd0655": "",
|
||||
"919cb0ce0c8db4e7": "",
|
||||
"91ee087c3d4f3220": "",
|
||||
"940a168911ade998": "",
|
||||
"946b3ffc02f026c0": "",
|
||||
"94803f35c825e47a": "",
|
||||
"94df1e7f04815daf": "",
|
||||
"95f76d30c25d5eda": "",
|
||||
"9850ed41a5bfbb0c": "",
|
||||
"997ec8201c2adeda": "",
|
||||
"9a026819dd1af5c5": "",
|
||||
"9ac0ac940982895d": "",
|
||||
"9c41b3a9e12ac994": "",
|
||||
"9e46da6923836182": "",
|
||||
"a026f37e613cf48b": "",
|
||||
"a03a1a0cb35414f8": "",
|
||||
"8d0c47eb9eac2d34": "Call type",
|
||||
"8df48894086d6fbd": "Reason (optional)",
|
||||
"90800c48a1dd0655": "{label} must be a JSON object",
|
||||
"919cb0ce0c8db4e7": "Leave blank to keep the current password",
|
||||
"91aaf184cfc17ffd": "Overview",
|
||||
"91ee087c3d4f3220": "Supports a complete HTTP(S) URL or a relative path beginning with /, combined with the provider URL.",
|
||||
"940a168911ade998": "Items per page",
|
||||
"946b3ffc02f026c0": "Delete this model?",
|
||||
"94803f35c825e47a": "Full request URL",
|
||||
"94df1e7f04815daf": "Used only for display; does not change the model name sent to the provider.",
|
||||
"95f76d30c25d5eda": "CA installation is not supported on this system",
|
||||
"9850ed41a5bfbb0c": "{count} selected",
|
||||
"997ec8201c2adeda": "Open terminal to install CA",
|
||||
"9a026819dd1af5c5": "Enter a model identifier directly or select one returned by the current provider.",
|
||||
"9ac0ac940982895d": "Select provider",
|
||||
"9c41b3a9e12ac994": "Reasoning effort",
|
||||
"9e46da6923836182": "For example: 2026-08-23 09:00, 1 hour ago",
|
||||
"9f6fee1aba17a565": "Language",
|
||||
"9fb48101d237ff96": "Last week",
|
||||
"a026f37e613cf48b": "Output Tokens",
|
||||
"a03a1a0cb35414f8": " must be an integer from 0 to 65535",
|
||||
"a1a42cd9b16e2162": "Application",
|
||||
"a2901cd2743c1921": "",
|
||||
"a3030bf8f16dc63c": "",
|
||||
"a363743025795ec7": "",
|
||||
"a3ab741ceb188e9e": "",
|
||||
"a49ffd73bc85333d": "",
|
||||
"a621ab606db2a11f": "",
|
||||
"a748cc074f78de00": "",
|
||||
"a98585871c5313ff": "",
|
||||
"a9ab292ea9feecdc": "",
|
||||
"aa678e13bda90e3c": "",
|
||||
"a2901cd2743c1921": "The API root URL for the model service. Changing it also updates the routing identity of this provider's models.",
|
||||
"a3030bf8f16dc63c": "Save",
|
||||
"a363743025795ec7": "I've initialized it — refresh",
|
||||
"a3ab741ceb188e9e": "Request content was not recorded. Enable detailed records and try again.",
|
||||
"a49ffd73bc85333d": "Average",
|
||||
"a621ab606db2a11f": "Password",
|
||||
"a748cc074f78de00": "View details",
|
||||
"a98585871c5313ff": "Display name",
|
||||
"a9ab292ea9feecdc": "Provider",
|
||||
"aa678e13bda90e3c": "Custom Header values must be strings or null",
|
||||
"ab2f31f30acf7fda": "Protocol",
|
||||
"ab9084a640fbb864": "",
|
||||
"ab9084a640fbb864": "Deselect all",
|
||||
"abecab6701177721": "Launch at login enabled",
|
||||
"ac69f68b7010ec79": "Download and install",
|
||||
"ad6a60ee93d3ba3e": "",
|
||||
"ae2d0b7f79cea4a3": "",
|
||||
"ad6a60ee93d3ba3e": "Loading call details…",
|
||||
"ae2d0b7f79cea4a3": "Model output: {tokens} × ${price}/1M = {cost}",
|
||||
"aee88743413144a2": "Refresh",
|
||||
"b4411558b932266f": "",
|
||||
"b502b1d414664337": "",
|
||||
"b5141d3d19e9a048": "",
|
||||
"b710ec36ad312918": "",
|
||||
"b75a46aad3e7c132": "",
|
||||
"b79354009c614ae9": "",
|
||||
"b89a0e4584f27ab5": "",
|
||||
"b9670c85a4ab939e": "",
|
||||
"b97ad406809572e1": "",
|
||||
"b9af2de88d903be7": "",
|
||||
"bb2b7736433ae867": "",
|
||||
"bf57afd709694b55": "",
|
||||
"bf84379b75735037": "",
|
||||
"bfc01caf9fe0c841": "",
|
||||
"c1e98892a77f7a19": "",
|
||||
"c3760858cdb6d9f4": "",
|
||||
"c62a58459251b02c": "",
|
||||
"c7ea2c9bc43134bd": "",
|
||||
"c8df3c14a003bfcd": "",
|
||||
"b4411558b932266f": "Provider type",
|
||||
"b502b1d414664337": "Prompt: {tokens}",
|
||||
"b5141d3d19e9a048": "Yes",
|
||||
"b710ec36ad312918": "The model service API root URL, for example https://api.openai.com/v1.",
|
||||
"b75a46aad3e7c132": "Non-cached input: {tokens}",
|
||||
"b79354009c614ae9": "Statistics",
|
||||
"b89a0e4584f27ab5": "Open terminal",
|
||||
"b8c9b486c83b5778": "Hide ad",
|
||||
"b9670c85a4ab939e": "Route",
|
||||
"b97ad406809572e1": "Enable reasoning",
|
||||
"b9af2de88d903be7": "Proxy address",
|
||||
"bb2b7736433ae867": "Cursor tracing",
|
||||
"bb7efdcb6af6e805": "Default dark",
|
||||
"bda62ce1d5e4ace9": "Tell us why",
|
||||
"bf57afd709694b55": "Overview time range",
|
||||
"bf84379b75735037": "Edit provider",
|
||||
"bfc01caf9fe0c841": "Cache hit rate {rate}",
|
||||
"c1e98892a77f7a19": "{count} per page",
|
||||
"c3760858cdb6d9f4": "Request body",
|
||||
"c62a58459251b02c": "Image generation",
|
||||
"c7ea2c9bc43134bd": "Edit model",
|
||||
"c8df3c14a003bfcd": "Unable to load call details",
|
||||
"c98e118e0a43f078": "Model",
|
||||
"c9b9ae7a61444ab7": "",
|
||||
"c9d146d006993cc1": "",
|
||||
"cb2f1709f983d2f4": "",
|
||||
"cea1aafe9416de7b": "",
|
||||
"c9b9ae7a61444ab7": "Previous page",
|
||||
"c9d146d006993cc1": "Cache statistics policy: default ({rate})",
|
||||
"cb2f1709f983d2f4": "Model name",
|
||||
"cea1aafe9416de7b": "Request headers",
|
||||
"cfae1a14d2120c57": "Detailed mode",
|
||||
"cfe085015632e9c8": "",
|
||||
"cfe999e50be8ef54": "",
|
||||
"d15a909c3490a7e0": "",
|
||||
"d2d648bd1c94b7f9": "",
|
||||
"d30d35c5ec4a888d": "",
|
||||
"cfe085015632e9c8": "The local management service port used by the desktop frontend. Enter 0 to select a random port at startup.",
|
||||
"cfe999e50be8ef54": "Whether the model declares image-generation support.",
|
||||
"d0bfccc77315d887": "Last month",
|
||||
"d15a909c3490a7e0": "Endpoint type",
|
||||
"d2d648bd1c94b7f9": "Authentication",
|
||||
"d30d35c5ec4a888d": "Select or enter at least one model",
|
||||
"d34335433395cd3a": "Start Cursor BYOK automatically after signing in.",
|
||||
"d3d21191f32e79a5": "",
|
||||
"d3d21191f32e79a5": "Processing…",
|
||||
"d44e9b3d3b31d37b": "Name",
|
||||
"d667c1616e31c4c4": "",
|
||||
"d86fa42c3848c680": "",
|
||||
"d8c47e9776cf1082": "",
|
||||
"da521d1c1cbd36af": "",
|
||||
"db0a1393f05aa4bd": "",
|
||||
"ddde16f8839da3ce": "",
|
||||
"de8184da1ef88d03": "",
|
||||
"dea7749c4cd77e6d": "",
|
||||
"df3d58c7d84b85f2": "",
|
||||
"df8b71c74d9b8478": "",
|
||||
"dfb802238b38fbd4": "",
|
||||
"e025f1ff71996425": "",
|
||||
"e1295adecbb77755": "",
|
||||
"e14115de7f7c5795": "",
|
||||
"e14a5eee9b0b0f9f": "",
|
||||
"e14f20d572c02611": "",
|
||||
"e18516550b9a5105": "",
|
||||
"d667c1616e31c4c4": "Leave blank to keep the current key",
|
||||
"d86fa42c3848c680": "Use system proxy",
|
||||
"d8c47e9776cf1082": "Main menu",
|
||||
"da521d1c1cbd36af": "Authorization is required to install the certificate",
|
||||
"da7ae985487c38e6": "Last hour",
|
||||
"db0a1393f05aa4bd": "Inherits the provider by default; override it for this model if needed.",
|
||||
"ddde16f8839da3ce": "Total requests",
|
||||
"de8184da1ef88d03": "Configured",
|
||||
"dea7749c4cd77e6d": "Total request Tokens include the prompt and model output.",
|
||||
"df3d58c7d84b85f2": "Settings",
|
||||
"df8b71c74d9b8478": "Response stream",
|
||||
"dfb802238b38fbd4": "Enabled",
|
||||
"e025f1ff71996425": "Set",
|
||||
"e1295adecbb77755": "Close ad",
|
||||
"e14115de7f7c5795": "Token usage over the past year",
|
||||
"e14a5eee9b0b0f9f": "Whether the model declares reasoning support.",
|
||||
"e14f20d572c02611": "Provider call sequence",
|
||||
"e18516550b9a5105": "No usage",
|
||||
"e25bf3f419bb68f0": "Call history",
|
||||
"e3fee05f688708b4": "LLM calls",
|
||||
"e59ae97924d62f01": "",
|
||||
"e5b9961a0d5242e3": "",
|
||||
"e6ca887f22288cde": "",
|
||||
"e77e3d58b0dcffaa": "",
|
||||
"e828bd3a0151edc2": "",
|
||||
"e5043c7a2b408271": "Last 10 minutes",
|
||||
"e59ae97924d62f01": "First page",
|
||||
"e5b9961a0d5242e3": "Port settings saved. Restart the app to apply them.",
|
||||
"e6ca887f22288cde": "Model ID and display name are required",
|
||||
"e77e3d58b0dcffaa": "Duration",
|
||||
"e828bd3a0151edc2": "The local CA must be trusted by the system",
|
||||
"ea26b760e930a7ca": "Call observability",
|
||||
"eb11e2df1d8ae387": "",
|
||||
"eb1be07f2ca6e506": "",
|
||||
"eb77492c9f76a7e1": "",
|
||||
"ed31fbb483ee1b0a": "",
|
||||
"edc70de18c6da1a6": "",
|
||||
"ede6d08f397df283": "",
|
||||
"ee239f3943293f87": "",
|
||||
"ee6b89a6a740a4c4": "",
|
||||
"f04c91a6bc3a6926": "",
|
||||
"f396118b8afd2a21": "",
|
||||
"f3a76d896853c1df": "",
|
||||
"f4694c46b1e19602": "",
|
||||
"f4dcb6a3ceb32247": "",
|
||||
"f4fa9f31ea2ae58d": "",
|
||||
"f50276449943286c": "",
|
||||
"f69273dbbebfb3a1": "",
|
||||
"f85537d1fd2ef6f6": "",
|
||||
"f95ea7f4c063eea7": "",
|
||||
"f9615d05d8e18595": "",
|
||||
"f9aa11dbb15ce647": "",
|
||||
"f9b55ca75425161b": "",
|
||||
"fa5b4b8a751c7d1b": "",
|
||||
"eb11e2df1d8ae387": "Provider URL",
|
||||
"eb1be07f2ca6e506": "Estimated using Claude Opus 4.7 pricing.",
|
||||
"eb77492c9f76a7e1": "The install command has been copied. Click “Open terminal”, paste it into the terminal, and enter your password when prompted.",
|
||||
"ed31fbb483ee1b0a": "Actions",
|
||||
"edc70de18c6da1a6": "Install local CA",
|
||||
"ede6d08f397df283": "Cursor settings",
|
||||
"ee239f3943293f87": "Sunday",
|
||||
"ee6b89a6a740a4c4": "If a port is occupied, a new random port is selected and saved automatically. Restart the app after changing these settings.",
|
||||
"f04c91a6bc3a6926": "Extra parameters",
|
||||
"f396118b8afd2a21": "Cursor interception is active. Add a provider and its model configuration to use BYOK models.",
|
||||
"f3a76d896853c1df": "Miss",
|
||||
"f4694c46b1e19602": "Final request type",
|
||||
"f4dcb6a3ceb32247": "Page {page} of {count}",
|
||||
"f4fa9f31ea2ae58d": "Token usage calendar for the past year",
|
||||
"f50276449943286c": "End time",
|
||||
"f69273dbbebfb3a1": "Format",
|
||||
"f78413c36d36f090": "Uses the operating system language by default; unsupported languages fall back to English. Current: {language}",
|
||||
"f85537d1fd2ef6f6": "Custom overview filters",
|
||||
"f95ea7f4c063eea7": "Disabled",
|
||||
"f9615d05d8e18595": "Clear {label} filter",
|
||||
"f9aa11dbb15ce647": "Saturday",
|
||||
"f9b55ca75425161b": "Response content was not recorded. Enable detailed records and try again.",
|
||||
"fa5b4b8a751c7d1b": "The local proxy port used by Cursor. Enter 0 to select a random port at startup.",
|
||||
"fb11aa6f29827095": "Checking…",
|
||||
"fbe8778fa8b9bab5": "",
|
||||
"fc3947ebe6b2177b": "",
|
||||
"fcd311fd8ad42462": "",
|
||||
"fd415f8e0097c832": "",
|
||||
"fdc4cabc370fa3f7": "",
|
||||
"fea405f9b01d1416": "",
|
||||
"ff509c9ba052a21c": "",
|
||||
"ff66b1c7e010bbc2": "",
|
||||
"ff673195f05fb056": ""
|
||||
"fbe8778fa8b9bab5": "Initialize the local CA first",
|
||||
"fc3947ebe6b2177b": "Default {defaultRate} / include creation {reuseRate}",
|
||||
"fcd311fd8ad42462": "Open model list",
|
||||
"fd415f8e0097c832": "Cache reads and writes are included in prompt-side statistics.",
|
||||
"fdc4cabc370fa3f7": "No options",
|
||||
"fea405f9b01d1416": "Summary",
|
||||
"fec45092945f8790": "User guide",
|
||||
"ff509c9ba052a21c": "Saving…",
|
||||
"ff66b1c7e010bbc2": "Paste the authorization command into the terminal and enter your password, then click the button below",
|
||||
"ff673195f05fb056": "Service port"
|
||||
}
|
||||
|
||||
@@ -45,9 +45,11 @@
|
||||
"1cef73a53d7ad0f2": "例如:OpenAI",
|
||||
"1f9b46e61efcccb5": "筛选项:{label},{summary}",
|
||||
"21296ab18ad9af25": "额外参数 JSON",
|
||||
"217cfe7db1e3d10a": "跟随系统",
|
||||
"22c6b4eb4caee6ae": "代理设置已保存",
|
||||
"23e49479e15e6770": "发现新版本 {version}",
|
||||
"29585d7193539200": "当前版本 {version}",
|
||||
"29fbbef32a6eb58b": "不再显示此广告",
|
||||
"2a2773134a829016": "按历史 LLM 调用记录汇总,进行中的调用不计入。",
|
||||
"2cd0f3be8738a86c": "取消",
|
||||
"2d30c2a98ebb5278": "当前:{rate}",
|
||||
@@ -58,6 +60,7 @@
|
||||
"2f6416a2c424856b": "最终请求地址",
|
||||
"2f7dec3be28d7597": "已选择 {count} 个",
|
||||
"2f9daa828907b93f": "删除",
|
||||
"36f33adaf0942634": "确认",
|
||||
"378bb0eec39fa8a2": "最后一页",
|
||||
"37cb98ff4d5dcfcc": "成功 {successful} / 异常 {failed}",
|
||||
"393df9bb13ea4900": "命中",
|
||||
@@ -70,20 +73,25 @@
|
||||
"3bd5b3f538e3270a": "新建上游",
|
||||
"3c94b4c75940c178": "输入 Token",
|
||||
"3cfae5728b92b334": "Token 用量:{tokens}",
|
||||
"3d13868593ae4eeb": "界面语言",
|
||||
"3da0bf1610ff5db5": "推荐内容",
|
||||
"3fd47edce45b3603": "关闭",
|
||||
"4125fc7ba333524c": "默认亮色",
|
||||
"42655ed8e4108ae2": "输入(非缓存)",
|
||||
"42a1d9e5b037c210": "字节数",
|
||||
"42aa8e01e98c0d8c": "总耗时",
|
||||
"43cb41d62de2d179": "代理需要认证",
|
||||
"4510b63fdcf7451f": "Cursor 助手 v0.1.0",
|
||||
"470049252e54de6a": "成功占比:{rate}",
|
||||
"48b970b568a7f8f9": "代理设置",
|
||||
"48d8db17bae06246": "共 {count} 条",
|
||||
"492042ed1fdc29ed": "版本 {version} 可以安装",
|
||||
"4927a53bcc886afb": "加载中…",
|
||||
"493e097fac6bd217": "上游配置",
|
||||
"497c85690c4cc0fc": "暂无数据",
|
||||
"4d0680f9efaef147": "未读",
|
||||
"4eafa9e925b30bcd": "自定义",
|
||||
"51d04bc3d286f018": "近1自然日",
|
||||
"5228358a6db59fe7": "如:现在、2026-08-23 18:00",
|
||||
"54644705e9c61009": "端口设置",
|
||||
"54c53e5fe791d1f3": "初始化 CA",
|
||||
@@ -93,6 +101,7 @@
|
||||
"5ae715656ffbc35d": "合并到该上游所有模型的请求体。",
|
||||
"5c55a67935af8f45": "全部",
|
||||
"5f8d556a9c47da3c": "已关闭开机启动",
|
||||
"5f9acfb945229062": "你确认不想再看到此广告吗?",
|
||||
"5fd2ec5a6e9b654c": "合计:{cost}",
|
||||
"6078a681a306930d": "缓存写入",
|
||||
"61a4c7bac12dc125": "异常调用:{count}",
|
||||
@@ -107,6 +116,7 @@
|
||||
"65a6318e07ec1e07": "工具数",
|
||||
"65cb9a7b4f620b6b": "提示词 {tokens}",
|
||||
"68152165b3348852": "选择上游服务使用的请求协议。",
|
||||
"68ea5dd4d7af20e6": "系统设置",
|
||||
"6a9906c79f26c0ba": "开始时间",
|
||||
"6ae80538c2b2572d": "最小化窗口",
|
||||
"6d1876364ac6457d": "代理方式",
|
||||
@@ -138,8 +148,10 @@
|
||||
"8a8542f6964852dc": "下一页",
|
||||
"8b6ff498515bcc2f": "时间",
|
||||
"8d0c47eb9eac2d34": "调用类型",
|
||||
"8df48894086d6fbd": "原因(可选)",
|
||||
"90800c48a1dd0655": "{label} 必须是 JSON 对象",
|
||||
"919cb0ce0c8db4e7": "留空表示保留当前密码",
|
||||
"91aaf184cfc17ffd": "数据概览",
|
||||
"91ee087c3d4f3220": "支持完整 HTTP(S) 地址或以 / 开头、与上游地址组合的相对路径。",
|
||||
"940a168911ade998": "每页条数",
|
||||
"946b3ffc02f026c0": "确定删除这个模型吗?",
|
||||
@@ -152,6 +164,8 @@
|
||||
"9ac0ac940982895d": "选择上游",
|
||||
"9c41b3a9e12ac994": "思考强度",
|
||||
"9e46da6923836182": "如:2026-08-23 09:00、1小时前",
|
||||
"9f6fee1aba17a565": "语言",
|
||||
"9fb48101d237ff96": "近一周",
|
||||
"a026f37e613cf48b": "输出 Token",
|
||||
"a03a1a0cb35414f8": "必须是 0–65535 之间的整数",
|
||||
"a1a42cd9b16e2162": "应用设置",
|
||||
@@ -179,10 +193,13 @@
|
||||
"b75a46aad3e7c132": "非缓存输入:{tokens}",
|
||||
"b79354009c614ae9": "统计数据",
|
||||
"b89a0e4584f27ab5": "打开终端",
|
||||
"b8c9b486c83b5778": "不再显示广告",
|
||||
"b9670c85a4ab939e": "路由",
|
||||
"b97ad406809572e1": "启用推理",
|
||||
"b9af2de88d903be7": "代理地址",
|
||||
"bb2b7736433ae867": "Cursor 追踪",
|
||||
"bb7efdcb6af6e805": "默认暗色",
|
||||
"bda62ce1d5e4ace9": "可以告诉我们原因",
|
||||
"bf57afd709694b55": "概览时间范围",
|
||||
"bf84379b75735037": "编辑上游",
|
||||
"bfc01caf9fe0c841": "缓存命中率 {rate}",
|
||||
@@ -199,6 +216,7 @@
|
||||
"cfae1a14d2120c57": "详细模式",
|
||||
"cfe085015632e9c8": "桌面前端连接的本地管理服务端口;填写 0 时启动时随机选择。",
|
||||
"cfe999e50be8ef54": "是否声明模型支持图片生成。",
|
||||
"d0bfccc77315d887": "近一个月",
|
||||
"d15a909c3490a7e0": "端点类型",
|
||||
"d2d648bd1c94b7f9": "认证",
|
||||
"d30d35c5ec4a888d": "请至少选择或输入一个模型",
|
||||
@@ -209,6 +227,7 @@
|
||||
"d86fa42c3848c680": "使用系统代理",
|
||||
"d8c47e9776cf1082": "主菜单",
|
||||
"da521d1c1cbd36af": "需要授权安装证书",
|
||||
"da7ae985487c38e6": "近1小时",
|
||||
"db0a1393f05aa4bd": "默认继承上游,可为当前模型单独修改。",
|
||||
"ddde16f8839da3ce": "总请求",
|
||||
"de8184da1ef88d03": "已配置",
|
||||
@@ -222,7 +241,9 @@
|
||||
"e14a5eee9b0b0f9f": "是否声明模型支持推理能力。",
|
||||
"e14f20d572c02611": "上游调用序号",
|
||||
"e18516550b9a5105": "无用量",
|
||||
"e25bf3f419bb68f0": "调用详细",
|
||||
"e3fee05f688708b4": "LLM 调用",
|
||||
"e5043c7a2b408271": "近10分钟",
|
||||
"e59ae97924d62f01": "第一页",
|
||||
"e5b9961a0d5242e3": "端口设置已保存,重启软件后生效",
|
||||
"e6ca887f22288cde": "Model ID 和显示名称不能为空",
|
||||
@@ -245,6 +266,7 @@
|
||||
"f4fa9f31ea2ae58d": "过去一年的 Token 用量日历",
|
||||
"f50276449943286c": "结束时间",
|
||||
"f69273dbbebfb3a1": "格式化",
|
||||
"f78413c36d36f090": "默认跟随操作系统;不支持的系统语言使用英文。当前:{language}",
|
||||
"f85537d1fd2ef6f6": "自定义概览筛选",
|
||||
"f95ea7f4c063eea7": "未启用",
|
||||
"f9615d05d8e18595": "清除{label}筛选",
|
||||
@@ -258,6 +280,7 @@
|
||||
"fd415f8e0097c832": "缓存读写已计入提示词侧统计。",
|
||||
"fdc4cabc370fa3f7": "暂无选项",
|
||||
"fea405f9b01d1416": "概览",
|
||||
"fec45092945f8790": "使用教程",
|
||||
"ff509c9ba052a21c": "保存中…",
|
||||
"ff66b1c7e010bbc2": "请在终端中粘贴授权命令并输入密码,完成后点击下方按钮",
|
||||
"ff673195f05fb056": "服务端口"
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { setRuntimeLocale, type Locale } from "./runtime";
|
||||
|
||||
export type LocalePreference = "system" | Locale;
|
||||
|
||||
type I18nSnapshot = {
|
||||
preference: LocalePreference;
|
||||
locale: Locale;
|
||||
};
|
||||
|
||||
const storageKey = "cursor-byok.locale";
|
||||
const listeners = new Set<() => void>();
|
||||
let initialized = false;
|
||||
let snapshot: I18nSnapshot = { preference: "system", locale: "en-US" };
|
||||
|
||||
function isLocale(value: string | null): value is Locale {
|
||||
return value === "zh-CN" || value === "en-US";
|
||||
}
|
||||
|
||||
export function resolveSystemLocale(
|
||||
languages: readonly string[] = navigator.languages.length
|
||||
? navigator.languages
|
||||
: [navigator.language],
|
||||
): Locale {
|
||||
const normalized = languages[0]?.toLowerCase() ?? "";
|
||||
if (normalized === "zh" || normalized.startsWith("zh-")) return "zh-CN";
|
||||
if (normalized === "en" || normalized.startsWith("en-")) return "en-US";
|
||||
return "en-US";
|
||||
}
|
||||
|
||||
function readPreference(): LocalePreference {
|
||||
try {
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
return isLocale(saved) ? saved : "system";
|
||||
} catch {
|
||||
return "system";
|
||||
}
|
||||
}
|
||||
|
||||
function applyPreference(preference: LocalePreference, notify: boolean) {
|
||||
const locale = preference === "system" ? resolveSystemLocale() : preference;
|
||||
const changed = snapshot.preference !== preference || snapshot.locale !== locale;
|
||||
snapshot = { preference, locale };
|
||||
setRuntimeLocale(locale);
|
||||
document.documentElement.lang = locale;
|
||||
if (notify && changed) listeners.forEach((listener) => listener());
|
||||
}
|
||||
|
||||
export function initializeI18n() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
applyPreference(readPreference(), false);
|
||||
window.addEventListener("languagechange", () => {
|
||||
if (snapshot.preference === "system") applyPreference("system", true);
|
||||
});
|
||||
}
|
||||
|
||||
export function setLocalePreference(preference: LocalePreference) {
|
||||
try {
|
||||
if (preference === "system") localStorage.removeItem(storageKey);
|
||||
else localStorage.setItem(storageKey, preference);
|
||||
} catch {
|
||||
// The preference still applies for the current session when storage is unavailable.
|
||||
}
|
||||
applyPreference(preference, true);
|
||||
}
|
||||
|
||||
export function useI18n(): I18nSnapshot {
|
||||
return useSyncExternalStore(
|
||||
(listener) => {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
},
|
||||
() => snapshot,
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "../node_modules/monaco-editor/min/vs/editor/editor.main.css";
|
||||
import { App } from "./App";
|
||||
import { setRuntimeLocale } from "./i18n/runtime";
|
||||
import { I18nRoot } from "./i18n/I18nRoot";
|
||||
import { initializeI18n } from "./i18n/store";
|
||||
import { appStore } from "./store/appStore";
|
||||
import { applyTheme } from "./theme/theme";
|
||||
import "./styles/globals.scss";
|
||||
|
||||
setRuntimeLocale("zh-CN");
|
||||
initializeI18n();
|
||||
applyTheme(appStore.getSnapshot().theme);
|
||||
void appStore.refresh();
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<App />,
|
||||
<I18nRoot />,
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ export function AppHeader({ platform, nativeDesktop }: AppHeaderProps) {
|
||||
{showNativeUi && <>
|
||||
<div className={styles.identity} aria-label="Cursor BYOK">
|
||||
<img src={appIcon} alt="" />
|
||||
<span>Cursor 助手 v0.1.0</span>
|
||||
<span>{t("Cursor 助手 v0.1.0")}</span>
|
||||
</div>
|
||||
<WindowControls />
|
||||
</>}
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
a {
|
||||
a,
|
||||
button {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
@@ -73,6 +74,22 @@
|
||||
background: var(--vscode-list-activeSelectionBackground);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.menuUnreadDot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
flex: 0 0 7px;
|
||||
margin-left: auto;
|
||||
background: #ef4444;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dismissReason {
|
||||
|
||||
@@ -13,50 +13,24 @@ import controls from "../components/ui/Controls.module.scss";
|
||||
import { Icon } from "../components/ui/Icon";
|
||||
import { Modal } from "../components/ui/Modal";
|
||||
import { TooltipTrigger } from "../components/ui/TooltipTrigger";
|
||||
import { flatColorAreaChartIcon, flatColorOrganizationIcon, flatColorSalesPerformanceIcon, flatColorSettingsIcon, refreshIcon } from "../components/ui/icons";
|
||||
import { flatColorAboutIcon, flatColorAreaChartIcon, flatColorOrganizationIcon, flatColorSalesPerformanceIcon, flatColorSettingsIcon, refreshIcon } from "../components/ui/icons";
|
||||
import { useMessage } from "../components/ui/message";
|
||||
import { VirtualList } from "../components/virtual/VirtualList";
|
||||
import { useI18n } from "../i18n/store";
|
||||
import { appStore, useAppStore } from "../store/appStore";
|
||||
import styles from "./AppLayout.module.scss";
|
||||
import { PageActionsTarget } from "./PageActions";
|
||||
|
||||
type MenuItem =
|
||||
| { kind: "page"; path: string; label: string; icon: IconifyIcon | string }
|
||||
| { kind: "external"; id: string; label: string; icon: IconifyIcon | string }
|
||||
| { kind: "group"; label: string };
|
||||
|
||||
const menuItems: MenuItem[] = [
|
||||
{ kind: "page", path: "/", label: "数据概览", icon: flatColorAreaChartIcon },
|
||||
{
|
||||
kind: "page",
|
||||
path: "/calls",
|
||||
label: "调用详细",
|
||||
icon: flatColorSalesPerformanceIcon,
|
||||
},
|
||||
{
|
||||
kind: "page",
|
||||
path: "/providers",
|
||||
label: "上游配置",
|
||||
icon: flatColorOrganizationIcon,
|
||||
},
|
||||
{ kind: "group", label: "Harness" },
|
||||
|
||||
{
|
||||
kind: "page",
|
||||
path: "/settings",
|
||||
label: "系统设置",
|
||||
icon: flatColorSettingsIcon,
|
||||
},
|
||||
{
|
||||
kind: "page",
|
||||
path: "/harness/cursor",
|
||||
label: "Cursor 设置",
|
||||
icon: cursorIconUrl,
|
||||
},
|
||||
];
|
||||
|
||||
const keptAlivePages = ["/", "/calls", "/providers", "/settings", "/harness/cursor"];
|
||||
const readAdStorageKey = "cursor-byok:read-ad-ids";
|
||||
const dismissedAdStorageKey = "cursor-byok:dismissed-ad-ids";
|
||||
const tutorialReadStorageKey = "cursor-byok:tutorial-read";
|
||||
const tutorialUrl = "https://docs.leokun.cn";
|
||||
|
||||
function loadStoredAdIds(key: string): Set<string> {
|
||||
try {
|
||||
@@ -69,6 +43,7 @@ function loadStoredAdIds(key: string): Set<string> {
|
||||
|
||||
export function AppLayout() {
|
||||
const { busy } = useAppStore();
|
||||
const { locale } = useI18n();
|
||||
const message = useMessage();
|
||||
const location = useLocation();
|
||||
const [actionTarget, setActionTarget] = useState<HTMLDivElement | null>(null);
|
||||
@@ -76,6 +51,13 @@ export function AppLayout() {
|
||||
const [activeAd, setActiveAd] = useState<AdSlot | null>(null);
|
||||
const [dismissCandidate, setDismissCandidate] = useState<AdSlot | null>(null);
|
||||
const [dismissReason, setDismissReason] = useState("");
|
||||
const [tutorialRead, setTutorialRead] = useState(() => {
|
||||
try {
|
||||
return localStorage.getItem(tutorialReadStorageKey) === "true";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const [readAdIds, setReadAdIds] = useState(() => loadStoredAdIds(readAdStorageKey));
|
||||
const [dismissedAdIds, setDismissedAdIds] = useState(() => loadStoredAdIds(dismissedAdStorageKey));
|
||||
const dismissedAdIdsRef = useRef(dismissedAdIds);
|
||||
@@ -83,6 +65,26 @@ export function AppLayout() {
|
||||
const adTriggers = useRef(new Map<string, HTMLButtonElement>());
|
||||
const closeAd = useCallback(() => setActiveAd(null), []);
|
||||
const visibleAds = ads.filter((ad) => ad.enabled && ad.placement === "menu" && !dismissedAdIds.has(ad.id));
|
||||
const menuItems: MenuItem[] = [
|
||||
{ kind: "page", path: "/", label: t("数据概览"), icon: flatColorAreaChartIcon },
|
||||
{ kind: "page", path: "/calls", label: t("调用详细"), icon: flatColorSalesPerformanceIcon },
|
||||
{ kind: "page", path: "/providers", label: t("上游配置"), icon: flatColorOrganizationIcon },
|
||||
{ kind: "external", id: "tutorial", label: t("使用教程"), icon: flatColorAboutIcon },
|
||||
{ kind: "group", label: "Harness" },
|
||||
{ kind: "page", path: "/settings", label: t("系统设置"), icon: flatColorSettingsIcon },
|
||||
{ kind: "page", path: "/harness/cursor", label: t("Cursor 设置"), icon: cursorIconUrl },
|
||||
];
|
||||
|
||||
const openTutorial = useCallback(() => {
|
||||
setTutorialRead(true);
|
||||
try {
|
||||
localStorage.setItem(tutorialReadStorageKey, "true");
|
||||
} catch {
|
||||
// Read state remains valid for the current session when storage is unavailable.
|
||||
}
|
||||
void api.openExternalUrl(tutorialUrl)
|
||||
.catch((cause) => message(cause instanceof Error ? cause.message : String(cause)));
|
||||
}, [message]);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
@@ -93,7 +95,7 @@ export function AppLayout() {
|
||||
if (pending || now - lastRequestedAt < 10_000) return;
|
||||
pending = true;
|
||||
lastRequestedAt = now;
|
||||
void api.ads(dismissedAdIdsRef.current)
|
||||
void api.ads(dismissedAdIdsRef.current, locale)
|
||||
.then((runtime) => {
|
||||
if (disposed) return;
|
||||
setAds(runtime.slots);
|
||||
@@ -120,7 +122,7 @@ export function AppLayout() {
|
||||
window.removeEventListener("focus", refreshAds);
|
||||
document.removeEventListener("visibilitychange", refreshVisibleAds);
|
||||
};
|
||||
}, []);
|
||||
}, [locale]);
|
||||
|
||||
const openAd = useCallback((ad: AdSlot) => {
|
||||
setReadAdIds((current) => {
|
||||
@@ -178,13 +180,27 @@ export function AppLayout() {
|
||||
<nav className={styles.navigation} aria-label={t("主菜单")}>
|
||||
<VirtualList
|
||||
items={menuItems}
|
||||
itemKey={(item) => item.kind === "group" ? `group-${item.label}` : item.path}
|
||||
itemKey={(item) => item.kind === "group" ? `group-${item.label}` : item.kind === "external" ? `external-${item.id}` : item.path}
|
||||
estimatedItemHeight={36}
|
||||
itemGap={3}
|
||||
className={`${styles.navigationList} scroll-shadow-bottom`}
|
||||
>
|
||||
{(item) => item.kind === "group"
|
||||
? <div className={styles.navigationGroup} key={`group-${item.label}`}>{item.label}</div>
|
||||
: item.kind === "external"
|
||||
? <div className={styles.navigationRow} key={item.id}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${item.label}${tutorialRead ? "" : `,${t("未读")}`}`}
|
||||
onClick={openTutorial}
|
||||
>
|
||||
{typeof item.icon === "string"
|
||||
? <Icon src={item.icon} size="1.3em" />
|
||||
: <Icon icon={item.icon} size="1.3em" />}
|
||||
<span>{item.label}</span>
|
||||
{!tutorialRead && <span className={styles.menuUnreadDot} aria-hidden="true" />}
|
||||
</button>
|
||||
</div>
|
||||
: <div className={styles.navigationRow} key={item.path}>
|
||||
<NavLink to={item.path} end={item.path === "/"}>
|
||||
{typeof item.icon === "string"
|
||||
|
||||
@@ -80,6 +80,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.languageControl {
|
||||
width: 180px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.portSettings {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
|
||||
@@ -7,7 +7,9 @@ import { Button } from "../components/ui/Button";
|
||||
import { Checkbox } from "../components/ui/Checkbox";
|
||||
import { FormField, TextInput } from "../components/ui/FormControls";
|
||||
import { Modal } from "../components/ui/Modal";
|
||||
import { Select } from "../components/ui/Select";
|
||||
import { TitledCard } from "../components/ui/TitledCard";
|
||||
import { setLocalePreference, useI18n, type LocalePreference } from "../i18n/store";
|
||||
import { useMessage } from "../components/ui/message";
|
||||
import { appStore, useAppStore } from "../store/appStore";
|
||||
import { themeOptions } from "../theme/theme";
|
||||
@@ -15,6 +17,7 @@ import styles from "./SettingsPage.module.scss";
|
||||
|
||||
export function SettingsPage() {
|
||||
const { detailed, ports, theme } = useAppStore();
|
||||
const { preference, locale } = useI18n();
|
||||
const message = useMessage();
|
||||
const [proxyPort, setProxyPort] = useState(String(ports.proxy_port));
|
||||
const [servicePort, setServicePort] = useState(String(ports.service_port));
|
||||
@@ -189,15 +192,35 @@ export function SettingsPage() {
|
||||
</TitledCard>
|
||||
<ProxySettingsCard settings={outboundProxy} draft={proxyDraft} editing={editingProxy} saving={savingProxy} onDraftChange={setProxyDraft} onEdit={editProxy} onCancel={cancelProxyEdit} onSave={() => void saveProxy()} />
|
||||
<AppLifecycleSettingsCard />
|
||||
<TitledCard title={t("语言")}>
|
||||
<div className={styles.settingRow}>
|
||||
<div>
|
||||
<strong>{t("界面语言")}</strong>
|
||||
<small>{t("默认跟随操作系统;不支持的系统语言使用英文。当前:{language}", { language: locale === "zh-CN" ? "简体中文" : "English" })}</small>
|
||||
</div>
|
||||
<div className={styles.languageControl}>
|
||||
<Select
|
||||
value={preference}
|
||||
ariaLabel={t("界面语言")}
|
||||
options={[
|
||||
{ value: "system", label: t("跟随系统") },
|
||||
{ value: "zh-CN", label: "简体中文" },
|
||||
{ value: "en-US", label: "English" },
|
||||
]}
|
||||
onChange={(value) => setLocalePreference(value as LocalePreference)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TitledCard>
|
||||
<TitledCard title={t("主题")}>
|
||||
<div className={styles.themeActions}>
|
||||
{themeOptions.map(({ id, name }) => (
|
||||
{themeOptions.map(({ id }) => (
|
||||
<button
|
||||
className={theme === id ? styles.selected : ""}
|
||||
key={id}
|
||||
onClick={() => appStore.selectTheme(id)}
|
||||
>
|
||||
{name}
|
||||
{id === "default-dark" ? t("默认暗色") : t("默认亮色")}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,9 @@ export const themeIds = ["default-dark", "default-light"] as const;
|
||||
export type ThemeId = (typeof themeIds)[number];
|
||||
|
||||
export const themeOptions = [
|
||||
{ id: "default-dark", name: "默认暗色" },
|
||||
{ id: "default-light", name: "默认亮色" },
|
||||
] satisfies { id: ThemeId; name: string }[];
|
||||
{ id: "default-dark" },
|
||||
{ id: "default-light" },
|
||||
] satisfies { id: ThemeId }[];
|
||||
|
||||
export function isThemeId(value: string | null): value is ThemeId {
|
||||
return value !== null && themeIds.some((id) => id === value);
|
||||
|
||||
@@ -15,11 +15,12 @@ use super::ControlService;
|
||||
// 此广告拉取不涉及用户隐私,用户id随机产生
|
||||
// 开源项目广告为作者唯一收入来源,PR时请勿去除此广告
|
||||
|
||||
pub(super) const ADS_ENDPOINT: &str = "http://127.0.0.1:8080/api/v1/ads?placement=menu";
|
||||
pub(super) const ADS_ENDPOINT: &str = "https://tab.leokun.cn/api/v1/ads?placement=menu";
|
||||
pub(super) const DEVICE_ID_HEADER: &str = "X-Cursor-Assistant-Device-ID";
|
||||
pub(super) const OS_HEADER: &str = "X-Cursor-Assistant-OS";
|
||||
pub(super) const APP_VERSION_HEADER: &str = "X-Cursor-Assistant-Version";
|
||||
pub(super) const DISABLED_AD_IDS_HEADER: &str = "disable-ad-ids";
|
||||
pub(super) const LANGUAGE_HEADER: &str = "accept-language";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct AdRuntime {
|
||||
@@ -121,7 +122,19 @@ pub async fn get(
|
||||
let disabled_ad_ids = headers
|
||||
.get(DISABLED_AD_IDS_HEADER)
|
||||
.and_then(|value| value.to_str().ok());
|
||||
Ok(Json(service.ads(disabled_ad_ids).await?))
|
||||
Ok(Json(
|
||||
service.ads(disabled_ad_ids, ad_language(&headers)).await?,
|
||||
))
|
||||
}
|
||||
|
||||
fn ad_language(headers: &HeaderMap) -> &'static str {
|
||||
match headers
|
||||
.get(LANGUAGE_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
{
|
||||
Some(value) if value.eq_ignore_ascii_case("zh-CN") => "zh-CN",
|
||||
_ => "en-US",
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dismiss(
|
||||
@@ -137,6 +150,16 @@ pub async fn dismiss(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn accepts_supported_ad_language_and_falls_back_to_english() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(LANGUAGE_HEADER, "zh-CN".parse().unwrap());
|
||||
assert_eq!(ad_language(&headers), "zh-CN");
|
||||
|
||||
headers.insert(LANGUAGE_HEADER, "fr-FR".parse().unwrap());
|
||||
assert_eq!(ad_language(&headers), "en-US");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_disabled_slots_without_limiting_menu_ads() {
|
||||
let slot = |id: &str, enabled| AdSlot {
|
||||
|
||||
@@ -185,6 +185,7 @@ fn desktop_cors() -> CorsLayer {
|
||||
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
|
||||
.allow_headers([
|
||||
CONTENT_TYPE,
|
||||
header::ACCEPT_LANGUAGE,
|
||||
header::HeaderName::from_static("disable-ad-ids"),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use url::Url;
|
||||
|
||||
use super::ads::{
|
||||
AdDismissalInput, AdRuntime, ADS_ENDPOINT, APP_VERSION_HEADER, DEVICE_ID_HEADER,
|
||||
DISABLED_AD_IDS_HEADER, OS_HEADER,
|
||||
DISABLED_AD_IDS_HEADER, LANGUAGE_HEADER, OS_HEADER,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -83,7 +83,11 @@ impl ControlService {
|
||||
&self.cursor_harness
|
||||
}
|
||||
|
||||
pub(super) async fn ads(&self, disabled_ad_ids: Option<&str>) -> Result<AdRuntime> {
|
||||
pub(super) async fn ads(
|
||||
&self,
|
||||
disabled_ad_ids: Option<&str>,
|
||||
language: &str,
|
||||
) -> Result<AdRuntime> {
|
||||
let client = crate::network::client(&self.store).await?;
|
||||
let installation_id = self.store.installation_id().await?;
|
||||
let mut request = client
|
||||
@@ -91,6 +95,7 @@ impl ControlService {
|
||||
.header(DEVICE_ID_HEADER, installation_id)
|
||||
.header(OS_HEADER, std::env::consts::OS)
|
||||
.header(APP_VERSION_HEADER, env!("CARGO_PKG_VERSION"))
|
||||
.header(LANGUAGE_HEADER, language)
|
||||
.timeout(std::time::Duration::from_secs(5));
|
||||
if let Some(disabled_ad_ids) = disabled_ad_ids.filter(|value| !value.is_empty()) {
|
||||
request = request.header(DISABLED_AD_IDS_HEADER, disabled_ad_ids);
|
||||
|
||||
Reference in New Issue
Block a user