diff options
| author | Filip <[email protected]> | 2026-02-13 12:08:13 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-13 05:08:13 -0600 |
| commit | ebb907d646022d2e7bb8effc164e1f09943d64a9 (patch) | |
| tree | 65662b4834701f2dc1e2e46da7ed5206341f8ba3 /packages/ui | |
| parent | b8ee88212639ec63f4fe87555b5e87f74643e76b (diff) | |
| download | opencode-ebb907d646022d2e7bb8effc164e1f09943d64a9.tar.gz opencode-ebb907d646022d2e7bb8effc164e1f09943d64a9.zip | |
fix(desktop): performance optimization for showing large diff & files (#13460)
Diffstat (limited to 'packages/ui')
| -rw-r--r-- | packages/ui/src/components/code.tsx | 118 | ||||
| -rw-r--r-- | packages/ui/src/components/diff.tsx | 38 | ||||
| -rw-r--r-- | packages/ui/src/components/session-review.css | 26 | ||||
| -rw-r--r-- | packages/ui/src/components/session-review.tsx | 228 | ||||
| -rw-r--r-- | packages/ui/src/i18n/ar.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/br.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/bs.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/da.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/de.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/en.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/es.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/fr.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/ja.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/ko.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/no.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/pl.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/ru.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/th.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/zh.ts | 5 | ||||
| -rw-r--r-- | packages/ui/src/i18n/zht.ts | 5 |
20 files changed, 370 insertions, 120 deletions
diff --git a/packages/ui/src/components/code.tsx b/packages/ui/src/components/code.tsx index abe0d7ca9..837cc5337 100644 --- a/packages/ui/src/components/code.tsx +++ b/packages/ui/src/components/code.tsx @@ -1,10 +1,27 @@ -import { type FileContents, File, FileOptions, LineAnnotation, type SelectedLineRange } from "@pierre/diffs" +import { + DEFAULT_VIRTUAL_FILE_METRICS, + type FileContents, + File, + FileOptions, + LineAnnotation, + type SelectedLineRange, + type VirtualFileMetrics, + VirtualizedFile, + Virtualizer, +} from "@pierre/diffs" import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMount, Show, splitProps } from "solid-js" import { Portal } from "solid-js/web" import { createDefaultOptions, styleVariables } from "../pierre" import { getWorkerPool } from "../pierre/worker" import { Icon } from "./icon" +const VIRTUALIZE_BYTES = 500_000 +const codeMetrics = { + ...DEFAULT_VIRTUAL_FILE_METRICS, + lineHeight: 24, + fileGap: 0, +} satisfies Partial<VirtualFileMetrics> + type SelectionSide = "additions" | "deletions" export type CodeProps<T = {}> = FileOptions<T> & { @@ -160,16 +177,28 @@ export function Code<T>(props: CodeProps<T>) { const [findPos, setFindPos] = createSignal<{ top: number; right: number }>({ top: 8, right: 8 }) - const file = createMemo( - () => - new File<T>( - { - ...createDefaultOptions<T>("unified"), - ...others, - }, - getWorkerPool("unified"), - ), - ) + let instance: File<T> | VirtualizedFile<T> | undefined + let virtualizer: Virtualizer | undefined + let virtualRoot: Document | HTMLElement | undefined + + const bytes = createMemo(() => { + const value = local.file.contents as unknown + if (typeof value === "string") return value.length + if (Array.isArray(value)) { + return value.reduce( + (acc, part) => acc + (typeof part === "string" ? part.length + 1 : String(part).length + 1), + 0, + ) + } + if (value == null) return 0 + return String(value).length + }) + const virtual = createMemo(() => bytes() > VIRTUALIZE_BYTES) + + const options = createMemo(() => ({ + ...createDefaultOptions<T>("unified"), + ...others, + })) const getRoot = () => { const host = container.querySelector("diffs-container") @@ -577,6 +606,14 @@ export function Code<T>(props: CodeProps<T>) { } const applySelection = (range: SelectedLineRange | null) => { + const current = instance + if (!current) return false + + if (virtual()) { + current.setSelectedLines(range) + return true + } + const root = getRoot() if (!root) return false @@ -584,7 +621,7 @@ export function Code<T>(props: CodeProps<T>) { if (root.querySelectorAll("[data-line]").length < lines) return false if (!range) { - file().setSelectedLines(null) + current.setSelectedLines(null) return true } @@ -592,12 +629,12 @@ export function Code<T>(props: CodeProps<T>) { const end = Math.max(range.start, range.end) if (start < 1 || end > lines) { - file().setSelectedLines(null) + current.setSelectedLines(null) return true } if (!root.querySelector(`[data-line="${start}"]`) || !root.querySelector(`[data-line="${end}"]`)) { - file().setSelectedLines(null) + current.setSelectedLines(null) return true } @@ -608,7 +645,7 @@ export function Code<T>(props: CodeProps<T>) { return { start: range.start, end: range.end } })() - file().setSelectedLines(normalized) + current.setSelectedLines(normalized) return true } @@ -619,9 +656,12 @@ export function Code<T>(props: CodeProps<T>) { const token = renderToken - const lines = lineCount() + const lines = virtual() ? undefined : lineCount() - const isReady = (root: ShadowRoot) => root.querySelectorAll("[data-line]").length >= lines + const isReady = (root: ShadowRoot) => + virtual() + ? root.querySelector("[data-line]") != null + : root.querySelectorAll("[data-line]").length >= (lines ?? 0) const notify = () => { if (token !== renderToken) return @@ -844,20 +884,41 @@ export function Code<T>(props: CodeProps<T>) { } createEffect(() => { - const current = file() + const opts = options() + const workerPool = getWorkerPool("unified") + const isVirtual = virtual() - onCleanup(() => { - current.cleanUp() - }) - }) - - createEffect(() => { observer?.disconnect() observer = undefined + instance?.cleanUp() + instance = undefined + + if (!isVirtual && virtualizer) { + virtualizer.cleanUp() + virtualizer = undefined + virtualRoot = undefined + } + + const v = (() => { + if (!isVirtual) return + if (typeof document === "undefined") return + + const root = getScrollParent(wrapper) ?? document + if (virtualizer && virtualRoot === root) return virtualizer + + virtualizer?.cleanUp() + virtualizer = new Virtualizer() + virtualRoot = root + virtualizer.setup(root, root instanceof Document ? undefined : wrapper) + return virtualizer + })() + + instance = isVirtual && v ? new VirtualizedFile<T>(opts, v, codeMetrics, workerPool) : new File<T>(opts, workerPool) + container.innerHTML = "" const value = text() - file().render({ + instance.render({ file: typeof local.file.contents === "string" ? local.file : { ...local.file, contents: value }, lineAnnotations: local.annotations, containerWrapper: container, @@ -910,6 +971,13 @@ export function Code<T>(props: CodeProps<T>) { onCleanup(() => { observer?.disconnect() + instance?.cleanUp() + instance = undefined + + virtualizer?.cleanUp() + virtualizer = undefined + virtualRoot = undefined + clearOverlayScroll() clearOverlay() if (findCurrent === host) { diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx index 0966db75e..0002232b0 100644 --- a/packages/ui/src/components/diff.tsx +++ b/packages/ui/src/components/diff.tsx @@ -1,5 +1,5 @@ -import { checksum } from "@opencode-ai/util/encode" -import { FileDiff, type SelectedLineRange, VirtualizedFileDiff } from "@pierre/diffs" +import { sampledChecksum } from "@opencode-ai/util/encode" +import { FileDiff, type FileDiffOptions, type SelectedLineRange, VirtualizedFileDiff } from "@pierre/diffs" import { createMediaQuery } from "@solid-primitives/media" import { createEffect, createMemo, createSignal, onCleanup, splitProps } from "solid-js" import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre" @@ -78,14 +78,29 @@ export function Diff<T>(props: DiffProps<T>) { const mobile = createMediaQuery("(max-width: 640px)") - const options = createMemo(() => { - const opts = { + const large = createMemo(() => { + const before = typeof local.before?.contents === "string" ? local.before.contents : "" + const after = typeof local.after?.contents === "string" ? local.after.contents : "" + return Math.max(before.length, after.length) > 500_000 + }) + + const largeOptions = { + lineDiffType: "none", + maxLineDiffLength: 0, + tokenizeMaxLineLength: 1, + } satisfies Pick<FileDiffOptions<T>, "lineDiffType" | "maxLineDiffLength" | "tokenizeMaxLineLength"> + + const options = createMemo<FileDiffOptions<T>>(() => { + const base = { ...createDefaultOptions(props.diffStyle), ...others, } - if (!mobile()) return opts + + const perf = large() ? { ...base, ...largeOptions } : base + if (!mobile()) return perf + return { - ...opts, + ...perf, disableLineNumbers: true, } }) @@ -528,12 +543,17 @@ export function Diff<T>(props: DiffProps<T>) { createEffect(() => { const opts = options() - const workerPool = getWorkerPool(props.diffStyle) + const workerPool = large() ? getWorkerPool("unified") : getWorkerPool(props.diffStyle) const virtualizer = getVirtualizer() const annotations = local.annotations const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : "" const afterContents = typeof local.after?.contents === "string" ? local.after.contents : "" + const cacheKey = (contents: string) => { + if (!large()) return sampledChecksum(contents, contents.length) + return sampledChecksum(contents) + } + instance?.cleanUp() instance = virtualizer ? new VirtualizedFileDiff<T>(opts, virtualizer, virtualMetrics, workerPool) @@ -545,12 +565,12 @@ export function Diff<T>(props: DiffProps<T>) { oldFile: { ...local.before, contents: beforeContents, - cacheKey: checksum(beforeContents), + cacheKey: cacheKey(beforeContents), }, newFile: { ...local.after, contents: afterContents, - cacheKey: checksum(afterContents), + cacheKey: cacheKey(afterContents), }, lineAnnotations: annotations, containerWrapper: container, diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css index 30bfe3b71..46473b75e 100644 --- a/packages/ui/src/components/session-review.css +++ b/packages/ui/src/components/session-review.css @@ -222,4 +222,30 @@ --line-comment-popover-z: 30; --line-comment-open-z: 6; } + + [data-slot="session-review-large-diff"] { + padding: 12px; + background: var(--background-stronger); + } + + [data-slot="session-review-large-diff-title"] { + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); + color: var(--text-strong); + margin-bottom: 4px; + } + + [data-slot="session-review-large-diff-meta"] { + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + color: var(--text-weak); + word-break: break-word; + } + + [data-slot="session-review-large-diff-actions"] { + display: flex; + gap: 8px; + margin-top: 10px; + } } diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index fe2475548..5f1e6b1ab 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -17,6 +17,26 @@ import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" import { type SelectedLineRange } from "@pierre/diffs" import { Dynamic } from "solid-js/web" +const MAX_DIFF_LINES = 20_000 +const MAX_DIFF_BYTES = 2_000_000 + +function linesOver(text: string, max: number) { + let lines = 1 + for (let i = 0; i < text.length; i++) { + if (text.charCodeAt(i) !== 10) continue + lines++ + if (lines > max) return true + } + return lines > max +} + +function formatBytes(bytes: number) { + if (!Number.isFinite(bytes) || bytes <= 0) return "0 B" + if (bytes < 1024) return `${bytes} B` + if (bytes < 1024 * 1024) return `${Math.round((bytes / 1024) * 10) / 10} KB` + return `${Math.round((bytes / (1024 * 1024)) * 10) / 10} MB` +} + export type SessionReviewDiffStyle = "unified" | "split" export type SessionReviewComment = { @@ -326,12 +346,28 @@ export const SessionReview = (props: SessionReviewProps) => { {(diff) => { let wrapper: HTMLDivElement | undefined + const expanded = createMemo(() => open().includes(diff.file)) + const [force, setForce] = createSignal(false) + const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === diff.file)) const commentedLines = createMemo(() => comments().map((c) => c.selection)) const beforeText = () => (typeof diff.before === "string" ? diff.before : "") const afterText = () => (typeof diff.after === "string" ? diff.after : "") + const tooLarge = createMemo(() => { + if (!expanded()) return false + if (force()) return false + if (isImageFile(diff.file)) return false + + const before = beforeText() + const after = afterText() + + if (before.length > MAX_DIFF_BYTES || after.length > MAX_DIFF_BYTES) return true + if (linesOver(before, MAX_DIFF_LINES) || linesOver(after, MAX_DIFF_LINES)) return true + return false + }) + const isAdded = () => diff.status === "added" || (beforeText().length === 0 && afterText().length > 0) const isDeleted = () => diff.status === "deleted" || (afterText().length === 0 && beforeText().length > 0) @@ -571,94 +607,114 @@ export const SessionReview = (props: SessionReviewProps) => { scheduleAnchors() }} > - <Switch> - <Match when={isImage() && imageSrc()}> - <div data-slot="session-review-image-container"> - <img data-slot="session-review-image" src={imageSrc()} alt={diff.file} /> - </div> - </Match> - <Match when={isImage() && isDeleted()}> - <div data-slot="session-review-image-container" data-removed> - <span data-slot="session-review-image-placeholder"> - {i18n.t("ui.sessionReview.change.removed")} - </span> - </div> - </Match> - <Match when={isImage() && !imageSrc()}> - <div data-slot="session-review-image-container"> - <span data-slot="session-review-image-placeholder"> - {imageStatus() === "loading" ? "Loading..." : "Image"} - </span> - </div> - </Match> - <Match when={!isImage()}> - <Dynamic - component={diffComponent} - preloadedDiff={diff.preloaded} - diffStyle={diffStyle()} - onRendered={() => { - props.onDiffRendered?.() - scheduleAnchors() - }} - enableLineSelection={props.onLineComment != null} - onLineSelected={handleLineSelected} - onLineSelectionEnd={handleLineSelectionEnd} - selectedLines={selectedLines()} - commentedLines={commentedLines()} - before={{ - name: diff.file!, - contents: typeof diff.before === "string" ? diff.before : "", - }} - after={{ - name: diff.file!, - contents: typeof diff.after === "string" ? diff.after : "", - }} - /> - </Match> - </Switch> - - <For each={comments()}> - {(comment) => ( - <LineComment - id={comment.id} - top={positions()[comment.id]} - onMouseEnter={() => setSelection({ file: comment.file, range: comment.selection })} - onClick={() => { - if (isCommentOpen(comment)) { - setOpened(null) - return - } - - openComment(comment) - }} - open={isCommentOpen(comment)} - comment={comment.comment} - selection={selectionLabel(comment.selection)} - /> - )} - </For> - - <Show when={draftRange()}> - {(range) => ( - <Show when={draftTop() !== undefined}> - <LineCommentEditor - top={draftTop()} - value={draft()} - selection={selectionLabel(range())} - onInput={setDraft} - onCancel={() => setCommenting(null)} - onSubmit={(comment) => { - props.onLineComment?.({ - file: diff.file, - selection: range(), - comment, - preview: selectionPreview(diff, range()), - }) - setCommenting(null) + <Show when={expanded()}> + <Switch> + <Match when={isImage() && imageSrc()}> + <div data-slot="session-review-image-container"> + <img data-slot="session-review-image" src={imageSrc()} alt={diff.file} /> + </div> + </Match> + <Match when={isImage() && isDeleted()}> + <div data-slot="session-review-image-container" data-removed> + <span data-slot="session-review-image-placeholder"> + {i18n.t("ui.sessionReview.change.removed")} + </span> + </div> + </Match> + <Match when={isImage() && !imageSrc()}> + <div data-slot="session-review-image-container"> + <span data-slot="session-review-image-placeholder"> + {imageStatus() === "loading" + ? i18n.t("ui.sessionReview.image.loading") + : i18n.t("ui.sessionReview.image.placeholder")} + </span> + </div> + </Match> + <Match when={!isImage() && tooLarge()}> + <div data-slot="session-review-large-diff"> + <div data-slot="session-review-large-diff-title"> + {i18n.t("ui.sessionReview.largeDiff.title")} + </div> + <div data-slot="session-review-large-diff-meta"> + Limit: {MAX_DIFF_LINES.toLocaleString()} lines / {formatBytes(MAX_DIFF_BYTES)}. + Current: {formatBytes(Math.max(beforeText().length, afterText().length))}. + </div> + <div data-slot="session-review-large-diff-actions"> + <Button size="normal" variant="secondary" onClick={() => setForce(true)}> + {i18n.t("ui.sessionReview.largeDiff.renderAnyway")} + </Button> + </div> + </div> + </Match> + <Match when={!isImage()}> + <Dynamic + component={diffComponent} + preloadedDiff={diff.preloaded} + diffStyle={diffStyle()} + onRendered={() => { + props.onDiffRendered?.() + scheduleAnchors() + }} + enableLineSelection={props.onLineComment != null} + onLineSelected={handleLineSelected} + onLineSelectionEnd={handleLineSelectionEnd} + selectedLines={selectedLines()} + commentedLines={commentedLines()} + before={{ + name: diff.file!, + contents: typeof diff.before === "string" ? diff.before : "", }} + after={{ + name: diff.file!, + contents: typeof diff.after === "string" ? diff.after : "", + }} + /> + </Match> + </Switch> + + <For each={comments()}> + {(comment) => ( + <LineComment + id={comment.id} + top={positions()[comment.id]} + onMouseEnter={() => setSelection({ file: comment.file, range: comment.selection })} + onClick={() => { + if (isCommentOpen(comment)) { + setOpened(null) + return + } + + openComment(comment) + }} + open={isCommentOpen(comment)} + comment={comment.comment} + selection={selectionLabel(comment.selection)} /> - </Show> - )} + )} + </For> + + <Show when={draftRange()}> + {(range) => ( + <Show when={draftTop() !== undefined}> + <LineCommentEditor + top={draftTop()} + value={draft()} + selection={selectionLabel(range())} + onInput={setDraft} + onCancel={() => setCommenting(null)} + onSubmit={(comment) => { + props.onLineComment?.({ + file: diff.file, + selection: range(), + comment, + preview: selectionPreview(diff, range()), + }) + setCommenting(null) + }} + /> + </Show> + )} + </Show> </Show> </div> </Accordion.Content> diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 7ee17e2e0..9a6c8dcbd 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -8,6 +8,11 @@ export const dict = { "ui.sessionReview.change.added": "مضاف", "ui.sessionReview.change.removed": "محذوف", "ui.sessionReview.change.modified": "معدل", + "ui.sessionReview.image.loading": "جار التحميل...", + "ui.sessionReview.image.placeholder": "صورة", + "ui.sessionReview.largeDiff.title": "Diff كبير جدا لعرضه", + "ui.sessionReview.largeDiff.meta": "الحد: {{lines}} سطر / {{limit}}. الحالي: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "اعرض على أي حال", "ui.lineComment.label.prefix": "تعليق على ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 6d7449d84..148b0ae17 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -8,6 +8,11 @@ export const dict = { "ui.sessionReview.change.added": "Adicionado", "ui.sessionReview.change.removed": "Removido", "ui.sessionReview.change.modified": "Modificado", + "ui.sessionReview.image.loading": "Carregando...", + "ui.sessionReview.image.placeholder": "Imagem", + "ui.sessionReview.largeDiff.title": "Diff grande demais para renderizar", + "ui.sessionReview.largeDiff.meta": "Limite: {{lines}} linhas / {{limit}}. Atual: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Renderizar mesmo assim", "ui.lineComment.label.prefix": "Comentar em ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 24e4c1206..7614af087 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -12,6 +12,11 @@ export const dict = { "ui.sessionReview.change.added": "Dodano", "ui.sessionReview.change.removed": "Uklonjeno", "ui.sessionReview.change.modified": "Izmijenjeno", + "ui.sessionReview.image.loading": "Učitavanje...", + "ui.sessionReview.image.placeholder": "Slika", + "ui.sessionReview.largeDiff.title": "Diff je prevelik za prikaz", + "ui.sessionReview.largeDiff.meta": "Limit: {{lines}} linija / {{limit}}. Trenutno: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Prikaži svejedno", "ui.lineComment.label.prefix": "Komentar na ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 218f3b26a..2f49a9434 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -9,6 +9,11 @@ export const dict = { "ui.sessionReview.change.added": "Tilføjet", "ui.sessionReview.change.removed": "Fjernet", "ui.sessionReview.change.modified": "Ændret", + "ui.sessionReview.image.loading": "Indlæser...", + "ui.sessionReview.image.placeholder": "Billede", + "ui.sessionReview.largeDiff.title": "Diff er for stor til at blive vist", + "ui.sessionReview.largeDiff.meta": "Grænse: {{lines}} linjer / {{limit}}. Nuværende: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Vis alligevel", "ui.lineComment.label.prefix": "Kommenter på ", "ui.lineComment.label.suffix": "", "ui.lineComment.editorLabel.prefix": "Kommenterer på ", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index 921a12c99..44090b7bd 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -13,6 +13,11 @@ export const dict = { "ui.sessionReview.change.added": "Hinzugefügt", "ui.sessionReview.change.removed": "Entfernt", "ui.sessionReview.change.modified": "Geändert", + "ui.sessionReview.image.loading": "Wird geladen...", + "ui.sessionReview.image.placeholder": "Bild", + "ui.sessionReview.largeDiff.title": "Diff zu groß zum Rendern", + "ui.sessionReview.largeDiff.meta": "Limit: {{lines}} Zeilen / {{limit}}. Aktuell: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Trotzdem rendern", "ui.lineComment.label.prefix": "Kommentar zu ", "ui.lineComment.label.suffix": "", "ui.lineComment.editorLabel.prefix": "Kommentiere ", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 631bc660a..9b6ab0bd6 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -8,6 +8,11 @@ export const dict = { "ui.sessionReview.change.added": "Added", "ui.sessionReview.change.removed": "Removed", "ui.sessionReview.change.modified": "Modified", + "ui.sessionReview.image.loading": "Loading...", + "ui.sessionReview.image.placeholder": "Image", + "ui.sessionReview.largeDiff.title": "Diff too large to render", + "ui.sessionReview.largeDiff.meta": "Limit: {{lines}} lines / {{limit}}. Current: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Render anyway", "ui.lineComment.label.prefix": "Comment on ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 4fd921b60..c2f8ac3b9 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -8,6 +8,11 @@ export const dict = { "ui.sessionReview.change.added": "Añadido", "ui.sessionReview.change.removed": "Eliminado", "ui.sessionReview.change.modified": "Modificado", + "ui.sessionReview.image.loading": "Cargando...", + "ui.sessionReview.image.placeholder": "Imagen", + "ui.sessionReview.largeDiff.title": "Diff demasiado grande para renderizar", + "ui.sessionReview.largeDiff.meta": "Límite: {{lines}} líneas / {{limit}}. Actual: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Renderizar de todos modos", "ui.lineComment.label.prefix": "Comentar en ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 537d01bba..679d56fa7 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -8,6 +8,11 @@ export const dict = { "ui.sessionReview.change.added": "Ajouté", "ui.sessionReview.change.removed": "Supprimé", "ui.sessionReview.change.modified": "Modifié", + "ui.sessionReview.image.loading": "Chargement...", + "ui.sessionReview.image.placeholder": "Image", + "ui.sessionReview.largeDiff.title": "Diff trop volumineux pour être affiché", + "ui.sessionReview.largeDiff.meta": "Limite : {{lines}} lignes / {{limit}}. Actuel : {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Afficher quand même", "ui.lineComment.label.prefix": "Commenter sur ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 6086070bd..bf85807d0 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -9,6 +9,11 @@ export const dict = { "ui.sessionReview.change.added": "追加", "ui.sessionReview.change.removed": "削除", "ui.sessionReview.change.modified": "変更", + "ui.sessionReview.image.loading": "読み込み中...", + "ui.sessionReview.image.placeholder": "画像", + "ui.sessionReview.largeDiff.title": "差分が大きすぎて表示できません", + "ui.sessionReview.largeDiff.meta": "上限: {{lines}} 行 / {{limit}}。現在: {{current}}。", + "ui.sessionReview.largeDiff.renderAnyway": "それでも表示する", "ui.lineComment.label.prefix": "", "ui.lineComment.label.suffix": "へのコメント", "ui.lineComment.editorLabel.prefix": "", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index fd394dbb7..aba793a11 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -8,6 +8,11 @@ export const dict = { "ui.sessionReview.change.added": "추가됨", "ui.sessionReview.change.removed": "삭제됨", "ui.sessionReview.change.modified": "수정됨", + "ui.sessionReview.image.loading": "로딩 중...", + "ui.sessionReview.image.placeholder": "이미지", + "ui.sessionReview.largeDiff.title": "차이가 너무 커서 렌더링할 수 없습니다", + "ui.sessionReview.largeDiff.meta": "제한: {{lines}}줄 / {{limit}}. 현재: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "그래도 렌더링", "ui.lineComment.label.prefix": "", "ui.lineComment.label.suffix": "에 댓글 달기", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index dcb353614..7982b3ac7 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -11,6 +11,11 @@ export const dict: Record<Keys, string> = { "ui.sessionReview.change.added": "Lagt til", "ui.sessionReview.change.removed": "Fjernet", "ui.sessionReview.change.modified": "Endret", + "ui.sessionReview.image.loading": "Laster...", + "ui.sessionReview.image.placeholder": "Bilde", + "ui.sessionReview.largeDiff.title": "Diff er for stor til å gjengi", + "ui.sessionReview.largeDiff.meta": "Grense: {{lines}} linjer / {{limit}}. Nåværende: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Gjengi likevel", "ui.lineComment.label.prefix": "Kommenter på ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index fb10debbb..2489ac7f2 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -9,6 +9,11 @@ export const dict = { "ui.sessionReview.change.added": "Dodano", "ui.sessionReview.change.removed": "Usunięto", "ui.sessionReview.change.modified": "Zmodyfikowano", + "ui.sessionReview.image.loading": "Ładowanie...", + "ui.sessionReview.image.placeholder": "Obraz", + "ui.sessionReview.largeDiff.title": "Diff jest zbyt duży, aby go wyrenderować", + "ui.sessionReview.largeDiff.meta": "Limit: {{lines}} linii / {{limit}}. Obecnie: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Renderuj mimo to", "ui.lineComment.label.prefix": "Komentarz do ", "ui.lineComment.label.suffix": "", "ui.lineComment.editorLabel.prefix": "Komentowanie: ", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 417fe0ce8..8e6bb678f 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -9,6 +9,11 @@ export const dict = { "ui.sessionReview.change.added": "Добавлено", "ui.sessionReview.change.removed": "Удалено", "ui.sessionReview.change.modified": "Изменено", + "ui.sessionReview.image.loading": "Загрузка...", + "ui.sessionReview.image.placeholder": "Изображение", + "ui.sessionReview.largeDiff.title": "Diff слишком большой для отображения", + "ui.sessionReview.largeDiff.meta": "Лимит: {{lines}} строк / {{limit}}. Текущий: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "Отобразить всё равно", "ui.lineComment.label.prefix": "Комментарий к ", "ui.lineComment.label.suffix": "", "ui.lineComment.editorLabel.prefix": "Комментирование: ", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 68bb0d733..b036eca2e 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -8,6 +8,11 @@ export const dict = { "ui.sessionReview.change.added": "เพิ่ม", "ui.sessionReview.change.removed": "ลบ", "ui.sessionReview.change.modified": "แก้ไข", + "ui.sessionReview.image.loading": "กำลังโหลด...", + "ui.sessionReview.image.placeholder": "รูปภาพ", + "ui.sessionReview.largeDiff.title": "Diff มีขนาดใหญ่เกินไปจนไม่สามารถแสดงผลได้", + "ui.sessionReview.largeDiff.meta": "ขีดจำกัด: {{lines}} บรรทัด / {{limit}}. ปัจจุบัน: {{current}}.", + "ui.sessionReview.largeDiff.renderAnyway": "แสดงผลต่อไป", "ui.lineComment.label.prefix": "แสดงความคิดเห็นบน ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 53beeb1e4..dcb8062a3 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -12,6 +12,11 @@ export const dict = { "ui.sessionReview.change.added": "已添加", "ui.sessionReview.change.removed": "已移除", "ui.sessionReview.change.modified": "已修改", + "ui.sessionReview.image.loading": "加载中...", + "ui.sessionReview.image.placeholder": "图片", + "ui.sessionReview.largeDiff.title": "差异过大,无法渲染", + "ui.sessionReview.largeDiff.meta": "限制:{{lines}} 行 / {{limit}}。当前:{{current}}。", + "ui.sessionReview.largeDiff.renderAnyway": "仍然渲染", "ui.lineComment.label.prefix": "评论 ", "ui.lineComment.label.suffix": "", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 1449b0530..271a6ded3 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -12,6 +12,11 @@ export const dict = { "ui.sessionReview.change.added": "已新增", "ui.sessionReview.change.removed": "已移除", "ui.sessionReview.change.modified": "已修改", + "ui.sessionReview.image.loading": "載入中...", + "ui.sessionReview.image.placeholder": "圖片", + "ui.sessionReview.largeDiff.title": "差異過大,無法渲染", + "ui.sessionReview.largeDiff.meta": "限制:{{lines}} 行 / {{limit}}。目前:{{current}}。", + "ui.sessionReview.largeDiff.renderAnyway": "仍然渲染", "ui.lineComment.label.prefix": "評論 ", "ui.lineComment.label.suffix": "", |
