feat(app): support locale plural rules (#40370)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
c7aadc83e1
commit
b1553c2b5c
@@ -1097,22 +1097,16 @@ export function ContextToolGroup(props: {
|
||||
<AnimatedCountList
|
||||
items={[
|
||||
{
|
||||
key: "read",
|
||||
key: "ui.messagePart.context.read",
|
||||
count: summary().read,
|
||||
one: i18n.t("ui.messagePart.context.read.one"),
|
||||
other: i18n.t("ui.messagePart.context.read.other"),
|
||||
},
|
||||
{
|
||||
key: "search",
|
||||
key: "ui.messagePart.context.search",
|
||||
count: summary().search,
|
||||
one: i18n.t("ui.messagePart.context.search.one"),
|
||||
other: i18n.t("ui.messagePart.context.search.other"),
|
||||
},
|
||||
{
|
||||
key: "list",
|
||||
key: "ui.messagePart.context.list",
|
||||
count: summary().list,
|
||||
one: i18n.t("ui.messagePart.context.list.one"),
|
||||
other: i18n.t("ui.messagePart.context.list.other"),
|
||||
},
|
||||
]}
|
||||
fallback=""
|
||||
|
||||
@@ -441,10 +441,7 @@ export function SessionTurn(
|
||||
>
|
||||
<div data-slot="session-turn-diffs-header">
|
||||
<span data-slot="session-turn-diffs-label">
|
||||
{i18n.t(
|
||||
edited() === 1 ? "ui.sessionTurn.diffs.changed.one" : "ui.sessionTurn.diffs.changed.other",
|
||||
{ count: String(edited()) },
|
||||
)}
|
||||
{i18n.plural("ui.sessionTurn.diffs.changed", edited())}
|
||||
</span>
|
||||
<DiffChanges changes={diffs()} />
|
||||
<Show when={overflow() > 0}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createMemo } from "solid-js"
|
||||
import { AnimatedNumber } from "@opencode-ai/ui/animated-number"
|
||||
import { pluralCategory, pluralKey, useI18n, type UiI18nPluralKey } from "@opencode-ai/ui/context"
|
||||
|
||||
function split(text: string) {
|
||||
const match = /{{\s*count\s*}}/.exec(text)
|
||||
@@ -23,14 +24,16 @@ function common(one: string, other: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function AnimatedCountLabel(props: { count: number; one: string; other: string; class?: string }) {
|
||||
const one = createMemo(() => split(props.one))
|
||||
const other = createMemo(() => split(props.other))
|
||||
const singular = createMemo(() => Math.round(props.count) === 1)
|
||||
const active = createMemo(() => (singular() ? one() : other()))
|
||||
export function AnimatedCountLabel(props: { count: number; plural: UiI18nPluralKey; class?: string }) {
|
||||
const i18n = useI18n()
|
||||
const category = createMemo(() => pluralCategory(i18n.locale(), Math.round(props.count)))
|
||||
const one = createMemo(() => split(i18n.t(pluralKey(props.plural, "one"))))
|
||||
const other = createMemo(() => split(i18n.t(pluralKey(props.plural, "other"))))
|
||||
const active = createMemo(() => split(i18n.t(pluralKey(props.plural, category()))))
|
||||
const suffix = createMemo(() => common(one().after, other().after))
|
||||
const splitSuffix = createMemo(
|
||||
() =>
|
||||
(category() === "one" || category() === "other") &&
|
||||
one().before === other().before &&
|
||||
(one().after.startsWith(other().after) || other().after.startsWith(one().after)),
|
||||
)
|
||||
@@ -38,7 +41,7 @@ export function AnimatedCountLabel(props: { count: number; one: string; other: s
|
||||
const stem = createMemo(() => (splitSuffix() ? suffix().stem : active().after))
|
||||
const tail = createMemo(() => {
|
||||
if (!splitSuffix()) return ""
|
||||
if (singular()) return suffix().one
|
||||
if (category() === "one") return suffix().one
|
||||
return suffix().other
|
||||
})
|
||||
const showTail = createMemo(() => splitSuffix() && tail().length > 0)
|
||||
|
||||
@@ -25,9 +25,6 @@ as it appears in the context tool group on the session page.`,
|
||||
const TEXT = {
|
||||
active: "Exploring",
|
||||
done: "Explored",
|
||||
read: { one: "{{count}} read", other: "{{count}} reads" },
|
||||
search: { one: "{{count}} search", other: "{{count}} searches" },
|
||||
list: { one: "{{count}} list", other: "{{count}} lists" },
|
||||
} as const
|
||||
|
||||
function rand(min: number, max: number) {
|
||||
@@ -118,9 +115,9 @@ export const Playground = {
|
||||
}
|
||||
|
||||
const items = (): CountItem[] => [
|
||||
{ key: "read", count: reads(), one: TEXT.read.one, other: TEXT.read.other },
|
||||
{ key: "search", count: searches(), one: TEXT.search.one, other: TEXT.search.other },
|
||||
{ key: "list", count: lists(), one: TEXT.list.one, other: TEXT.list.other },
|
||||
{ key: "ui.messagePart.context.read", count: reads() },
|
||||
{ key: "ui.messagePart.context.search", count: searches() },
|
||||
{ key: "ui.messagePart.context.list", count: lists() },
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -210,8 +207,8 @@ export const Empty = {
|
||||
<ToolStatusTitle active activeText="Exploring" doneText="Explored" split={false} />
|
||||
<AnimatedCountList
|
||||
items={[
|
||||
{ key: "read", count: 0, one: "{{count}} read", other: "{{count}} reads" },
|
||||
{ key: "search", count: 0, one: "{{count}} search", other: "{{count}} searches" },
|
||||
{ key: "ui.messagePart.context.read", count: 0 },
|
||||
{ key: "ui.messagePart.context.search", count: 0 },
|
||||
]}
|
||||
fallback=""
|
||||
/>
|
||||
@@ -226,9 +223,9 @@ export const Done = {
|
||||
<span style={{ "font-weight": "400", color: "var(--text-base, #ccc)" }}>
|
||||
<AnimatedCountList
|
||||
items={[
|
||||
{ key: "read", count: 5, one: "{{count}} read", other: "{{count}} reads" },
|
||||
{ key: "search", count: 3, one: "{{count}} search", other: "{{count}} searches" },
|
||||
{ key: "list", count: 1, one: "{{count}} list", other: "{{count}} lists" },
|
||||
{ key: "ui.messagePart.context.read", count: 5 },
|
||||
{ key: "ui.messagePart.context.search", count: 3 },
|
||||
{ key: "ui.messagePart.context.list", count: 1 },
|
||||
]}
|
||||
fallback=""
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Index, createMemo } from "solid-js"
|
||||
import type { UiI18nPluralKey } from "@opencode-ai/ui/context"
|
||||
import { AnimatedCountLabel } from "./tool-count-label"
|
||||
|
||||
export type CountItem = {
|
||||
key: string
|
||||
key: UiI18nPluralKey
|
||||
count: number
|
||||
one: string
|
||||
other: string
|
||||
}
|
||||
|
||||
export function AnimatedCountList(props: { items: CountItem[]; fallback?: string; class?: string }) {
|
||||
@@ -36,11 +35,7 @@ export function AnimatedCountList(props: { items: CountItem[]; fallback?: string
|
||||
</span>
|
||||
<span data-slot="tool-count-summary-item" data-active={active() ? "true" : "false"}>
|
||||
<span data-slot="tool-count-summary-item-inner">
|
||||
<AnimatedCountLabel
|
||||
one={item().one}
|
||||
other={item().other}
|
||||
count={Math.max(0, Math.round(item().count))}
|
||||
/>
|
||||
<AnimatedCountLabel plural={item().key} count={Math.max(0, Math.round(item().count))} />
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user