mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
chore: generate
This commit is contained in:
parent
233d003b49
commit
bb8bf32abe
@ -236,8 +236,12 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Match when={provider().id === "opencode"}>
|
<Match when={provider().id === "opencode"}>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line1")}</div>
|
<div class="text-14-regular text-text-base">
|
||||||
<div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line2")}</div>
|
{language.t("provider.connect.opencodeZen.line1")}
|
||||||
|
</div>
|
||||||
|
<div class="text-14-regular text-text-base">
|
||||||
|
{language.t("provider.connect.opencodeZen.line2")}
|
||||||
|
</div>
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="text-14-regular text-text-base">
|
||||||
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
||||||
<Link href="https://opencode.ai/zen" tabIndex={-1}>
|
<Link href="https://opencode.ai/zen" tabIndex={-1}>
|
||||||
@ -317,7 +321,9 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="text-14-regular text-text-base">
|
||||||
{language.t("provider.connect.oauth.code.visit.prefix")}
|
{language.t("provider.connect.oauth.code.visit.prefix")}
|
||||||
<Link href={store.authorization!.url}>{language.t("provider.connect.oauth.code.visit.link")}</Link>
|
<Link href={store.authorization!.url}>
|
||||||
|
{language.t("provider.connect.oauth.code.visit.link")}
|
||||||
|
</Link>
|
||||||
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
|
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
|
||||||
@ -367,7 +373,9 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="text-14-regular text-text-base">
|
||||||
{language.t("provider.connect.oauth.auto.visit.prefix")}
|
{language.t("provider.connect.oauth.auto.visit.prefix")}
|
||||||
<Link href={store.authorization!.url}>{language.t("provider.connect.oauth.auto.visit.link")}</Link>
|
<Link href={store.authorization!.url}>
|
||||||
|
{language.t("provider.connect.oauth.auto.visit.link")}
|
||||||
|
</Link>
|
||||||
{language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
|
{language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
|
||||||
</div>
|
</div>
|
||||||
<TextField
|
<TextField
|
||||||
|
|||||||
@ -62,9 +62,9 @@ export const DialogSelectModelUnpaid: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="px-1.5 pb-1.5">
|
<div class="px-1.5 pb-1.5">
|
||||||
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
|
<div class="w-full rounded-sm border border-border-weak-base bg-surface-raised-base">
|
||||||
<div class="w-full flex flex-col items-start gap-4 px-1.5 pt-4 pb-4">
|
<div class="w-full flex flex-col items-start gap-4 px-1.5 pt-4 pb-4">
|
||||||
<div class="px-2 text-14-medium text-text-base">{language.t("dialog.model.unpaid.addMore.title")}</div>
|
<div class="px-2 text-14-medium text-text-base">{language.t("dialog.model.unpaid.addMore.title")}</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<List
|
<List
|
||||||
class="w-full px-0"
|
class="w-full px-0"
|
||||||
key={(x) => x?.id}
|
key={(x) => x?.id}
|
||||||
|
|||||||
@ -200,9 +200,7 @@ export function DialogSelectServer() {
|
|||||||
<div class="mt-6 px-3 flex flex-col gap-1.5">
|
<div class="mt-6 px-3 flex flex-col gap-1.5">
|
||||||
<div class="px-3">
|
<div class="px-3">
|
||||||
<h3 class="text-14-regular text-text-weak">{language.t("dialog.server.default.title")}</h3>
|
<h3 class="text-14-regular text-text-weak">{language.t("dialog.server.default.title")}</h3>
|
||||||
<p class="text-12-regular text-text-weak mt-1">
|
<p class="text-12-regular text-text-weak mt-1">{language.t("dialog.server.default.description")}</p>
|
||||||
{language.t("dialog.server.default.description")}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 px-3 py-2">
|
<div class="flex items-center gap-2 px-3 py-2">
|
||||||
<Show
|
<Show
|
||||||
@ -210,7 +208,9 @@ export function DialogSelectServer() {
|
|||||||
fallback={
|
fallback={
|
||||||
<Show
|
<Show
|
||||||
when={server.url}
|
when={server.url}
|
||||||
fallback={<span class="text-14-regular text-text-weak">{language.t("dialog.server.default.none")}</span>}
|
fallback={
|
||||||
|
<span class="text-14-regular text-text-weak">{language.t("dialog.server.default.none")}</span>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|||||||
@ -261,7 +261,10 @@ export function SessionContextTab(props: SessionContextTabProps) {
|
|||||||
value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}`,
|
value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}`,
|
||||||
},
|
},
|
||||||
{ label: language.t("context.stats.userMessages"), value: count.user.toLocaleString(language.locale()) },
|
{ label: language.t("context.stats.userMessages"), value: count.user.toLocaleString(language.locale()) },
|
||||||
{ label: language.t("context.stats.assistantMessages"), value: count.assistant.toLocaleString(language.locale()) },
|
{
|
||||||
|
label: language.t("context.stats.assistantMessages"),
|
||||||
|
value: count.assistant.toLocaleString(language.locale()),
|
||||||
|
},
|
||||||
{ label: language.t("context.stats.totalCost"), value: cost() },
|
{ label: language.t("context.stats.totalCost"), value: cost() },
|
||||||
{ label: language.t("context.stats.sessionCreated"), value: time(props.info()?.time.created) },
|
{ label: language.t("context.stats.sessionCreated"), value: time(props.info()?.time.created) },
|
||||||
{ label: language.t("context.stats.lastActivity"), value: time(c?.message.time.created) },
|
{ label: language.t("context.stats.lastActivity"), value: time(c?.message.time.created) },
|
||||||
@ -402,9 +405,7 @@ export function SessionContextTab(props: SessionContextTabProps) {
|
|||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden text-11-regular text-text-weaker">
|
<div class="hidden text-11-regular text-text-weaker">{language.t("context.breakdown.note")}</div>
|
||||||
{language.t("context.breakdown.note")}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
|||||||
@ -183,7 +183,10 @@ export function SessionHeader() {
|
|||||||
}}
|
}}
|
||||||
aria-hidden={!showReview()}
|
aria-hidden={!showReview()}
|
||||||
>
|
>
|
||||||
<TooltipKeybind title={language.t("command.review.toggle")} keybind={command.keybind("review.toggle")}>
|
<TooltipKeybind
|
||||||
|
title={language.t("command.review.toggle")}
|
||||||
|
keybind={command.keybind("review.toggle")}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="group/review-toggle size-6 p-0"
|
class="group/review-toggle size-6 p-0"
|
||||||
|
|||||||
@ -15,13 +15,11 @@ export const SettingsGeneral: Component = () => {
|
|||||||
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
|
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
|
||||||
)
|
)
|
||||||
|
|
||||||
const colorSchemeOptions = createMemo(
|
const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [
|
||||||
(): { value: ColorScheme; label: string }[] => [
|
{ value: "system", label: language.t("theme.scheme.system") },
|
||||||
{ value: "system", label: language.t("theme.scheme.system") },
|
{ value: "light", label: language.t("theme.scheme.light") },
|
||||||
{ value: "light", label: language.t("theme.scheme.light") },
|
{ value: "dark", label: language.t("theme.scheme.dark") },
|
||||||
{ value: "dark", label: language.t("theme.scheme.dark") },
|
])
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
const languageOptions = createMemo(() =>
|
const languageOptions = createMemo(() =>
|
||||||
language.locales.map((locale) => ({
|
language.locales.map((locale) => ({
|
||||||
@ -107,7 +105,7 @@ export const SettingsGeneral: Component = () => {
|
|||||||
title={language.t("settings.general.row.theme.title")}
|
title={language.t("settings.general.row.theme.title")}
|
||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
{language.t("settings.general.row.theme.description")} {" "}
|
{language.t("settings.general.row.theme.description")}{" "}
|
||||||
<a href="#" class="text-text-interactive-base">
|
<a href="#" class="text-text-interactive-base">
|
||||||
{language.t("common.learnMore")}
|
{language.t("common.learnMore")}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -658,10 +658,7 @@ function createGlobalSync() {
|
|||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
if (!health?.healthy) {
|
if (!health?.healthy) {
|
||||||
setGlobalStore(
|
setGlobalStore("error", new Error(language.t("error.globalSync.connectFailed", { url: globalSDK.url })))
|
||||||
"error",
|
|
||||||
new Error(language.t("error.globalSync.connectFailed", { url: globalSDK.url })),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -96,7 +96,11 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
|
|
||||||
const href = `/${base64Encode(directory)}/session/${sessionID}`
|
const href = `/${base64Encode(directory)}/session/${sessionID}`
|
||||||
if (settings.notifications.agent()) {
|
if (settings.notifications.agent()) {
|
||||||
void platform.notify(language.t("notification.session.responseReady.title"), session?.title ?? sessionID, href)
|
void platform.notify(
|
||||||
|
language.t("notification.session.responseReady.title"),
|
||||||
|
session?.title ?? sessionID,
|
||||||
|
href,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -117,7 +121,8 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
error,
|
error,
|
||||||
})
|
})
|
||||||
const description =
|
const description =
|
||||||
session?.title ?? (typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
|
session?.title ??
|
||||||
|
(typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
|
||||||
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
|
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
|
||||||
if (settings.notifications.errors()) {
|
if (settings.notifications.errors()) {
|
||||||
void platform.notify(language.t("notification.session.error.title"), description, href)
|
void platform.notify(language.t("notification.session.error.title"), description, href)
|
||||||
|
|||||||
@ -19,7 +19,7 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
})()
|
})()
|
||||||
|
|
||||||
const key = "error.dev.rootNotFound" as const
|
const key = "error.dev.rootNotFound" as const
|
||||||
const message = locale === "zh" ? zh[key] ?? en[key] : en[key]
|
const message = locale === "zh" ? (zh[key] ?? en[key]) : en[key]
|
||||||
throw new Error(message)
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -144,7 +144,7 @@ export const dict = {
|
|||||||
"common.attachment": "attachment",
|
"common.attachment": "attachment",
|
||||||
|
|
||||||
"prompt.placeholder.shell": "Enter shell command...",
|
"prompt.placeholder.shell": "Enter shell command...",
|
||||||
"prompt.placeholder.normal": "Ask anything... \"{{example}}\"",
|
"prompt.placeholder.normal": 'Ask anything... "{{example}}"',
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.shell.exit": "esc to exit",
|
"prompt.mode.shell.exit": "esc to exit",
|
||||||
|
|
||||||
@ -219,7 +219,8 @@ export const dict = {
|
|||||||
"dialog.server.add.checking": "Checking...",
|
"dialog.server.add.checking": "Checking...",
|
||||||
"dialog.server.add.button": "Add",
|
"dialog.server.add.button": "Add",
|
||||||
"dialog.server.default.title": "Default server",
|
"dialog.server.default.title": "Default server",
|
||||||
"dialog.server.default.description": "Connect to this server on app launch instead of starting a local server. Requires restart.",
|
"dialog.server.default.description":
|
||||||
|
"Connect to this server on app launch instead of starting a local server. Requires restart.",
|
||||||
"dialog.server.default.none": "No server selected",
|
"dialog.server.default.none": "No server selected",
|
||||||
"dialog.server.default.set": "Set current server as default",
|
"dialog.server.default.set": "Set current server as default",
|
||||||
"dialog.server.default.clear": "Clear",
|
"dialog.server.default.clear": "Clear",
|
||||||
@ -233,7 +234,7 @@ export const dict = {
|
|||||||
"dialog.project.edit.color": "Color",
|
"dialog.project.edit.color": "Color",
|
||||||
|
|
||||||
"context.breakdown.title": "Context Breakdown",
|
"context.breakdown.title": "Context Breakdown",
|
||||||
"context.breakdown.note": "Approximate breakdown of input tokens. \"Other\" includes tool definitions and overhead.",
|
"context.breakdown.note": 'Approximate breakdown of input tokens. "Other" includes tool definitions and overhead.',
|
||||||
"context.breakdown.system": "System",
|
"context.breakdown.system": "System",
|
||||||
"context.breakdown.user": "User",
|
"context.breakdown.user": "User",
|
||||||
"context.breakdown.assistant": "Assistant",
|
"context.breakdown.assistant": "Assistant",
|
||||||
@ -327,14 +328,14 @@ export const dict = {
|
|||||||
"error.chain.didYouMean": "Did you mean: {{suggestions}}",
|
"error.chain.didYouMean": "Did you mean: {{suggestions}}",
|
||||||
"error.chain.modelNotFound": "Model not found: {{provider}}/{{model}}",
|
"error.chain.modelNotFound": "Model not found: {{provider}}/{{model}}",
|
||||||
"error.chain.checkConfig": "Check your config (opencode.json) provider/model names",
|
"error.chain.checkConfig": "Check your config (opencode.json) provider/model names",
|
||||||
"error.chain.mcpFailed":
|
"error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.',
|
||||||
"MCP server \"{{name}}\" failed. Note, OpenCode does not support MCP authentication yet.",
|
|
||||||
"error.chain.providerAuthFailed": "Provider authentication failed ({{provider}}): {{message}}",
|
"error.chain.providerAuthFailed": "Provider authentication failed ({{provider}}): {{message}}",
|
||||||
"error.chain.providerInitFailed": "Failed to initialize provider \"{{provider}}\". Check credentials and configuration.",
|
"error.chain.providerInitFailed":
|
||||||
|
'Failed to initialize provider "{{provider}}". Check credentials and configuration.',
|
||||||
"error.chain.configJsonInvalid": "Config file at {{path}} is not valid JSON(C)",
|
"error.chain.configJsonInvalid": "Config file at {{path}} is not valid JSON(C)",
|
||||||
"error.chain.configJsonInvalidWithMessage": "Config file at {{path}} is not valid JSON(C): {{message}}",
|
"error.chain.configJsonInvalidWithMessage": "Config file at {{path}} is not valid JSON(C): {{message}}",
|
||||||
"error.chain.configDirectoryTypo":
|
"error.chain.configDirectoryTypo":
|
||||||
"Directory \"{{dir}}\" in {{path}} is not valid. Rename the directory to \"{{suggestion}}\" or remove it. This is a common typo.",
|
'Directory "{{dir}}" in {{path}} is not valid. Rename the directory to "{{suggestion}}" or remove it. This is a common typo.',
|
||||||
"error.chain.configFrontmatterError": "Failed to parse frontmatter in {{path}}:\n{{message}}",
|
"error.chain.configFrontmatterError": "Failed to parse frontmatter in {{path}}:\n{{message}}",
|
||||||
"error.chain.configInvalid": "Config file at {{path}} is invalid",
|
"error.chain.configInvalid": "Config file at {{path}} is invalid",
|
||||||
"error.chain.configInvalidWithMessage": "Config file at {{path}} is invalid: {{message}}",
|
"error.chain.configInvalidWithMessage": "Config file at {{path}} is invalid: {{message}}",
|
||||||
@ -374,8 +375,10 @@ export const dict = {
|
|||||||
"session.header.search.placeholder": "Search {{project}}",
|
"session.header.search.placeholder": "Search {{project}}",
|
||||||
|
|
||||||
"session.share.popover.title": "Publish on web",
|
"session.share.popover.title": "Publish on web",
|
||||||
"session.share.popover.description.shared": "This session is public on the web. It is accessible to anyone with the link.",
|
"session.share.popover.description.shared":
|
||||||
"session.share.popover.description.unshared": "Share session publicly on the web. It will be accessible to anyone with the link.",
|
"This session is public on the web. It is accessible to anyone with the link.",
|
||||||
|
"session.share.popover.description.unshared":
|
||||||
|
"Share session publicly on the web. It will be accessible to anyone with the link.",
|
||||||
"session.share.action.share": "Share",
|
"session.share.action.share": "Share",
|
||||||
"session.share.action.publish": "Publish",
|
"session.share.action.publish": "Publish",
|
||||||
"session.share.action.publishing": "Publishing...",
|
"session.share.action.publishing": "Publishing...",
|
||||||
@ -433,7 +436,8 @@ export const dict = {
|
|||||||
"settings.general.row.font.description": "Customise the mono font used in code blocks",
|
"settings.general.row.font.description": "Customise the mono font used in code blocks",
|
||||||
|
|
||||||
"settings.general.notifications.agent.title": "Agent",
|
"settings.general.notifications.agent.title": "Agent",
|
||||||
"settings.general.notifications.agent.description": "Show system notification when the agent is complete or needs attention",
|
"settings.general.notifications.agent.description":
|
||||||
|
"Show system notification when the agent is complete or needs attention",
|
||||||
"settings.general.notifications.permissions.title": "Permissions",
|
"settings.general.notifications.permissions.title": "Permissions",
|
||||||
"settings.general.notifications.permissions.description": "Show system notification when a permission is required",
|
"settings.general.notifications.permissions.description": "Show system notification when a permission is required",
|
||||||
"settings.general.notifications.errors.title": "Errors",
|
"settings.general.notifications.errors.title": "Errors",
|
||||||
@ -530,10 +534,10 @@ export const dict = {
|
|||||||
"workspace.status.clean": "No unmerged changes detected.",
|
"workspace.status.clean": "No unmerged changes detected.",
|
||||||
"workspace.status.dirty": "Unmerged changes detected in this workspace.",
|
"workspace.status.dirty": "Unmerged changes detected in this workspace.",
|
||||||
"workspace.delete.title": "Delete workspace",
|
"workspace.delete.title": "Delete workspace",
|
||||||
"workspace.delete.confirm": "Delete workspace \"{{name}}\"?",
|
"workspace.delete.confirm": 'Delete workspace "{{name}}"?',
|
||||||
"workspace.delete.button": "Delete workspace",
|
"workspace.delete.button": "Delete workspace",
|
||||||
"workspace.reset.title": "Reset workspace",
|
"workspace.reset.title": "Reset workspace",
|
||||||
"workspace.reset.confirm": "Reset workspace \"{{name}}\"?",
|
"workspace.reset.confirm": 'Reset workspace "{{name}}"?',
|
||||||
"workspace.reset.button": "Reset workspace",
|
"workspace.reset.button": "Reset workspace",
|
||||||
"workspace.reset.archived.none": "No active sessions will be archived.",
|
"workspace.reset.archived.none": "No active sessions will be archived.",
|
||||||
"workspace.reset.archived.one": "1 session will be archived.",
|
"workspace.reset.archived.one": "1 session will be archived.",
|
||||||
|
|||||||
@ -108,7 +108,8 @@ export const dict = {
|
|||||||
"provider.connect.status.inProgress": "正在授权...",
|
"provider.connect.status.inProgress": "正在授权...",
|
||||||
"provider.connect.status.waiting": "等待授权...",
|
"provider.connect.status.waiting": "等待授权...",
|
||||||
"provider.connect.status.failed": "授权失败: {{error}}",
|
"provider.connect.status.failed": "授权失败: {{error}}",
|
||||||
"provider.connect.apiKey.description": "输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。",
|
"provider.connect.apiKey.description":
|
||||||
|
"输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。",
|
||||||
"provider.connect.apiKey.label": "{{provider}} API 密钥",
|
"provider.connect.apiKey.label": "{{provider}} API 密钥",
|
||||||
"provider.connect.apiKey.placeholder": "API 密钥",
|
"provider.connect.apiKey.placeholder": "API 密钥",
|
||||||
"provider.connect.apiKey.required": "API 密钥为必填项",
|
"provider.connect.apiKey.required": "API 密钥为必填项",
|
||||||
@ -143,7 +144,7 @@ export const dict = {
|
|||||||
"common.attachment": "附件",
|
"common.attachment": "附件",
|
||||||
|
|
||||||
"prompt.placeholder.shell": "输入 shell 命令...",
|
"prompt.placeholder.shell": "输入 shell 命令...",
|
||||||
"prompt.placeholder.normal": "随便问点什么... \"{{example}}\"",
|
"prompt.placeholder.normal": '随便问点什么... "{{example}}"',
|
||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.shell.exit": "按 esc 退出",
|
"prompt.mode.shell.exit": "按 esc 退出",
|
||||||
|
|
||||||
@ -325,12 +326,13 @@ export const dict = {
|
|||||||
"error.chain.didYouMean": "你是不是想输入: {{suggestions}}",
|
"error.chain.didYouMean": "你是不是想输入: {{suggestions}}",
|
||||||
"error.chain.modelNotFound": "未找到模型: {{provider}}/{{model}}",
|
"error.chain.modelNotFound": "未找到模型: {{provider}}/{{model}}",
|
||||||
"error.chain.checkConfig": "请检查你的配置 (opencode.json) 中的 provider/model 名称",
|
"error.chain.checkConfig": "请检查你的配置 (opencode.json) 中的 provider/model 名称",
|
||||||
"error.chain.mcpFailed": "MCP 服务器 \"{{name}}\" 启动失败。注意: OpenCode 暂不支持 MCP 认证。",
|
"error.chain.mcpFailed": 'MCP 服务器 "{{name}}" 启动失败。注意: OpenCode 暂不支持 MCP 认证。',
|
||||||
"error.chain.providerAuthFailed": "提供商认证失败 ({{provider}}): {{message}}",
|
"error.chain.providerAuthFailed": "提供商认证失败 ({{provider}}): {{message}}",
|
||||||
"error.chain.providerInitFailed": "无法初始化提供商 \"{{provider}}\"。请检查凭据和配置。",
|
"error.chain.providerInitFailed": '无法初始化提供商 "{{provider}}"。请检查凭据和配置。',
|
||||||
"error.chain.configJsonInvalid": "配置文件 {{path}} 不是有效的 JSON(C)",
|
"error.chain.configJsonInvalid": "配置文件 {{path}} 不是有效的 JSON(C)",
|
||||||
"error.chain.configJsonInvalidWithMessage": "配置文件 {{path}} 不是有效的 JSON(C): {{message}}",
|
"error.chain.configJsonInvalidWithMessage": "配置文件 {{path}} 不是有效的 JSON(C): {{message}}",
|
||||||
"error.chain.configDirectoryTypo": "{{path}} 中的目录 \"{{dir}}\" 无效。请将目录重命名为 \"{{suggestion}}\" 或移除它。这是一个常见拼写错误。",
|
"error.chain.configDirectoryTypo":
|
||||||
|
'{{path}} 中的目录 "{{dir}}" 无效。请将目录重命名为 "{{suggestion}}" 或移除它。这是一个常见拼写错误。',
|
||||||
"error.chain.configFrontmatterError": "无法解析 {{path}} 中的 frontmatter:\n{{message}}",
|
"error.chain.configFrontmatterError": "无法解析 {{path}} 中的 frontmatter:\n{{message}}",
|
||||||
"error.chain.configInvalid": "配置文件 {{path}} 无效",
|
"error.chain.configInvalid": "配置文件 {{path}} 无效",
|
||||||
"error.chain.configInvalidWithMessage": "配置文件 {{path}} 无效: {{message}}",
|
"error.chain.configInvalidWithMessage": "配置文件 {{path}} 无效: {{message}}",
|
||||||
@ -526,10 +528,10 @@ export const dict = {
|
|||||||
"workspace.status.clean": "未检测到未合并的更改。",
|
"workspace.status.clean": "未检测到未合并的更改。",
|
||||||
"workspace.status.dirty": "检测到未合并的更改。",
|
"workspace.status.dirty": "检测到未合并的更改。",
|
||||||
"workspace.delete.title": "删除工作区",
|
"workspace.delete.title": "删除工作区",
|
||||||
"workspace.delete.confirm": "删除工作区 \"{{name}}\"?",
|
"workspace.delete.confirm": '删除工作区 "{{name}}"?',
|
||||||
"workspace.delete.button": "删除工作区",
|
"workspace.delete.button": "删除工作区",
|
||||||
"workspace.reset.title": "重置工作区",
|
"workspace.reset.title": "重置工作区",
|
||||||
"workspace.reset.confirm": "重置工作区 \"{{name}}\"?",
|
"workspace.reset.confirm": '重置工作区 "{{name}}"?',
|
||||||
"workspace.reset.button": "重置工作区",
|
"workspace.reset.button": "重置工作区",
|
||||||
"workspace.reset.archived.none": "不会归档任何活跃会话。",
|
"workspace.reset.archived.none": "不会归档任何活跃会话。",
|
||||||
"workspace.reset.archived.one": "将归档 1 个会话。",
|
"workspace.reset.archived.one": "将归档 1 个会话。",
|
||||||
|
|||||||
@ -78,9 +78,10 @@ function formatInitError(error: InitError, t: Translator): string {
|
|||||||
suggestions?: string[]
|
suggestions?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const suggestionsLine = Array.isArray(suggestions) && suggestions.length
|
const suggestionsLine =
|
||||||
? [t("error.chain.didYouMean", { suggestions: suggestions.join(", ") })]
|
Array.isArray(suggestions) && suggestions.length
|
||||||
: []
|
? [t("error.chain.didYouMean", { suggestions: suggestions.join(", ") })]
|
||||||
|
: []
|
||||||
|
|
||||||
return [
|
return [
|
||||||
t("error.chain.modelNotFound", { provider: providerID, model: modelID }),
|
t("error.chain.modelNotFound", { provider: providerID, model: modelID }),
|
||||||
@ -253,7 +254,9 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
|||||||
when={store.version}
|
when={store.version}
|
||||||
fallback={
|
fallback={
|
||||||
<Button size="large" variant="ghost" onClick={checkForUpdates} disabled={store.checking}>
|
<Button size="large" variant="ghost" onClick={checkForUpdates} disabled={store.checking}>
|
||||||
{store.checking ? language.t("error.page.action.checking") : language.t("error.page.action.checkUpdates")}
|
{store.checking
|
||||||
|
? language.t("error.page.action.checking")
|
||||||
|
: language.t("error.page.action.checkUpdates")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -76,13 +76,13 @@ export default function Home() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={sync.data.project.length > 0}>
|
<Match when={sync.data.project.length > 0}>
|
||||||
<div class="mt-20 w-full flex flex-col gap-4">
|
<div class="mt-20 w-full flex flex-col gap-4">
|
||||||
<div class="flex gap-2 items-center justify-between pl-3">
|
<div class="flex gap-2 items-center justify-between pl-3">
|
||||||
<div class="text-14-medium text-text-strong">{language.t("home.recentProjects")}</div>
|
<div class="text-14-medium text-text-strong">{language.t("home.recentProjects")}</div>
|
||||||
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
|
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
|
||||||
{language.t("command.project.open")}
|
{language.t("command.project.open")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ul class="flex flex-col gap-2">
|
<ul class="flex flex-col gap-2">
|
||||||
<For
|
<For
|
||||||
each={sync.data.project
|
each={sync.data.project
|
||||||
|
|||||||
@ -1909,9 +1909,9 @@ export default function Layout(props: ParentProps) {
|
|||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
onOpenChange={setOpen}
|
onOpenChange={setOpen}
|
||||||
>
|
>
|
||||||
<div class="-m-3 p-2 flex flex-col w-72">
|
<div class="-m-3 p-2 flex flex-col w-72">
|
||||||
<div class="px-4 pt-2 pb-1 text-14-medium text-text-strong truncate">{displayName(props.project)}</div>
|
<div class="px-4 pt-2 pb-1 text-14-medium text-text-strong truncate">{displayName(props.project)}</div>
|
||||||
<div class="px-4 pb-2 text-12-medium text-text-weak">{language.t("sidebar.project.recentSessions")}</div>
|
<div class="px-4 pb-2 text-12-medium text-text-weak">{language.t("sidebar.project.recentSessions")}</div>
|
||||||
<div class="px-2 pb-2 flex flex-col gap-2">
|
<div class="px-2 pb-2 flex flex-col gap-2">
|
||||||
<Show
|
<Show
|
||||||
when={workspaceEnabled()}
|
when={workspaceEnabled()}
|
||||||
@ -2177,22 +2177,22 @@ export default function Layout(props: ParentProps) {
|
|||||||
class="shrink-0 size-6 rounded-md opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100 data-[expanded]:bg-surface-base-active"
|
class="shrink-0 size-6 rounded-md opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100 data-[expanded]:bg-surface-base-active"
|
||||||
/>
|
/>
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
<DropdownMenu.Content class="mt-1">
|
<DropdownMenu.Content class="mt-1">
|
||||||
<DropdownMenu.Item onSelect={() => dialog.show(() => <DialogEditProject project={p} />)}>
|
<DropdownMenu.Item onSelect={() => dialog.show(() => <DialogEditProject project={p} />)}>
|
||||||
<DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item onSelect={() => layout.sidebar.toggleWorkspaces(p.worktree)}>
|
<DropdownMenu.Item onSelect={() => layout.sidebar.toggleWorkspaces(p.worktree)}>
|
||||||
<DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>
|
||||||
{layout.sidebar.workspaces(p.worktree)()
|
{layout.sidebar.workspaces(p.worktree)()
|
||||||
? language.t("sidebar.workspaces.disable")
|
? language.t("sidebar.workspaces.disable")
|
||||||
: language.t("sidebar.workspaces.enable")}
|
: language.t("sidebar.workspaces.enable")}
|
||||||
</DropdownMenu.ItemLabel>
|
</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Separator />
|
<DropdownMenu.Separator />
|
||||||
<DropdownMenu.Item onSelect={() => closeProject(p.worktree)}>
|
<DropdownMenu.Item onSelect={() => closeProject(p.worktree)}>
|
||||||
<DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</DropdownMenu.Portal>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -673,14 +673,14 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
...(sync.data.config.share !== "disabled"
|
...(sync.data.config.share !== "disabled"
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
id: "session.share",
|
id: "session.share",
|
||||||
title: language.t("command.session.share"),
|
title: language.t("command.session.share"),
|
||||||
description: language.t("command.session.share.description"),
|
description: language.t("command.session.share.description"),
|
||||||
category: language.t("command.category.session"),
|
category: language.t("command.category.session"),
|
||||||
slash: "share",
|
slash: "share",
|
||||||
disabled: !params.id || !!info()?.share?.url,
|
disabled: !params.id || !!info()?.share?.url,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
await sdk.client.session
|
await sdk.client.session
|
||||||
.share({ sessionID: params.id })
|
.share({ sessionID: params.id })
|
||||||
@ -708,14 +708,14 @@ export default function Page() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "session.unshare",
|
id: "session.unshare",
|
||||||
title: language.t("command.session.unshare"),
|
title: language.t("command.session.unshare"),
|
||||||
description: language.t("command.session.unshare.description"),
|
description: language.t("command.session.unshare.description"),
|
||||||
category: language.t("command.category.session"),
|
category: language.t("command.category.session"),
|
||||||
slash: "unshare",
|
slash: "unshare",
|
||||||
disabled: !params.id || !info()?.share?.url,
|
disabled: !params.id || !info()?.share?.url,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
await sdk.client.session
|
await sdk.client.session
|
||||||
.unshare({ sessionID: params.id })
|
.unshare({ sessionID: params.id })
|
||||||
@ -1262,7 +1262,9 @@ export default function Page() {
|
|||||||
<Show
|
<Show
|
||||||
when={diffsReady()}
|
when={diffsReady()}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="px-4 py-4 text-text-weak">{language.t("session.review.loadingChanges")}</div>
|
<div class="px-4 py-4 text-text-weak">
|
||||||
|
{language.t("session.review.loadingChanges")}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SessionReviewTab
|
<SessionReviewTab
|
||||||
@ -1283,13 +1285,15 @@ export default function Page() {
|
|||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<div class="h-full px-4 pb-30 flex flex-col items-center justify-center text-center gap-6">
|
<div class="h-full px-4 pb-30 flex flex-col items-center justify-center text-center gap-6">
|
||||||
<Mark class="w-14 opacity-10" />
|
<Mark class="w-14 opacity-10" />
|
||||||
<div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.empty")}</div>
|
<div class="text-14-regular text-text-weak max-w-56">
|
||||||
|
{language.t("session.review.empty")}
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</div>
|
||||||
</Switch>
|
</Match>
|
||||||
</div>
|
</Switch>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class="relative w-full h-full min-w-0">
|
<div class="relative w-full h-full min-w-0">
|
||||||
@ -1502,11 +1506,11 @@ export default function Page() {
|
|||||||
<Show when={diffs()}>
|
<Show when={diffs()}>
|
||||||
<DiffChanges changes={diffs()} variant="bars" />
|
<DiffChanges changes={diffs()} variant="bars" />
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<div>{language.t("session.tab.review")}</div>
|
<div>{language.t("session.tab.review")}</div>
|
||||||
<Show when={info()?.summary?.files}>
|
<Show when={info()?.summary?.files}>
|
||||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
||||||
{info()?.summary?.files ?? 0}
|
{info()?.summary?.files ?? 0}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
@ -1558,7 +1562,9 @@ export default function Page() {
|
|||||||
<Show
|
<Show
|
||||||
when={diffsReady()}
|
when={diffsReady()}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="px-6 py-4 text-text-weak">{language.t("session.review.loadingChanges")}</div>
|
<div class="px-6 py-4 text-text-weak">
|
||||||
|
{language.t("session.review.loadingChanges")}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SessionReviewTab
|
<SessionReviewTab
|
||||||
@ -1575,13 +1581,15 @@ export default function Page() {
|
|||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<div class="h-full px-6 pb-30 flex flex-col items-center justify-center text-center gap-6">
|
<div class="h-full px-6 pb-30 flex flex-col items-center justify-center text-center gap-6">
|
||||||
<Mark class="w-14 opacity-10" />
|
<Mark class="w-14 opacity-10" />
|
||||||
<div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.empty")}</div>
|
<div class="text-14-regular text-text-weak max-w-56">
|
||||||
|
{language.t("session.review.empty")}
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</div>
|
||||||
</Switch>
|
</Match>
|
||||||
</div>
|
</Switch>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
</Show>
|
</Show>
|
||||||
@ -1871,13 +1879,15 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<div class="text-text-weak pr-2">{language.t("common.loading")}...</div>
|
<div class="text-text-weak pr-2">{language.t("common.loading")}...</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 flex items-center justify-center text-text-weak">
|
||||||
|
{language.t("terminal.loading")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 flex items-center justify-center text-text-weak">{language.t("terminal.loading")}</div>
|
}
|
||||||
</div>
|
>
|
||||||
}
|
|
||||||
>
|
|
||||||
<DragDropProvider
|
<DragDropProvider
|
||||||
onDragStart={handleTerminalDragStart}
|
onDragStart={handleTerminalDragStart}
|
||||||
onDragEnd={handleTerminalDragEnd}
|
onDragEnd={handleTerminalDragEnd}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ function detectLocale() {
|
|||||||
function UiI18nBridge(props: ParentProps) {
|
function UiI18nBridge(props: ParentProps) {
|
||||||
const locale = createMemo(() => detectLocale())
|
const locale = createMemo(() => detectLocale())
|
||||||
const t = (key: keyof typeof uiEn, params?: UiI18nParams) => {
|
const t = (key: keyof typeof uiEn, params?: UiI18nParams) => {
|
||||||
const value = locale() === "zh" ? uiZh[key] ?? uiEn[key] : uiEn[key]
|
const value = locale() === "zh" ? (uiZh[key] ?? uiEn[key]) : uiEn[key]
|
||||||
const text = value ?? String(key)
|
const text = value ?? String(key)
|
||||||
return resolveTemplate(text, params)
|
return resolveTemplate(text, params)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -228,9 +228,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||||||
when={flat().length > 0}
|
when={flat().length > 0}
|
||||||
fallback={
|
fallback={
|
||||||
<div data-slot="list-empty-state">
|
<div data-slot="list-empty-state">
|
||||||
<div data-slot="list-message">
|
<div data-slot="list-message">{emptyMessage()}</div>
|
||||||
{emptyMessage()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -417,7 +417,11 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
|||||||
<Icon name="chevron-down" size="small" />
|
<Icon name="chevron-down" size="small" />
|
||||||
</button>
|
</button>
|
||||||
<div data-slot="user-message-copy-wrapper">
|
<div data-slot="user-message-copy-wrapper">
|
||||||
<Tooltip value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")} placement="top" gutter={8}>
|
<Tooltip
|
||||||
|
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
|
||||||
|
placement="top"
|
||||||
|
gutter={8}
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={copied() ? "check" : "copy"}
|
icon={copied() ? "check" : "copy"}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|||||||
@ -163,6 +163,7 @@ Completed (2026-01-20):
|
|||||||
### 15) File Load Failure Toast (Duplicate)
|
### 15) File Load Failure Toast (Duplicate)
|
||||||
|
|
||||||
Files:
|
Files:
|
||||||
|
|
||||||
- `packages/app/src/context/file.tsx`
|
- `packages/app/src/context/file.tsx`
|
||||||
- `packages/app/src/context/local.tsx`
|
- `packages/app/src/context/local.tsx`
|
||||||
|
|
||||||
@ -220,13 +221,17 @@ Also reuse existing command keys for tooltip titles whenever possible (e.g. `com
|
|||||||
## Appendix: Remaining Files At-a-Glance
|
## Appendix: Remaining Files At-a-Glance
|
||||||
|
|
||||||
Pages:
|
Pages:
|
||||||
|
|
||||||
- (none)
|
- (none)
|
||||||
|
|
||||||
Components:
|
Components:
|
||||||
|
|
||||||
- (none)
|
- (none)
|
||||||
|
|
||||||
Context:
|
Context:
|
||||||
|
|
||||||
- (none)
|
- (none)
|
||||||
|
|
||||||
Utils:
|
Utils:
|
||||||
|
|
||||||
- (none)
|
- (none)
|
||||||
|
|||||||
@ -27,7 +27,7 @@ Why this is the best long-term shape:
|
|||||||
|
|
||||||
### Proposed Architecture
|
### Proposed Architecture
|
||||||
|
|
||||||
1) **UI provides an i18n context (no persistence)**
|
1. **UI provides an i18n context (no persistence)**
|
||||||
|
|
||||||
- Add `packages/ui/src/context/i18n.tsx`:
|
- Add `packages/ui/src/context/i18n.tsx`:
|
||||||
- Exports `I18nProvider` and `useI18n()`.
|
- Exports `I18nProvider` and `useI18n()`.
|
||||||
@ -36,14 +36,14 @@ Why this is the best long-term shape:
|
|||||||
- `locale()` accessor for locale-sensitive formatting (Luxon/Intl).
|
- `locale()` accessor for locale-sensitive formatting (Luxon/Intl).
|
||||||
- Context should have a safe default (English) so UI components can render even if a consumer forgets the provider.
|
- Context should have a safe default (English) so UI components can render even if a consumer forgets the provider.
|
||||||
|
|
||||||
2) **UI owns UI strings (dictionaries live in UI)**
|
2. **UI owns UI strings (dictionaries live in UI)**
|
||||||
|
|
||||||
- Add `packages/ui/src/i18n/en.ts` and `packages/ui/src/i18n/zh.ts`.
|
- Add `packages/ui/src/i18n/en.ts` and `packages/ui/src/i18n/zh.ts`.
|
||||||
- Export them from `@opencode-ai/ui` via `packages/ui/package.json` exports (e.g. `"./i18n/*": "./src/i18n/*.ts"`).
|
- Export them from `@opencode-ai/ui` via `packages/ui/package.json` exports (e.g. `"./i18n/*": "./src/i18n/*.ts"`).
|
||||||
- Use a clear namespace prefix for all UI keys to avoid collisions:
|
- Use a clear namespace prefix for all UI keys to avoid collisions:
|
||||||
- Recommended: `ui.*` (e.g. `ui.sessionReview.title`).
|
- Recommended: `ui.*` (e.g. `ui.sessionReview.title`).
|
||||||
|
|
||||||
3) **Consumers merge dictionaries and provide `t`/`locale` once**
|
3. **Consumers merge dictionaries and provide `t`/`locale` once**
|
||||||
|
|
||||||
- `packages/app/`:
|
- `packages/app/`:
|
||||||
- Keep `packages/app/src/context/language.tsx` as the source of truth for locale selection/persistence.
|
- Keep `packages/app/src/context/language.tsx` as the source of truth for locale selection/persistence.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user