chore(app): i18n sync (#17283)

This commit is contained in:
Adam
2026-03-13 06:48:38 -05:00
committed by GitHub
parent 270cb0b8b4
commit 05cb3c87ca
65 changed files with 1776 additions and 156 deletions

View File

@@ -1,5 +1,6 @@
import { createEffect, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js"
import { animate, type AnimationPlaybackControls } from "motion"
import { useI18n } from "../context/i18n"
import { createStore } from "solid-js/store"
import { Collapsible } from "./collapsible"
import type { IconProps } from "./icon"
@@ -233,12 +234,14 @@ export function GenericTool(props: {
hideDetails?: boolean
input?: Record<string, unknown>
}) {
const i18n = useI18n()
return (
<BasicTool
icon="mcp"
status={props.status}
trigger={{
title: `Called \`${props.tool}\``,
title: i18n.t("ui.basicTool.called", { tool: props.tool }),
subtitle: label(props.input),
args: args(props.input),
}}

View File

@@ -1,4 +1,5 @@
import { Portal } from "solid-js/web"
import { useI18n } from "../context/i18n"
import { Icon } from "./icon"
export function FileSearchBar(props: {
@@ -13,6 +14,8 @@ export function FileSearchBar(props: {
onPrev: () => void
onNext: () => void
}) {
const i18n = useI18n()
return (
<Portal>
<div
@@ -26,7 +29,7 @@ export function FileSearchBar(props: {
<Icon name="magnifying-glass" size="small" class="text-text-weak shrink-0" />
<input
ref={props.setInput}
placeholder="Find"
placeholder={i18n.t("ui.fileSearch.placeholder")}
value={props.query()}
class="w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
onInput={(e) => props.onInput(e.currentTarget.value)}
@@ -40,7 +43,7 @@ export function FileSearchBar(props: {
type="button"
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
disabled={props.count() === 0}
aria-label="Previous match"
aria-label={i18n.t("ui.fileSearch.previousMatch")}
onClick={props.onPrev}
>
<Icon name="chevron-down" size="small" class="rotate-180" />
@@ -49,7 +52,7 @@ export function FileSearchBar(props: {
type="button"
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
disabled={props.count() === 0}
aria-label="Next match"
aria-label={i18n.t("ui.fileSearch.nextMatch")}
onClick={props.onNext}
>
<Icon name="chevron-down" size="small" />
@@ -58,7 +61,7 @@ export function FileSearchBar(props: {
<button
type="button"
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
aria-label="Close search"
aria-label={i18n.t("ui.fileSearch.close")}
onClick={props.onClose}
>
<Icon name="close-small" size="small" />

View File

@@ -2,6 +2,7 @@ import { type DiffLineAnnotation, type SelectedLineRange } from "@pierre/diffs"
import { createEffect, createMemo, createSignal, onCleanup, Show, type Accessor, type JSX } from "solid-js"
import { createStore } from "solid-js/store"
import { render as renderSolid } from "solid-js/web"
import { useI18n } from "../context/i18n"
import { createHoverCommentUtility } from "../pierre/comment-hover"
import { cloneSelectedLineRange, formatSelectedLineLabel, lineInSelectedRange } from "../pierre/selection-bridge"
import { LineComment, LineCommentEditor } from "./line-comment"
@@ -341,6 +342,7 @@ export function createLineCommentController<T extends LineCommentShape>(
export function createLineCommentController<T extends LineCommentShape>(
props: LineCommentControllerProps<T> | LineCommentControllerWithSideProps<T>,
) {
const i18n = useI18n()
const note = createLineCommentState<string>(props.state)
const annotations =
@@ -376,7 +378,7 @@ export function createLineCommentController<T extends LineCommentShape>(
return note.isOpen(comment.id) || note.isEditing(comment.id)
},
comment: comment.comment,
selection: formatSelectedLineLabel(comment.selection),
selection: formatSelectedLineLabel(comment.selection, i18n.t),
get actions() {
return props.renderCommentActions?.(comment, { edit, remove })
},
@@ -386,7 +388,7 @@ export function createLineCommentController<T extends LineCommentShape>(
get value() {
return note.draft()
},
selection: formatSelectedLineLabel(comment.selection),
selection: formatSelectedLineLabel(comment.selection, i18n.t),
onInput: note.setDraft,
onCancel: note.cancelDraft,
onSubmit: (value: string) => {
@@ -412,7 +414,7 @@ export function createLineCommentController<T extends LineCommentShape>(
get value() {
return note.draft()
},
selection: formatSelectedLineLabel(range),
selection: formatSelectedLineLabel(range, i18n.t),
onInput: note.setDraft,
onCancel: note.cancelDraft,
onSubmit: (comment) => {

View File

@@ -322,7 +322,7 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
case "skill":
return {
icon: "brain",
title: input.name || "skill",
title: input.name || i18n.t("ui.tool.skill"),
}
default:
return {
@@ -924,15 +924,12 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
const match = data.store.provider?.all?.find((p) => p.id === providerID)
return match?.models?.[modelID]?.name ?? modelID
})
const timefmt = createMemo(() => new Intl.DateTimeFormat(i18n.locale(), { timeStyle: "short" }))
const stamp = createMemo(() => {
const created = props.message.time?.created
if (typeof created !== "number") return ""
const date = new Date(created)
const hours = date.getHours()
const hour12 = hours % 12 || 12
const minute = String(date.getMinutes()).padStart(2, "0")
return `${hour12}:${minute} ${hours < 12 ? "AM" : "PM"}`
return timefmt().format(created)
})
const metaHead = createMemo(() => {
@@ -1318,6 +1315,7 @@ PART_MAPPING["compaction"] = function CompactionPartDisplay() {
PART_MAPPING["text"] = function TextPartDisplay(props) {
const data = useData()
const i18n = useI18n()
const numfmt = createMemo(() => new Intl.NumberFormat(i18n.locale()))
const part = () => props.part as TextPart
const interrupted = createMemo(
() =>
@@ -1343,10 +1341,13 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
: -1
if (!(ms >= 0)) return ""
const total = Math.round(ms / 1000)
if (total < 60) return `${total}s`
if (total < 60) return i18n.t("ui.message.duration.seconds", { count: numfmt().format(total) })
const minutes = Math.floor(total / 60)
const seconds = total % 60
return `${minutes}m ${seconds}s`
return i18n.t("ui.message.duration.minutesSeconds", {
minutes: numfmt().format(minutes),
seconds: numfmt().format(seconds),
})
})
const meta = createMemo(() => {
@@ -2206,7 +2207,8 @@ ToolRegistry.register({
ToolRegistry.register({
name: "skill",
render(props) {
const title = createMemo(() => props.input.name || "skill")
const i18n = useI18n()
const title = createMemo(() => props.input.name || i18n.t("ui.tool.skill"))
const running = createMemo(() => props.status === "pending" || props.status === "running")
const titleContent = () => <TextShimmer text={title()} active={running()} />

View File

@@ -30,7 +30,7 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
list: "ui.tool.list",
glob: "ui.tool.glob",
grep: "ui.tool.grep",
task: "Task",
task: "ui.tool.task",
webfetch: "ui.tool.webfetch",
websearch: "ui.tool.websearch",
codesearch: "ui.tool.codesearch",
@@ -54,10 +54,10 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
const subtitle = createMemo(() => {
if (split.subtitle) return split.subtitle
const parts = tail().split(": ")
if (parts.length <= 1) return "Failed"
if (parts.length <= 1) return i18n.t("ui.toolErrorCard.failed")
const head = (parts[0] ?? "").trim()
if (!head) return "Failed"
return head[0] ? head[0].toUpperCase() + head.slice(1) : "Failed"
if (!head) return i18n.t("ui.toolErrorCard.failed")
return head[0] ? head[0].toUpperCase() + head.slice(1) : i18n.t("ui.toolErrorCard.failed")
})
const body = createMemo(() => {
@@ -116,7 +116,11 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
<div data-slot="tool-error-card-content">
<Show when={open()}>
<div data-slot="tool-error-card-copy">
<Tooltip value={copied() ? i18n.t("ui.message.copied") : "Copy error"} placement="top" gutter={4}>
<Tooltip
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.toolErrorCard.copyError")}
placement="top"
gutter={4}
>
<IconButton
icon={copied() ? "check" : "copy"}
size="normal"
@@ -126,7 +130,7 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
e.stopPropagation()
copy()
}}
aria-label={copied() ? i18n.t("ui.message.copied") : "Copy error"}
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.toolErrorCard.copyError")}
/>
</Tooltip>
</div>