дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
fix(i18n): Translate interpolated UI text through a tx template tag (#98)
About 270 UI strings were JS template literals with interpolation passed to exact catalog lookup, so they could never be translated; about 25 more spliced lowercase English fragments into sentences.
`tx` now also works as a template tag (`` tx`Remove data source '${name}'?` ``): it looks up the entry by the literal parts (`{name}` placeholders), translates interpolated values that have entries, and remembers generated text so it re-translates after a language switch or when the catalog loads later. Call sites use the tag; spliced fragments are written as whole sentences per variant. Adds 280 Chinese translations and removes stale ones.
The catalog guard now also fails on untagged template literals reaching exact lookup (unless allowlisted with a reason), tagged text missing from the catalog, case-changed or ternary-suffix interpolations, and duplicate template patterns.
Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
1 джерело
2a8e5e791d
коміт
8568ff001b
142 files changed
+1031
-362
No files matched your search
@@ -54,6 +54,7 @@ import {
|
||||
} from "./cm/structural";
|
||||
import type { DocModel } from "./model/document";
|
||||
import { structure } from "@/editor/structure";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Text-editing actions: the editor has focus, or a popup (Find Action) runs them on it. */
|
||||
export const TEXT_WHEN = "editorTextFocus || textEditorActive && popupOpen";
|
||||
@@ -293,7 +294,7 @@ function copyReference(view: EditorView, slot: EditorSlot) {
|
||||
});
|
||||
recordClipboard(reference);
|
||||
void navigator.clipboard?.writeText(reference).catch(() => {});
|
||||
void executeAction("editor.showHint", { text: `Copied reference: ${reference}` });
|
||||
void executeAction("editor.showHint", { text: tx`Copied reference: ${reference}` });
|
||||
}
|
||||
|
||||
/** ⌃G; when nothing more is found, a hint first and the search from the top on the next press (IDEA). */
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import type { EditorState } from "@codemirror/state";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export const MAX_CLIPBOARD_ENTRIES = 30;
|
||||
|
||||
@@ -45,7 +46,7 @@ export function clipLabel(text: string, max = 80): { label: string; detail: stri
|
||||
const first = (lines.find((l) => l.trim()) ?? "").trim();
|
||||
const label = first.length > max ? `${first.slice(0, max - 1)}…` : first || "(whitespace)";
|
||||
const count = text.endsWith("\n") ? lines.length - 1 : lines.length;
|
||||
return { label, detail: count > 1 ? `${count} lines` : undefined };
|
||||
return { label, detail: count > 1 ? tx`${count} lines` : undefined };
|
||||
}
|
||||
|
||||
export const clipboardHistoryExtension = EditorView.domEventHandlers({
|
||||
|
||||
@@ -12,6 +12,7 @@ import { openideaHighlightStyle } from "./highlight";
|
||||
import { openideaTheme } from "./theme";
|
||||
import { findLanguageDescription, loadSupport, loadedSupport, uiModeForPath } from "./languages";
|
||||
import { fontExtension } from "./setup";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export async function previewExtensions(path: string, settings: EditorSettings | undefined): Promise<Extension> {
|
||||
const open = editorsFor(path)[0]?.ctx;
|
||||
@@ -25,7 +26,7 @@ export async function previewExtensions(path: string, settings: EditorSettings |
|
||||
syntaxHighlighting(openideaHighlightStyle),
|
||||
EditorState.readOnly.of(true),
|
||||
EditorView.editable.of(false),
|
||||
EditorView.contentAttributes.of({ "aria-label": `Preview of ${path}` }),
|
||||
EditorView.contentAttributes.of({ "aria-label": tx`Preview of ${path}` }),
|
||||
support ?? [],
|
||||
];
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import { gutterLineDrag } from "./gutterDrag";
|
||||
import { overwriteField } from "./overwrite";
|
||||
import { clipboardHistoryExtension } from "../clipboardHistory";
|
||||
import type { LanguageInfo } from "../backend";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export const compartments = {
|
||||
language: new Compartment(),
|
||||
@@ -309,7 +310,7 @@ export function baseExtensions(o: SetupOptions): Extension {
|
||||
findExtension(),
|
||||
overviewLane,
|
||||
codeLensExtension,
|
||||
EditorView.contentAttributes.of({ spellcheck: "false", autocorrect: "off", autocapitalize: "off", "aria-label": `Editor for ${o.ctx.path}` }),
|
||||
EditorView.contentAttributes.of({ spellcheck: "false", autocorrect: "off", autocapitalize: "off", "aria-label": tx`Editor for ${o.ctx.path}` }),
|
||||
tabKeymap,
|
||||
keymap.of([...closeBracketsKeymap, ...editorBaseKeymap, indentWithTab]),
|
||||
compartments.language.of(o.language ?? []),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* every language with a grammar is covered.
|
||||
*/
|
||||
import type { Text } from "@codemirror/state";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export interface ChainNode {
|
||||
kind: string;
|
||||
@@ -133,7 +134,7 @@ export function unwrapOptions(doc: Text, chain: readonly ChainNode[]): UnwrapOpt
|
||||
const key = `${target.from}:${target.to}:${label}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
out.push({ label: `Unwrap '${label}'`, target: { from: target.from, to: target.to }, ...replacement(doc, target, child) });
|
||||
out.push({ label: tx`Unwrap '${label}'`, target: { from: target.from, to: target.to }, ...replacement(doc, target, child) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -8,11 +8,12 @@ import { basename } from "@/platform/project";
|
||||
import { notifications } from "@/workbench/contributions";
|
||||
import { confirm } from "@/workbench/messages";
|
||||
import type { DocModel } from "./model/document";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function readOnlyQuestion(name: string, readOnly: boolean) {
|
||||
return readOnly
|
||||
? { title: "Make File Writable", message: `'${name}' is read-only. Make it writable?`, confirm: "Make Writable" }
|
||||
: { title: "Make File Read-only", message: `Make '${name}' read-only?`, confirm: "Make Read-only" };
|
||||
? { title: "Make File Writable", message: tx`'${name}' is read-only. Make it writable?`, confirm: "Make Writable" }
|
||||
: { title: "Make File Read-only", message: tx`Make '${name}' read-only?`, confirm: "Make Read-only" };
|
||||
}
|
||||
|
||||
/** Applies the toggle without asking. Returns the new read-only state, or null on failure. */
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
} from "../cm/setup";
|
||||
import { setMethodLines, softWrapActive } from "../cm/prefs";
|
||||
import type { EditorSlot } from "./slot";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export type Banner = "binary" | "tooLarge" | "readOnly" | "library" | null;
|
||||
|
||||
@@ -237,7 +238,7 @@ export class DocModel {
|
||||
manager.notify({
|
||||
level: "warning",
|
||||
title: "Editor resynchronised",
|
||||
body: `Some unsaved edits to ${this.path} could not be applied and were replaced by the file's current content.`,
|
||||
body: tx`Some unsaved edits to ${this.path} could not be applied and were replaced by the file's current content.`,
|
||||
}),
|
||||
onError: (e) => console.warn(`document ${this.path}: sync error`, e),
|
||||
},
|
||||
|
||||
@@ -44,6 +44,7 @@ import { FindBar } from "./FindBar";
|
||||
import { EditorBanners, VirtualBanner } from "./Banner";
|
||||
import { CodeBreadcrumbs } from "./StatusItems";
|
||||
import styles from "./EditorArea.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function EditorAreaView() {
|
||||
const layout = useTabs((s) => s.layout);
|
||||
@@ -343,7 +344,7 @@ function PaneContent({ paneId, tab }: { paneId: string; tab: Tab }) {
|
||||
);
|
||||
}
|
||||
const kind = kinds.get(tab.input.kind);
|
||||
if (!kind) return <EmptyState icon="file" title={`No editor for “${tab.input.kind}”`} />;
|
||||
if (!kind) return <EmptyState icon="file" title={tx`No editor for “${tab.input.kind}”`} />;
|
||||
const Component = kind.component;
|
||||
return (
|
||||
<div className={styles.kind} data-editor-focus="" tabIndex={-1} key={tab.id}>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { posToOffset } from "./positions";
|
||||
import { onLspRefresh, serverBackoff } from "./availability";
|
||||
import { editorPrefs } from "@/platform/editorSettings";
|
||||
import { authorLabel, authorSummary, authorTooltip, blameFor, onBlameInvalidated } from "./authorLens";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const LENS_KINDS = new Set<SymbolKind>([
|
||||
"class",
|
||||
@@ -190,7 +191,7 @@ class UsagesLensPlugin {
|
||||
lenses.push({
|
||||
pos: offset,
|
||||
title: usagesLabel(n),
|
||||
tooltip: n ? `Show usages of ${t.name}` : undefined,
|
||||
tooltip: n ? tx`Show usages of ${t.name}` : undefined,
|
||||
order: 10,
|
||||
run: n ? (v) => openUsages(v, Math.min(offset, v.state.doc.length)) : undefined,
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import { call, commands, isTauri, onIdeEvent, type PathCompletion } from "@/plat
|
||||
import { fileIconName, folderIconName } from "@/ui";
|
||||
import { setFileDiagnostics } from "./diagnostics";
|
||||
import { docOf, lspDoc } from "./lsp";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
// --- completion ---------------------------------------------------------------
|
||||
|
||||
@@ -118,7 +119,7 @@ export function unresolvedReferences(): Extension {
|
||||
refs.map((r) => ({
|
||||
range: r.range,
|
||||
severity: "warning",
|
||||
message: `Cannot resolve file '${r.value}'`,
|
||||
message: tx`Cannot resolve file '${r.value}'`,
|
||||
source: doc.language === "html" || doc.language === "vue" || doc.language === "svelte" ? "HTML" : "CSS",
|
||||
code: null,
|
||||
})),
|
||||
|
||||
@@ -161,7 +161,7 @@ export async function reformatDialog(
|
||||
const name = ctx.path.slice(ctx.path.lastIndexOf("/") + 1);
|
||||
openPopup({
|
||||
kind: "reformat",
|
||||
title: `Reformat File: ${name}`,
|
||||
title: tx`Reformat File: ${name}`,
|
||||
hasSelection: !sel.empty,
|
||||
canOptimizeImports: !contributedAt(view.state, CONTRIBUTED_FORMAT, 0),
|
||||
initial: lastOptions,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { debounce, docOf, isUnavailable, lspRequest } from "./lsp";
|
||||
import { offsetsToRange, posToOffset } from "./positions";
|
||||
import { onLspRefresh, serverBackoff } from "./availability";
|
||||
import { gotoTargets } from "./navigation";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const KIND_LABEL: Record<InlayHint["kind"], string> = {
|
||||
type: "Type hints",
|
||||
@@ -39,7 +40,7 @@ function typeHintOffset(view: EditorView, at: number): number | null {
|
||||
/** Right-click on a hint: turn its kind off (IDEA's "Disable '…' hints"), or open the settings. */
|
||||
export function inlayMenuItems(kind: InlayHint["kind"]): MenuItem[] {
|
||||
return [
|
||||
{ label: `Disable ${KIND_LABEL[kind]}`, run: () => void setEditorSetting(`inlay.${kind}`, false) },
|
||||
{ label: tx`Disable ${KIND_LABEL[kind]}`, run: () => void setEditorSetting(`inlay.${kind}`, false) },
|
||||
{ kind: "separator" },
|
||||
{ label: "Inlay Hints Settings…", run: () => void executeAction("file.settings", "editor.inlayHints") },
|
||||
];
|
||||
|
||||
@@ -21,6 +21,7 @@ import { spellingIntentions } from "./spelling";
|
||||
import { inspectionsAt } from "./diagnostics";
|
||||
import { SPELLING_INSPECTION } from "@/platform/inspections";
|
||||
import { editInspectionSettings, intentionControls } from "@/platform/inspectionControls";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const isQuickFix = (a: CodeAction) => !!a.kind && (a.kind === "quickfix" || a.kind.startsWith("quickfix."));
|
||||
|
||||
@@ -195,7 +196,7 @@ export function withInspectionControls(view: EditorView, items: ListPopupItem[])
|
||||
...items,
|
||||
...inspections.map((inspection) => ({
|
||||
id: `inspection:${inspection.id}`,
|
||||
label: `Inspection '${inspection.name}' options`,
|
||||
label: tx`Inspection '${inspection.name}' options`,
|
||||
icon: "settings",
|
||||
run: () => editInspectionSettings(inspection.id),
|
||||
submenu: intentionControls(inspection),
|
||||
|
||||
@@ -52,6 +52,7 @@ import { isVirtualUri, virtualContainer } from "@/platform/virtualDocs";
|
||||
import { shortcutLabel } from "@/platform/keymap";
|
||||
import { isNavigationGesture, onNavigationModifier } from "@/platform/navigationModifier";
|
||||
import { create } from "zustand";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export async function navigateTo(loc: Location) {
|
||||
await editorService().openFile(loc.path, {
|
||||
@@ -224,7 +225,7 @@ async function gotoWebReference(view: EditorView, doc: EditorDocContext, pos: Po
|
||||
openPopup({
|
||||
kind: "list",
|
||||
anchor: caretAnchor(view, offset),
|
||||
title: `Choose Declaration of '${label}' (${targets.length} found)`,
|
||||
title: tx`Choose Declaration of '${label}' (${targets.length} found)`,
|
||||
items: await locationItems(targets),
|
||||
pin: pinToFind(label, targets),
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
renameRisks,
|
||||
type LocalRenamePlan,
|
||||
} from "./syntaxFallback";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
async function applyEdit(view: EditorView, edit: WorkspaceEdit) {
|
||||
try {
|
||||
@@ -83,7 +84,7 @@ export async function startRename(view: EditorView) {
|
||||
openPopup({
|
||||
kind: "rename",
|
||||
anchor: caretAnchor(view, from),
|
||||
title: `Rename '${oldName}'`,
|
||||
title: tx`Rename '${oldName}'`,
|
||||
initial,
|
||||
onSubmit: (newName) => void finishRename(view, doc, pos, oldName, newName),
|
||||
});
|
||||
@@ -99,7 +100,7 @@ export async function startRename(view: EditorView) {
|
||||
startInplaceRename(view, {
|
||||
primary: { from, to },
|
||||
others,
|
||||
hint: keys ? `Press ${keys} to show dialog with more options` : "Rename in place: ↵ to finish, Esc to cancel",
|
||||
hint: keys ? tx`Press ${keys} to show dialog with more options` : "Rename in place: ↵ to finish, Esc to cancel",
|
||||
commit: (newName) => void commitInplace(view, doc, pos, oldName, newName),
|
||||
dialog,
|
||||
});
|
||||
@@ -223,7 +224,7 @@ async function finishRename(view: EditorView, doc: EditorDocContext, pos: Pos, o
|
||||
}
|
||||
openPopup({
|
||||
kind: "preview",
|
||||
title: `Rename '${oldName}' to '${newName}'`,
|
||||
title: tx`Rename '${oldName}' to '${newName}'`,
|
||||
summary,
|
||||
confirmLabel: "Do Refactor",
|
||||
onConfirm: () => void applyEdit(view, edit),
|
||||
@@ -265,7 +266,7 @@ async function startSyntaxRename(view: EditorView, doc: EditorDocContext, pos: P
|
||||
openPopup({
|
||||
kind: "rename",
|
||||
anchor: caretAnchor(view, rangeToOffsets(view.state.doc, (at ?? plan.occurrences[0]!).range).from),
|
||||
title: `Rename '${plan.symbol.name}'`,
|
||||
title: tx`Rename '${plan.symbol.name}'`,
|
||||
initial: plan.symbol.name,
|
||||
note: `${SYNTAX_NOTE}: local occurrences only`,
|
||||
onSubmit: (newName) => void commitSyntaxRename(view, doc, plan, newName),
|
||||
@@ -315,7 +316,7 @@ export async function commitSyntaxRename(view: EditorView, doc: EditorDocContext
|
||||
});
|
||||
openPopup({
|
||||
kind: "preview",
|
||||
title: `Rename '${oldName}' to '${newName}'`,
|
||||
title: tx`Rename '${oldName}' to '${newName}'`,
|
||||
summary: {
|
||||
files: 1,
|
||||
occurrences: occurrences.length,
|
||||
|
||||
@@ -27,6 +27,7 @@ import { rangeToOffsets } from "./positions";
|
||||
import { showHint } from "./hint";
|
||||
import { caretAnchor, openPopup, type ListPopupItem } from "./popupStore";
|
||||
import { renameNamePart } from "./rename";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const CHECK_DELAY = 800;
|
||||
|
||||
@@ -124,7 +125,7 @@ async function changeTo(view: EditorView, typo: PlacedTypo) {
|
||||
openPopup({
|
||||
kind: "list",
|
||||
anchor: caretAnchor(view, typo.from),
|
||||
title: `${rename ? "Rename" : "Change"} '${typo.word}' to`,
|
||||
title: rename ? tx`Rename '${typo.word}' to` : tx`Change '${typo.word}' to`,
|
||||
items: suggestions.map((s, i) => ({ id: `${i}`, label: s, run: () => applyCorrection(view, typo, s) })),
|
||||
});
|
||||
}
|
||||
@@ -142,7 +143,7 @@ export function spellingIntentions(view: EditorView): ListPopupItem[] {
|
||||
? [
|
||||
{
|
||||
id: "spelling.saveProject",
|
||||
label: `Save '${typo.word}' to project-level dictionary`,
|
||||
label: tx`Save '${typo.word}' to project-level dictionary`,
|
||||
icon: "bulb-fix",
|
||||
run: () => saveToDictionary(typo.word, "project"),
|
||||
},
|
||||
@@ -150,7 +151,7 @@ export function spellingIntentions(view: EditorView): ListPopupItem[] {
|
||||
: []),
|
||||
{
|
||||
id: "spelling.saveIde",
|
||||
label: `Save '${typo.word}' to application-level dictionary`,
|
||||
label: tx`Save '${typo.word}' to application-level dictionary`,
|
||||
icon: "bulb-fix",
|
||||
run: () => saveToDictionary(typo.word, "ide"),
|
||||
},
|
||||
@@ -210,7 +211,7 @@ export function spellingExtension(): Extension {
|
||||
typos.map((t) => ({
|
||||
range: t.range,
|
||||
severity: "info",
|
||||
message: `Typo: In word '${t.word}'`,
|
||||
message: tx`Typo: In word '${t.word}'`,
|
||||
source: "Spelling",
|
||||
code: null,
|
||||
})),
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { Location, Pos, Range, SymbolKind, SymbolMatch } from "@/bindings";
|
||||
import { call, commands } from "@/platform/ipc";
|
||||
import { structure, type Locals, type LocalSymbol, type NodeAt } from "@/editor/structure";
|
||||
import { comparePos, rangeContains, rangeToOffsets } from "./positions";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Subtle marker for answers that did not come from the language server. */
|
||||
export const SYNTAX_NOTE = "Based on syntax (language server not ready)";
|
||||
@@ -237,7 +238,7 @@ export async function planLocalRename(
|
||||
if (locals.status === "notLocal")
|
||||
return {
|
||||
kind: "refused",
|
||||
message: `Can't rename '${locals.name}' without the language server: only local variables can be renamed based on syntax`,
|
||||
message: tx`Can't rename '${locals.name}' without the language server: only local variables can be renamed based on syntax`,
|
||||
};
|
||||
const { symbol } = locals;
|
||||
const ranges = [symbol.definition, ...symbol.references].sort((a, b) => comparePos(a.start, b.start));
|
||||
|
||||
@@ -20,6 +20,7 @@ import { editorService } from "@/workbench/contributions";
|
||||
import { fileIconName } from "@/ui";
|
||||
import { gotoTestOrSubject, type TestNavDeps } from "./gotoTest";
|
||||
import { baseName, memberAt, methodStop, superMethods } from "./structureNav";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const EDITOR_WHEN = "textEditorActive && (editorFocus || popupOpen || !toolWindowFocus)";
|
||||
|
||||
@@ -97,7 +98,7 @@ async function superMethod(view: EditorView) {
|
||||
openPopup({
|
||||
kind: "list",
|
||||
anchor: caretAnchor(view, head),
|
||||
title: method ? `Choose Super Method of ${baseName(method.name)} (${locs.length} found)` : `Choose Super Class or Interface (${locs.length} found)`,
|
||||
title: method ? tx`Choose Super Method of ${baseName(method.name)} (${locs.length} found)` : tx`Choose Super Class or Interface (${locs.length} found)`,
|
||||
items: await locationItems(locs),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
type DeclResult,
|
||||
type Declaration,
|
||||
} from "./templates";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Why the language server could not do it (see `withFallback`). */
|
||||
export type FallbackReason = "notReady" | "error" | "empty";
|
||||
@@ -594,7 +595,7 @@ async function chooseOccurrences(p: Prepared, self: Occurrence, others: Occurren
|
||||
},
|
||||
{
|
||||
id: "all",
|
||||
label: `Replace all ${others.length + 1} occurrences`,
|
||||
label: tx`Replace all ${others.length + 1} occurrences`,
|
||||
onActive: () => highlightRanges(view, [self, ...others]),
|
||||
run: () => apply(true),
|
||||
},
|
||||
@@ -663,7 +664,7 @@ function applyAndName(p: Prepared, plan: Plan, names: string[]) {
|
||||
initial: name,
|
||||
suggestions: names,
|
||||
note: NOTES[p.reason],
|
||||
hint: `↵ Apply · Esc Keep '${name}'`,
|
||||
hint: tx`↵ Apply · Esc Keep '${name}'`,
|
||||
onSubmit: (chosen) => void rename(p, chosen, name, { original, applied, plan, ranges }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ describe("safeDelete in the editor", () => {
|
||||
expect((await run("delete")).state.doc.toString()).toBe("fn unused() {\n unused();\n}\n\nfn main() {}\n");
|
||||
});
|
||||
|
||||
it("confirms first ('Delete function …?') and searches nothing when cancelled", async () => {
|
||||
it("confirms first ('Delete Function …?') and searches nothing when cancelled", async () => {
|
||||
const v = editor(text, "unused() {");
|
||||
outline = ok([item(text, "unused", "fn unused() {\n unused();\n}")]);
|
||||
confirmAnswer = null;
|
||||
@@ -272,7 +272,7 @@ describe("safeDelete in the editor", () => {
|
||||
expect(confirmChecked).toHaveBeenCalledTimes(1);
|
||||
const opts = confirmChecked.mock.calls[0]![0];
|
||||
expect(opts.title).toBe("Safe Delete");
|
||||
expect(opts.message).toBe("Delete function 'unused'?");
|
||||
expect(opts.message).toBe("Delete Function 'unused'?");
|
||||
expect(opts.checkbox).toBe("Search in comments and strings");
|
||||
expect(lspReferences).not.toHaveBeenCalled();
|
||||
expect(v.state.doc.toString()).toBe(text);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { ask, confirmChecked } from "@/workbench/messages";
|
||||
import { openPopup, type PreviewOccurrence } from "@/editor/lsp/popupStore";
|
||||
import { cannotPerform } from "./catalog";
|
||||
import { cancelled, languageName } from "./query";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Deletion range (pure)
|
||||
@@ -269,11 +270,19 @@ export function usagesMessage(
|
||||
): { message: string; detail: string } {
|
||||
const n = usages.length;
|
||||
const files = new Set(usages.map((u) => u.path)).size;
|
||||
const noun = n === 1 ? "usage that is" : "usages that are";
|
||||
return {
|
||||
message: `${n} ${n === 1 ? "usage" : "usages"} found`,
|
||||
detail: `${subject} ${plural ? "have" : "has"} ${n} ${noun} not safe to delete${files > 1 ? ` (in ${files} files)` : ""}.`,
|
||||
};
|
||||
const where = files > 1 ? tx` (in ${files} files)` : "";
|
||||
let detail: string;
|
||||
if (plural)
|
||||
detail =
|
||||
n === 1
|
||||
? tx`${subject} have ${n} usage that is not safe to delete${where}.`
|
||||
: tx`${subject} have ${n} usages that are not safe to delete${where}.`;
|
||||
else
|
||||
detail =
|
||||
n === 1
|
||||
? tx`${subject} has ${n} usage that is not safe to delete${where}.`
|
||||
: tx`${subject} has ${n} usages that are not safe to delete${where}.`;
|
||||
return { message: n === 1 ? tx`${n} usage found` : tx`${n} usages found`, detail };
|
||||
}
|
||||
|
||||
/** IDEA's "Usages Detected" question; resolves "cancel" when dismissed. */
|
||||
@@ -391,7 +400,7 @@ function previewTextOccurrences(
|
||||
});
|
||||
openPopup({
|
||||
kind: "preview",
|
||||
title: `Safe Delete ${target.kind.toLowerCase()} '${target.name}'`,
|
||||
title: tx`Safe Delete ${target.kind} '${target.name}'`,
|
||||
summary: {
|
||||
files: 1,
|
||||
occurrences: occurrences.length,
|
||||
@@ -436,7 +445,7 @@ export async function safeDelete(view: EditorView): Promise<void> {
|
||||
// IDEA always confirms first; usages are searched after OK.
|
||||
const confirmed = await confirmChecked({
|
||||
title: "Safe Delete",
|
||||
message: `Delete ${target.kind.toLowerCase()} '${target.name}'?`,
|
||||
message: tx`Delete ${target.kind} '${target.name}'?`,
|
||||
ok: "OK",
|
||||
checkbox: "Search in comments and strings",
|
||||
checked: searchInComments,
|
||||
@@ -453,7 +462,7 @@ export async function safeDelete(view: EditorView): Promise<void> {
|
||||
]);
|
||||
if (view.state.doc !== docBefore) return;
|
||||
if (usages.length) {
|
||||
const answer = await askUsagesDetected(`${target.kind} '${target.name}'`, usages);
|
||||
const answer = await askUsagesDetected(tx`${target.kind} '${target.name}'`, usages);
|
||||
if (answer === "view") viewUsages(target.name, usages);
|
||||
else if (answer === "delete") applyDeletion(view, docBefore, target, comments);
|
||||
else view.focus();
|
||||
|
||||
@@ -27,6 +27,7 @@ import type { RefactoringId } from "./catalog";
|
||||
import { expressionCandidates, expressionPreview } from "./introduce";
|
||||
import { findTextOccurrences } from "./occurrences";
|
||||
import { isIdentifier, isKeyword, suggestNames, uniqueNames } from "./names";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Refactorings that take an expression (chosen first when nothing is selected). */
|
||||
export const EXPRESSION_REFACTORINGS: ReadonlySet<RefactoringId> = new Set([
|
||||
@@ -135,7 +136,7 @@ export function chooseOccurrences(view: EditorView, actions: readonly CodeAction
|
||||
},
|
||||
{
|
||||
id: "all",
|
||||
label: n > 1 ? `Replace all ${n} occurrences` : "Replace all occurrences",
|
||||
label: n > 1 ? tx`Replace all ${n} occurrences` : "Replace all occurrences",
|
||||
onActive: () => highlightRanges(view, n ? occurrences : [{ from: sel.from, to: sel.to }]),
|
||||
run: () => applyIntroduceAction(view, variants.all, id),
|
||||
},
|
||||
@@ -291,13 +292,13 @@ export async function applyIntroduceAction(view: EditorView, action: CodeAction,
|
||||
primary,
|
||||
others,
|
||||
suggestions: names.length > 1 ? names : undefined,
|
||||
hint: keys ? `Press ${keys} to show dialog with more options` : "↵ to finish, Esc to keep the name",
|
||||
hint: keys ? tx`Press ${keys} to show dialog with more options` : "↵ to finish, Esc to keep the name",
|
||||
commit: (name) => renameIntroduced(view, [primary, ...others], found.name, name),
|
||||
dialog: (typed) =>
|
||||
openPopup({
|
||||
kind: "rename",
|
||||
anchor: caretAnchor(view, primary.from),
|
||||
title: `Rename '${found.name}'`,
|
||||
title: tx`Rename '${found.name}'`,
|
||||
initial: typed,
|
||||
suggestions: names,
|
||||
onSubmit: (name) => renameIntroduced(view, [primary, ...others], found.name, name),
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Icon, IconButton, Spinner, T, Tree, showContextMenu, type MenuItem } fr
|
||||
import { age, branchLabel, groupTasks, isBusy, type TaskGroup } from "./model";
|
||||
import { archiveTask, deleteTask, openHome, openTask, useAgents } from "./store";
|
||||
import styles from "./Agents.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function AgentsHeaderActions() {
|
||||
return <IconButton icon="plus" action="agents.newTask" size="small" />;
|
||||
@@ -117,8 +118,8 @@ async function confirmDelete(task: AgentTask) {
|
||||
const r = await confirmChecked({
|
||||
title: "Delete Agent Task",
|
||||
message: worktree
|
||||
? `Delete “${task.title}”? Its branch ${task.branch ?? ""} is kept.`
|
||||
: `Delete “${task.title}”? Files it changed are kept.`,
|
||||
? tx`Delete “${task.title}”? Its branch ${task.branch ?? ""} is kept.`
|
||||
: tx`Delete “${task.title}”? Files it changed are kept.`,
|
||||
ok: "Delete",
|
||||
icon: "warning",
|
||||
checkbox: worktree ? "Also remove its worktree folder" : undefined,
|
||||
|
||||
@@ -157,7 +157,7 @@ export function Composer(props: ComposerProps) {
|
||||
for (const file of picked) {
|
||||
if (!file.type.startsWith("image/")) continue;
|
||||
if (file.size > MAX_IMAGE_BYTES) {
|
||||
notifications().notify({ level: "warning", title: "Image too large", body: `${file.name} is over 5 MB.` });
|
||||
notifications().notify({ level: "warning", title: "Image too large", body: tx`${file.name} is over 5 MB.` });
|
||||
continue;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** Pure composer helpers: `@` / `/` completion, attachments, history recall, settings. */
|
||||
import type { AgentCommand, AgentConfigOption, AgentImage, AgentPrompt, AgentUsage } from "@/bindings";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export interface FileRef {
|
||||
/** Absolute path (in the task's directory). */
|
||||
@@ -114,7 +115,7 @@ export function usageLabel(u: AgentUsage): { short: string; detail: string } {
|
||||
const pct = Math.min(100, Math.round((u.used / u.size) * 100));
|
||||
const k = (n: number) => (n >= 1000 ? `${Math.round(n / 1000)}k` : `${Math.round(n)}`);
|
||||
const cost = u.cost != null ? ` · ${u.currency === "USD" || !u.currency ? "$" : `${u.currency} `}${u.cost.toFixed(2)}` : "";
|
||||
return { short: `${pct}%`, detail: `${k(u.used)} / ${k(u.size)} tokens${cost}` };
|
||||
return { short: `${pct}%`, detail: tx`${k(u.used)} / ${k(u.size)} tokens${cost}` };
|
||||
}
|
||||
|
||||
/** `data:image/png;base64,AAA` → an image attachment. */
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
type StructuralForm,
|
||||
} from "./structural";
|
||||
import styles from "./Analysis.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function InspectCodeDialog({ onClose }: { onClose: () => void }) {
|
||||
const [scope, setScope] = useState<ScopeChoice>(() => defaultScope());
|
||||
@@ -363,7 +364,7 @@ function ReplacePreviewDialog({ files, onClose }: { files: StructuralFile[]; onC
|
||||
<Checkbox
|
||||
checked={!excluded.has(key)}
|
||||
onChange={(on) => toggle(key, on)}
|
||||
aria-label={`Replace in ${f.relPath}:${m.range.start.line + 1}`}
|
||||
aria-label={tx`Replace in ${f.relPath}:${m.range.start.line + 1}`}
|
||||
/>
|
||||
<div className={styles.previewBody}>
|
||||
<span className={styles.previewWhere}>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { maybeEditorService, notifications } from "@/workbench/contributions";
|
||||
import { confirm } from "@/workbench/messages";
|
||||
import { removeTask, upsertTask } from "@/workbench/tasks";
|
||||
import { changedFiles, currentFile, type ScopeChoice } from "./scope";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export interface CleanupOptions {
|
||||
fixAll: boolean;
|
||||
@@ -97,7 +98,7 @@ export async function runCodeCleanup(choice: ScopeChoice, options: CleanupOption
|
||||
}
|
||||
if (
|
||||
files.length > CONFIRM_ABOVE &&
|
||||
!(await confirm({ title: "Code Cleanup", message: `Clean up ${files.length} files?`, ok: "Clean Up" }))
|
||||
!(await confirm({ title: "Code Cleanup", message: tx`Clean up ${files.length} files?`, ok: "Clean Up" }))
|
||||
)
|
||||
return;
|
||||
const editors = maybeEditorService();
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "@/platform/ipc";
|
||||
import { notifications } from "@/workbench/contributions";
|
||||
import { resolveScope, type ScopeChoice } from "./scope";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const STATE_KEY = "openidea.analysis.structural.v1";
|
||||
|
||||
@@ -152,7 +153,7 @@ export async function showStructuralResults(
|
||||
group: "analysis",
|
||||
level: "warning",
|
||||
title: "Search Structurally",
|
||||
body: `Stopped after ${summary.matches} matches`,
|
||||
body: tx`Stopped after ${summary.matches} matches`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useProject } from "@/platform/project";
|
||||
import { notifications, registerEditorBanner } from "@/workbench/contributions";
|
||||
import { promptInput } from "@/workbench/inputDialogs";
|
||||
import { showNewProject } from "./NewProject";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** localStorage prefixes of IDE-level state exported with the settings (templates, macros, layouts). */
|
||||
export const EXPORTED_STORAGE = [
|
||||
@@ -93,7 +94,7 @@ async function renameProject() {
|
||||
initial: project.name,
|
||||
okLabel: "Rename",
|
||||
validate: () => null,
|
||||
hint: `Leave empty to use the folder name (${folder}).`,
|
||||
hint: tx`Leave empty to use the folder name (${folder}).`,
|
||||
});
|
||||
if (name === null) return;
|
||||
try {
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
useBuild,
|
||||
} from "./store";
|
||||
import styles from "./BuildTools.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function MavenView() {
|
||||
return <BuildToolView tool="maven" />;
|
||||
@@ -292,8 +293,8 @@ function BuildToolView({ tool }: { tool: BuildTool }) {
|
||||
const config = isRunnable(node) && model ? nodeConfig(nodes.filter(isRunnable), model) : null;
|
||||
if (config) {
|
||||
items.push(
|
||||
{ label: `Run '${config.name}'`, icon: "run", run: () => runConfig(config, "run") },
|
||||
{ label: `Debug '${config.name}'`, icon: "debug", run: () => runConfig(config, "debug") },
|
||||
{ label: tx`Run '${config.name}'`, icon: "run", run: () => runConfig(config, "run") },
|
||||
{ label: tx`Debug '${config.name}'`, icon: "debug", run: () => runConfig(config, "debug") },
|
||||
{ kind: "separator" },
|
||||
);
|
||||
if (node.kind === "runConfig")
|
||||
@@ -318,7 +319,7 @@ function BuildToolView({ tool }: { tool: BuildTool }) {
|
||||
const file = node.project.buildFile;
|
||||
if (file)
|
||||
items.push({
|
||||
label: `Open ${file.split("/").pop()}`,
|
||||
label: tx`Open ${file.split("/").pop()}`,
|
||||
icon: "file-gradle",
|
||||
run: () => void editorService().openFile(`${root}/${file}`),
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
type HierarchyNode,
|
||||
} from "./hierarchy";
|
||||
import styles from "./HierarchyView.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
type Node = { kind: "root"; id: string } | { kind: "item"; id: string; node: HierarchyNode };
|
||||
|
||||
@@ -78,7 +79,7 @@ function HierarchyToolbar() {
|
||||
icon="arrow-up"
|
||||
size="small"
|
||||
iconSize={16}
|
||||
title={`${directionLabel(mode, "incoming")} Hierarchy`}
|
||||
title={tx`${directionLabel(mode, "incoming")} Hierarchy`}
|
||||
pressed={direction === "incoming"}
|
||||
onClick={() => setDirection("incoming")}
|
||||
/>
|
||||
@@ -86,7 +87,7 @@ function HierarchyToolbar() {
|
||||
icon="arrow-down"
|
||||
size="small"
|
||||
iconSize={16}
|
||||
title={`${directionLabel(mode, "outgoing")} Hierarchy`}
|
||||
title={tx`${directionLabel(mode, "outgoing")} Hierarchy`}
|
||||
pressed={direction === "outgoing"}
|
||||
onClick={() => setDirection("outgoing")}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { downloadJdkActions } from "@/platform/jdk";
|
||||
import { relativePath, useProject } from "@/platform/project";
|
||||
import { STATUS_LABEL, restartServer, serversSummary, useServers } from "./servers";
|
||||
import styles from "./ServerStatus.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function ServerStatus() {
|
||||
const map = useServers((s) => s.servers);
|
||||
@@ -23,7 +24,7 @@ export function ServerStatus() {
|
||||
<BarButton
|
||||
ref={ref}
|
||||
aria-expanded={open}
|
||||
aria-label={`Language servers: ${summary.text}`}
|
||||
aria-label={tx`Language servers: ${summary.text}`}
|
||||
tooltip="Language servers"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
>
|
||||
@@ -65,7 +66,7 @@ export function ServerStatus() {
|
||||
) : s.status === "missing" && canInstall ? (
|
||||
<Button
|
||||
size="small"
|
||||
aria-label={`Install ${s.name}`}
|
||||
aria-label={tx`Install ${s.name}`}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
void executeAction("tools.install", s.name);
|
||||
@@ -77,7 +78,7 @@ export function ServerStatus() {
|
||||
<IconButton
|
||||
icon="refresh"
|
||||
size="small"
|
||||
title={`Restart ${s.name}`}
|
||||
title={tx`Restart ${s.name}`}
|
||||
disabled={s.status === "missing" || s.status === "starting"}
|
||||
onClick={() => void restartServer(s.id)}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
/** "problems" file decorations: files with errors (and their folders) get IDEA's red underline. */
|
||||
import { setFileDecorations, type FileDecoration } from "@/workbench/contributions";
|
||||
import { countSeverities, fileDiagnostics, useProblems, type ProblemsMap } from "./problemsStore";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function problemDecorations(map: ProblemsMap): Map<string, FileDecoration> {
|
||||
const out = new Map<string, FileDecoration>();
|
||||
for (const path of map.keys()) {
|
||||
const c = countSeverities(fileDiagnostics(map, path));
|
||||
if (!c.error) continue;
|
||||
out.set(path, {
|
||||
error: true,
|
||||
propagate: true,
|
||||
tooltip: `${c.error} ${c.error === 1 ? "error" : "errors"}${c.warning ? `, ${c.warning} ${c.warning === 1 ? "warning" : "warnings"}` : ""}`,
|
||||
});
|
||||
const errors = c.error === 1 ? tx`${c.error} error` : tx`${c.error} errors`;
|
||||
const warnings = !c.warning ? "" : c.warning === 1 ? tx`${c.warning} warning` : tx`${c.warning} warnings`;
|
||||
out.set(path, { error: true, propagate: true, tooltip: warnings ? `${errors}, ${warnings}` : errors });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { call, commands, isTauri, type InstallCommand, type RunConfig } from "@/
|
||||
import { notifications, toolWindowService, type ExecutorLaunch, type RunExecutorContribution } from "@/workbench/contributions";
|
||||
import { coverageSupports, formatPercent } from "./model";
|
||||
import { addSuite } from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const errorText = (e: unknown) => (e instanceof Error ? e.message : String(e));
|
||||
|
||||
@@ -47,7 +48,7 @@ export function notifyMissingTool(
|
||||
level: "warning",
|
||||
key: `${group}-missing-${tool}`,
|
||||
title: `${tool} is not installed`,
|
||||
body: line ? `${missing.message} Install it with: ${line}` : missing.homepage ? `${missing.message} ${missing.homepage}` : missing.message,
|
||||
body: line ? tx`${missing.message} Install it with: ${line}` : missing.homepage ? `${missing.message} ${missing.homepage}` : missing.message,
|
||||
sticky: true,
|
||||
actions,
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CodePreview } from "./CodePreview";
|
||||
import { guessSeparator, importEdits, parseDelimited } from "./model";
|
||||
import { closeDialog, detailsKey, loadDetails, openConsoleWith, targetLabel, useDatabase } from "./store";
|
||||
import styles from "./Database.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function when(t: number): string {
|
||||
const d = new Date(t);
|
||||
@@ -60,7 +61,7 @@ export function HistoryDialog({ id, path }: { id: string; path: string | null })
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title={`${targetLabel(id)} Query History`}
|
||||
title={tx`${targetLabel(id)} Query History`}
|
||||
width={820}
|
||||
footerStart={
|
||||
<IconButton icon="trash" title="Clear History" disabled={!entries?.length} onClick={() => void remove(null)} />
|
||||
@@ -192,12 +193,12 @@ export function ImportDialog({ id, schema, table }: { id: string; schema: string
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title={`Import Data into ${schema}.${table}`}
|
||||
title={tx`Import Data into ${schema}.${table}`}
|
||||
width={720}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
{
|
||||
label: `Import ${edits.length} Rows`,
|
||||
label: tx`Import ${edits.length} Rows`,
|
||||
role: "default",
|
||||
disabled: !edits.length || !mapped.length || busy,
|
||||
onClick: () => void run(),
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "./resultView";
|
||||
import { openDialog } from "./store";
|
||||
import styles from "./Database.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export type DataPanel = "record" | "value" | "aggregates";
|
||||
|
||||
@@ -30,7 +31,7 @@ export function exportMenu(data: ExportData, source: string, format?: ExportForm
|
||||
{ label: "Export Data…", icon: "export", run: () => openDialog({ kind: "export", data, source, format }) },
|
||||
{ kind: "separator" },
|
||||
...EXPORT_FORMATS.map((f): MenuItem => ({
|
||||
label: `Copy as ${f.label}`,
|
||||
label: tx`Copy as ${f.label}`,
|
||||
icon: "copy",
|
||||
run: () => void navigator.clipboard?.writeText(exportData(f.id, data)),
|
||||
})),
|
||||
@@ -207,14 +208,14 @@ function FilterPopup(props: {
|
||||
anchor={props.anchor}
|
||||
placement="bottom-start"
|
||||
onClose={props.onClose}
|
||||
aria-label={`Local filter of ${props.column}`}
|
||||
aria-label={tx`Local filter of ${props.column}`}
|
||||
className={styles.filterPopup}
|
||||
trapFocus
|
||||
>
|
||||
<Input
|
||||
autoFocus
|
||||
aria-label="Search values"
|
||||
placeholder={`Values of ${props.column}`}
|
||||
placeholder={tx`Values of ${props.column}`}
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SourceDialog } from "./SourceDialog";
|
||||
import { closeDialog, useDatabase } from "./store";
|
||||
import styles from "./Database.module.css";
|
||||
import { isImeKey } from "@/platform/ime";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const ObjectDialogs = lazyComponent(() => import("./ObjectDialogs").then((m) => m.ObjectDialogs));
|
||||
const ExportDialog = lazyComponent(() => import("./ExportDialog").then((m) => m.ExportDialog));
|
||||
@@ -28,7 +29,7 @@ function PasswordDialog({ source, resolve }: { source: DataSource; resolve: (ok:
|
||||
<Dialog
|
||||
open
|
||||
onClose={() => done(false)}
|
||||
title={`Connect to ${source.name}`}
|
||||
title={tx`Connect to ${source.name}`}
|
||||
width={400}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
@@ -82,7 +83,7 @@ function SshSecretDialog({
|
||||
<Dialog
|
||||
open
|
||||
onClose={() => done(false)}
|
||||
title={`SSH: ${sshLabel(source) || source.name}`}
|
||||
title={tx`SSH: ${sshLabel(source) || source.name}`}
|
||||
width={440}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from "./store";
|
||||
import { openTableData } from "./treeActions";
|
||||
import styles from "./Database.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Read-only SQL with the editor's syntax colours (IDEA's dialog Preview pane). */
|
||||
function SqlPreview({ sql, label = "Preview" }: { sql: string; label?: string }) {
|
||||
@@ -287,7 +288,7 @@ function ModifyTableDialog(props: ModifyProps) {
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title={`Modify Table ${table}`}
|
||||
title={tx`Modify Table ${table}`}
|
||||
width={440}
|
||||
buttons={[{ label: "Cancel", role: "cancel" }]}
|
||||
>
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
type DbSslMode,
|
||||
type DbTxMode,
|
||||
} from "@/platform/ipc";
|
||||
import { useTx, useTxTemplate } from "@/platform/i18n";
|
||||
import { tx, useTx, useTxTemplate } from "@/platform/i18n";
|
||||
import { Button, Checkbox, Dialog, IconButton, Input, Select, Tabs, T, type SelectOption } from "@/ui";
|
||||
import { colorVar, connectRemedy, fileSourceName, newSourceId, parseSourceUrl, sshLabel } from "./model";
|
||||
import { closeDialog, loadDatabases, loadSchemas, saveSource, useDatabase } from "./store";
|
||||
@@ -224,7 +224,7 @@ function SecondsOption({
|
||||
<Input
|
||||
className={styles.secondsInput}
|
||||
inputMode="numeric"
|
||||
aria-label={`${label} (seconds)`}
|
||||
aria-label={tx`${label} (seconds)`}
|
||||
disabled={!on}
|
||||
value={draft}
|
||||
onChange={(e) => {
|
||||
@@ -243,14 +243,18 @@ function SecondsOption({
|
||||
|
||||
/** Schemas tab: "All …" or a choice of names and `*` / `?` patterns. */
|
||||
function NamePicker({
|
||||
title,
|
||||
allLabel,
|
||||
addLabel,
|
||||
all,
|
||||
chosen,
|
||||
known,
|
||||
onAll,
|
||||
onChosen,
|
||||
}: {
|
||||
title: string;
|
||||
/** "All databases" */
|
||||
allLabel: string;
|
||||
/** Accessible name of the pattern input. */
|
||||
addLabel: string;
|
||||
all: boolean;
|
||||
chosen: string[];
|
||||
known: string[];
|
||||
@@ -266,7 +270,7 @@ function NamePicker({
|
||||
};
|
||||
return (
|
||||
<div className={styles.namePicker}>
|
||||
<Checkbox checked={all} onChange={onAll} label={`All ${title}`} />
|
||||
<Checkbox checked={all} onChange={onAll} label={allLabel} />
|
||||
{!all && (
|
||||
<div className={styles.nameList}>
|
||||
{names.map((n) => (
|
||||
@@ -282,7 +286,7 @@ function NamePicker({
|
||||
className={styles.fill}
|
||||
value={pattern}
|
||||
placeholder="Name or pattern, e.g. app_*"
|
||||
aria-label={`Add ${title} pattern`}
|
||||
aria-label={addLabel}
|
||||
onChange={(e) => setPattern(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !isImeKey(e.nativeEvent)) {
|
||||
@@ -401,7 +405,7 @@ export function SourceDialog({
|
||||
setTest(null);
|
||||
try {
|
||||
const v = await call(commands.dbTest(effective(), password ?? null, sshOn ? (sshSecret ?? null) : null));
|
||||
setTest({ ok: true, text: `Succeeded: ${v}` });
|
||||
setTest({ ok: true, text: tx`Succeeded: ${v}` });
|
||||
} catch (e) {
|
||||
const text = e instanceof Error ? e.message : String(e);
|
||||
const trust = e instanceof IpcError && connectRemedy(e.kind, e.message) === "trustHostKey";
|
||||
@@ -802,7 +806,8 @@ export function SourceDialog({
|
||||
<div className={styles.formWide}>
|
||||
{postgres && (
|
||||
<NamePicker
|
||||
title="databases"
|
||||
allLabel={tx("All databases")}
|
||||
addLabel={tx("Add databases pattern")}
|
||||
all={intro.allDatabases !== false}
|
||||
chosen={intro.databases ?? []}
|
||||
known={knownDbs}
|
||||
@@ -811,7 +816,8 @@ export function SourceDialog({
|
||||
/>
|
||||
)}
|
||||
<NamePicker
|
||||
title={s.driver === "mysql" ? "schemas (databases)" : "schemas"}
|
||||
allLabel={s.driver === "mysql" ? tx("All schemas (databases)") : tx("All schemas")}
|
||||
addLabel={s.driver === "mysql" ? tx("Add schemas (databases) pattern") : tx("Add schemas pattern")}
|
||||
all={intro.allSchemas !== false}
|
||||
chosen={intro.schemas ?? []}
|
||||
known={knownSchemas}
|
||||
@@ -921,7 +927,7 @@ export function SourceDialog({
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title={initial ? `Data Source: ${initial.name}` : "New Data Source"}
|
||||
title={initial ? tx`Data Source: ${initial.name}` : "New Data Source"}
|
||||
width={640}
|
||||
footerStart={
|
||||
<Button onClick={() => void runTest()} disabled={busy || !valid}>
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import { openDialog, sourceById, withConnection } from "./store";
|
||||
import type { TableRef } from "./tableInput";
|
||||
import styles from "./Database.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const PAGE_SIZES = ["100", "500", "1000", "5000"];
|
||||
|
||||
@@ -306,7 +307,7 @@ export function TableEditor({ input }: { input: EditorInput }) {
|
||||
) : (
|
||||
<div className={styles.fill}>
|
||||
<DataView
|
||||
aria-label={`${ref.table} data`}
|
||||
aria-label={tx`${ref.table} data`}
|
||||
columns={gridColumns}
|
||||
rowCount={rowCount}
|
||||
fixedRows={loaded}
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
useDatabase,
|
||||
} from "./store";
|
||||
import styles from "./Database.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** `templates` feature's language-data key for finer template contexts. */
|
||||
const TEMPLATE_CONTEXT_KEY = "openideaTemplateContexts";
|
||||
@@ -287,7 +288,7 @@ function ConsoleBar({ path, view }: { path: string; view: EditorView }) {
|
||||
<Select
|
||||
aria-label="Row limit"
|
||||
value={String(maxRows)}
|
||||
options={ROW_LIMITS.map((v) => ({ value: v, label: `${Number(v).toLocaleString("en")} rows` }))}
|
||||
options={ROW_LIMITS.map((v) => ({ value: v, label: tx`${Number(v).toLocaleString("en")} rows` }))}
|
||||
onChange={(v) => useDatabase.setState({ maxRows: Number(v) })}
|
||||
/>
|
||||
<span className={styles.toolbarSeparator} />
|
||||
|
||||
@@ -38,6 +38,7 @@ import type { ExportData, ExportFormat } from "./export";
|
||||
import type { DbObjectRef } from "./objects";
|
||||
import { formatted } from "./sql/format";
|
||||
import { bindParams, findParams } from "./sql/params";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export const DB_TOOL_WINDOW = "database";
|
||||
export const RESULTS_VIEW = "dbResults";
|
||||
@@ -281,7 +282,7 @@ export async function renameSource(id: string, name: string): Promise<void> {
|
||||
export async function removeSource(source: DataSource): Promise<void> {
|
||||
const ok = await confirm({
|
||||
title: "Remove Data Source",
|
||||
message: `Remove data source '${source.name}'? Its query console file stays on disk.`,
|
||||
message: tx`Remove data source '${source.name}'? Its query console file stays on disk.`,
|
||||
ok: "Remove",
|
||||
});
|
||||
if (!ok) return;
|
||||
@@ -788,7 +789,7 @@ export async function executeConsole(
|
||||
.filter((o) => o.result?.columns.length)
|
||||
.map((outcome) => {
|
||||
const n = nextTab++;
|
||||
return { id: n, title: `Result ${n}`, path, sourceId: id, outcome };
|
||||
return { id: n, title: tx`Result ${n}`, path, sourceId: id, outcome };
|
||||
});
|
||||
const failed = outcomes.some((o) => o.error);
|
||||
useDatabase.setState((s) => ({
|
||||
@@ -957,7 +958,7 @@ export async function finishTransaction(id: string, commit: boolean): Promise<bo
|
||||
async function askEndTransaction(source: DataSource): Promise<boolean | null> {
|
||||
const { button } = await ask({
|
||||
title: "Switch to Auto-Commit",
|
||||
body: `${source.name} has uncommitted changes. Commit or roll them back before switching to auto-commit?`,
|
||||
body: tx`${source.name} has uncommitted changes. Commit or roll them back before switching to auto-commit?`,
|
||||
buttons: [
|
||||
{ id: "rollback", label: "Roll Back" },
|
||||
{ id: "commit", label: "Commit", primary: true },
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
removeSource,
|
||||
} from "./store";
|
||||
import { tableInput } from "./tableInput";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Context key: the Database tree has focus. */
|
||||
export const DB_VIEW_FOCUS = "databaseViewFocus";
|
||||
@@ -57,11 +58,12 @@ function sourceOf(n: DbNode | null): DataSource | undefined {
|
||||
return n ? sourceById(nodeTarget(n)) : undefined;
|
||||
}
|
||||
|
||||
async function copy(text: string, what: string) {
|
||||
/** Copies `text`; `failure` is the notification title when the clipboard refuses it. */
|
||||
async function copy(text: string, failure: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} catch {
|
||||
notifications().notify({ level: "error", title: `Cannot copy the ${what}` });
|
||||
notifications().notify({ level: "error", title: failure });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,10 +375,10 @@ export function registerDatabaseTreeActions(): () => void {
|
||||
const n = nodeFrom(arg);
|
||||
if (!n) return;
|
||||
const id = nodeTarget(n);
|
||||
if (n.kind === "source") return void copy(n.source.name ?? "", "name");
|
||||
if (n.kind === "database") return void copy(n.name, "name");
|
||||
if (n.kind === "source") return void copy(n.source.name ?? "", tx("Cannot copy the name"));
|
||||
if (n.kind === "database") return void copy(n.name, tx("Cannot copy the name"));
|
||||
const o = objectOf(n);
|
||||
if (id && o) void copy(reference(driverOf(id), o), "reference");
|
||||
if (id && o) void copy(reference(driverOf(id), o), tx("Cannot copy the reference"));
|
||||
},
|
||||
}),
|
||||
tree({
|
||||
@@ -397,7 +399,7 @@ export function registerDatabaseTreeActions(): () => void {
|
||||
run: async ({ arg }) => {
|
||||
const n = nodeFrom(arg);
|
||||
const ddl = n && (await ddlOf(n));
|
||||
if (ddl) await copy(ddl.text, "DDL");
|
||||
if (ddl) await copy(ddl.text, tx("Cannot copy the DDL"));
|
||||
},
|
||||
}),
|
||||
tree({
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
openSmartStepChooser,
|
||||
useOverlays,
|
||||
} from "./ui/popoverStore";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function isRunConfig(arg: unknown): arg is RunConfig {
|
||||
return !!arg && typeof arg === "object" && "command" in arg && "args" in arg && "debugAdapter" in arg;
|
||||
@@ -159,7 +160,7 @@ async function listenForJvm(name: string, host: string, port: number): Promise<n
|
||||
key,
|
||||
sticky: true,
|
||||
title: `'${name}': listening for the remote JVM on port ${port}`,
|
||||
body: `Start the remote JVM with -agentlib:jdwp=transport=dt_socket,server=n,address=${host}:${port},suspend=y`,
|
||||
body: tx`Start the remote JVM with -agentlib:jdwp=transport=dt_socket,server=n,address=${host}:${port},suspend=y`,
|
||||
actions: [{ label: "Cancel", run: () => void commands.cancelRequest(request) }],
|
||||
});
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { call, commands, isTauri, type DebugEvent, type DebugSessionId } from "@/platform/ipc";
|
||||
import { maybeEditorService, notifications } from "@/workbench/contributions";
|
||||
import { isLive, useDebug, type DebugSession } from "./state";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const KEY = "debug.hotswap";
|
||||
/** `BUILD_COMPLETE` events of our own build are not worth a prompt. */
|
||||
@@ -25,7 +26,7 @@ function plural(n: number, word: string) {
|
||||
export function hotSwapMessage(changed: number, errors: string[]): { level: "info" | "error"; title: string; body?: string } {
|
||||
if (errors.length) return { level: "error", title: "Hot Swap failed", body: errors.join("\n") };
|
||||
if (changed === 0) return { level: "info", title: "Loaded classes are up to date. Nothing to reload." };
|
||||
return { level: "info", title: `${plural(changed, "class")} reloaded` };
|
||||
return { level: "info", title: tx`${plural(changed, "class")} reloaded` };
|
||||
}
|
||||
|
||||
/** Compiles (unless `build` is false) and reloads changed classes into every Java debug session. */
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
import { IpcError } from "@/platform/ipc";
|
||||
|
||||
/** Adapters the tools installer can provide (adapter id → tool name). */
|
||||
const INSTALLABLE: Record<string, { tool: string; label: string }> = {
|
||||
debugpy: { tool: "debugpy", label: "debugpy" },
|
||||
"js-debug": { tool: "js-debug", label: "the JavaScript debugger (vscode-js-debug)" },
|
||||
delve: { tool: "delve", label: "Delve" },
|
||||
codelldb: { tool: "codelldb", label: "CodeLLDB" },
|
||||
const INSTALLABLE: Record<string, { tool: string; title: string }> = {
|
||||
debugpy: { tool: "debugpy", title: "debugpy is not installed" },
|
||||
"js-debug": { tool: "js-debug", title: "The JavaScript debugger (vscode-js-debug) is not installed" },
|
||||
delve: { tool: "delve", title: "Delve is not installed" },
|
||||
codelldb: { tool: "codelldb", title: "CodeLLDB is not installed" },
|
||||
};
|
||||
|
||||
export interface MissingAdapter {
|
||||
@@ -25,14 +25,14 @@ export function missingAdapter(adapter: string, e: unknown): MissingAdapter | nu
|
||||
if (adapter === "debugpy" && e.message.startsWith("debugpy is not installed")) {
|
||||
return {
|
||||
tool: entry.tool,
|
||||
title: "debugpy is not installed",
|
||||
title: entry.title,
|
||||
body: `${e.message}. OpenIDEA can install a private copy that every Python interpreter uses for debugging.`,
|
||||
};
|
||||
}
|
||||
if (!e.message.startsWith(`debug adapter \`${adapter}\` not found`)) return null;
|
||||
return {
|
||||
tool: entry.tool,
|
||||
title: `${entry.label[0]!.toUpperCase()}${entry.label.slice(1)} is not installed`,
|
||||
title: entry.title,
|
||||
body: e.message,
|
||||
};
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
} from "../state";
|
||||
import { VariablesView, type VarRoot } from "./VariablesView";
|
||||
import styles from "./DebugToolWindow.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function useActionEnabled(id: string): boolean {
|
||||
const ctx = useContextKeys();
|
||||
@@ -114,7 +115,7 @@ export function DebugTabs() {
|
||||
if (!list.length) return null;
|
||||
const items: TabItem[] = list.map((s) => ({
|
||||
id: String(s.id),
|
||||
label: s.parent != null ? `${s.name} (child)` : s.name,
|
||||
label: s.parent != null ? tx`${s.name} (child)` : s.name,
|
||||
text: s.name,
|
||||
icon: "debug",
|
||||
italic: s.status === "terminated",
|
||||
|
||||
@@ -34,6 +34,7 @@ import { VariablesView, type VarRoot } from "./VariablesView";
|
||||
import { pushHistory, readHistory } from "./expressionHistory";
|
||||
import styles from "./Overlays.module.css";
|
||||
import { isImeKey } from "@/platform/ime";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function DebuggerOverlays() {
|
||||
return (
|
||||
@@ -447,7 +448,7 @@ function BreakpointsDialog() {
|
||||
<Checkbox
|
||||
checked={r.bp.enabled}
|
||||
onChange={(enabled) => updateSpecial(r.bp.id, { enabled })}
|
||||
aria-label={`Enable ${specialTitle(r.bp)}`}
|
||||
aria-label={tx`Enable ${specialTitle(r.bp)}`}
|
||||
/>
|
||||
<Icon name={r.bp.kind === "method" ? "breakpoint-method" : "breakpoint-field"} size={14} />
|
||||
<span className={styles.bpName}>{specialTitle(r.bp)}</span>
|
||||
@@ -467,7 +468,7 @@ function BreakpointsDialog() {
|
||||
<Checkbox
|
||||
checked={r.bp.enabled}
|
||||
onChange={(enabled) => updateBreakpoint(r.path, r.bp.id, { enabled })}
|
||||
aria-label={`Enable breakpoint at ${basename(r.path)}:${r.bp.line + 1}`}
|
||||
aria-label={tx`Enable breakpoint at ${basename(r.path)}:${r.bp.line + 1}`}
|
||||
/>
|
||||
<span
|
||||
className={styles.bpDot}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import { addComment, checkoutPullRequest, openCommentLocation, openFileDiff, openUrl, submitReview } from "./ops";
|
||||
import { cancelBusy, loadDetails, refreshAll, select, setQuery, useGithub, type DetailTab } from "./store";
|
||||
import styles from "./Github.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const STATES: Array<{ value: PrStateFilter; label: string }> = [
|
||||
{ value: "open", label: "Open" },
|
||||
@@ -515,9 +516,9 @@ function Details() {
|
||||
aria-label="Pull request"
|
||||
items={[
|
||||
{ id: "overview", label: "Overview" },
|
||||
{ id: "files", label: `Files ${pr.files.length}` },
|
||||
{ id: "commits", label: `Commits ${pr.commits.length}` },
|
||||
{ id: "conversation", label: `Conversation ${pr.timeline.length + comments.length}` },
|
||||
{ id: "files", label: tx`Files ${pr.files.length}` },
|
||||
{ id: "commits", label: tx`Commits ${pr.commits.length}` },
|
||||
{ id: "conversation", label: tx`Conversation ${pr.timeline.length + comments.length}` },
|
||||
]}
|
||||
/>
|
||||
{tab === "overview" && <Overview pr={pr} />}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { ReviewEvent } from "@/platform/ipc";
|
||||
import { call, commands } from "@/platform/ipc";
|
||||
import { Checkbox, Dialog, Input, Select, T } from "@/ui";
|
||||
import styles from "./Github.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
interface Entry {
|
||||
id: number;
|
||||
@@ -134,7 +135,7 @@ function CreateDialog(props: {
|
||||
/>
|
||||
<div className={styles.formRow}>
|
||||
<Checkbox checked={draft} onChange={setDraft} label="Draft" />
|
||||
<Checkbox checked={push} onChange={setPush} label={`Push '${props.head}' first`} />
|
||||
<Checkbox checked={push} onChange={setPush} label={tx`Push '${props.head}' first`} />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
@@ -165,7 +166,7 @@ function ReviewDialog(props: {
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
title={`Review #${props.number}`}
|
||||
title={tx`Review #${props.number}`}
|
||||
onClose={() => props.onDone(null)}
|
||||
width={520}
|
||||
buttons={[
|
||||
|
||||
@@ -10,6 +10,7 @@ import { EmptyState, IconButton, SplitGroup, SplitHandle, SplitPanel, Spinner, T
|
||||
import { bodyKind, formatMs, formatSize, prettyBody } from "./format";
|
||||
import { cancelRunning, clearHistory, refreshHistory, selectEntry, useHttpClient } from "./store";
|
||||
import styles from "./HttpClient.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function statusTone(status: number | null | undefined): string {
|
||||
if (status == null) return styles.statusError ?? "";
|
||||
@@ -73,7 +74,7 @@ function ExchangeView({ ex }: { ex: HttpExchange }) {
|
||||
onSelect={(id) => setTab(id as Tab)}
|
||||
items={[
|
||||
{ id: "body", label: "Response" },
|
||||
{ id: "headers", label: `Headers${r ? ` (${r.headers.length})` : ""}` },
|
||||
{ id: "headers", label: r ? tx`Headers (${r.headers.length})` : "Headers" },
|
||||
{ id: "request", label: "Request" },
|
||||
]}
|
||||
trailing={
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useState } from "react";
|
||||
import { create } from "zustand";
|
||||
import { executeAction, getAction } from "@/platform/actions";
|
||||
import { t } from "@/platform/i18n";
|
||||
import { t, tx } from "@/platform/i18n";
|
||||
import { BarButton, Button, Dialog, Icon, List, T } from "@/ui";
|
||||
import { confirm } from "@/workbench/messages";
|
||||
import { promptInput } from "@/workbench/inputDialogs";
|
||||
@@ -60,7 +60,7 @@ function EditMacrosDialog({ onClose }: { onClose: () => void }) {
|
||||
};
|
||||
const remove = async () => {
|
||||
if (!macro) return;
|
||||
if (await confirm({ title: "Delete Macro", message: `Delete the macro '${macro.name}'?`, ok: "Delete", icon: "warning" }))
|
||||
if (await confirm({ title: "Delete Macro", message: tx`Delete the macro '${macro.name}'?`, ok: "Delete", icon: "warning" }))
|
||||
deleteMacro(macro.name);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { indicatorLabel, indicatorTooltip, memorySections, summarizeMemory } fro
|
||||
import { startPolling } from "./poll";
|
||||
import { METRICS_INTERVAL_MS, refreshMetrics, usePerf } from "./store";
|
||||
import styles from "./perf.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Status bar widget id (hidden by default, toggled from the status bar context menu). */
|
||||
export const MEMORY_WIDGET = { id: "perf.memory", title: "Memory Indicator", defaultVisible: false } as const;
|
||||
@@ -41,7 +42,7 @@ export function MemoryStatus() {
|
||||
ref={ref}
|
||||
className={styles.indicator}
|
||||
aria-expanded={open}
|
||||
aria-label={`Memory: ${tooltip}`}
|
||||
aria-label={tx`Memory: ${tooltip}`}
|
||||
tooltip={tooltip}
|
||||
onClick={() => usePerf.setState({ popupOpen: !open })}
|
||||
>
|
||||
@@ -101,7 +102,7 @@ export function MemoryDetails({ metrics: m }: { metrics: AppMetrics }) {
|
||||
label={r.label}
|
||||
detail={r.detail}
|
||||
value={r.value}
|
||||
title={r.processes > 1 ? `${r.processes} processes` : undefined}
|
||||
title={r.processes > 1 ? tx`${r.processes} processes` : undefined}
|
||||
indented
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useProject } from "@/platform/project";
|
||||
import { formatSize } from "./format";
|
||||
import { MEMORY_WIDGET } from "./MemoryStatus";
|
||||
import { clearCaches, refreshMetrics, usePerf } from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function memoryIndicatorShown(): boolean {
|
||||
const overrides = useProject.getState().settings?.appearance?.statusBarWidgets;
|
||||
@@ -30,12 +31,15 @@ export function perfActions(): Action[] {
|
||||
}
|
||||
const memory =
|
||||
r.rssBeforeBytes != null && r.rssAfterBytes != null
|
||||
? ` Memory ${formatSize(r.rssBeforeBytes)} → ${formatSize(r.rssAfterBytes)}.`
|
||||
? tx` Memory ${formatSize(r.rssBeforeBytes)} → ${formatSize(r.rssAfterBytes)}.`
|
||||
: "";
|
||||
notifications().notify({
|
||||
level: "info",
|
||||
title: "Caches cleared",
|
||||
body: `${r.syntaxTrees} syntax tree${r.syntaxTrees === 1 ? "" : "s"} dropped, index compacted.${memory}`,
|
||||
body:
|
||||
r.syntaxTrees === 1
|
||||
? tx`${r.syntaxTrees} syntax tree dropped, index compacted.${memory}`
|
||||
: tx`${r.syntaxTrees} syntax trees dropped, index compacted.${memory}`,
|
||||
key: "perf.clearCaches",
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
usePlugins,
|
||||
} from "./store";
|
||||
import styles from "./PluginsPage.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const STATUS: Record<PluginStatus, { label: string; tone: "success" | "accent" | "neutral" | "danger" }> = {
|
||||
active: { label: "Running", tone: "success" },
|
||||
@@ -137,7 +138,7 @@ function InstalledRow({ plugin, selected, onSelect }: { plugin: PluginInfo; sele
|
||||
checked={plugin.enabled}
|
||||
disabled={busy}
|
||||
onChange={(on) => void setPluginEnabled(plugin.id, on)}
|
||||
aria-label={`${plugin.enabled ? "Disable" : "Enable"} ${plugin.name}`}
|
||||
aria-label={plugin.enabled ? tx`Disable ${plugin.name}` : tx`Enable ${plugin.name}`}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
@@ -308,7 +309,7 @@ function InstalledDetails({ plugin }: { plugin: PluginInfo }) {
|
||||
icon="run"
|
||||
disabled={!running}
|
||||
onClick={() => void runPluginCommand(plugin.id, c.id)}
|
||||
aria-label={`Run ${c.title}`}
|
||||
aria-label={tx`Run ${c.title}`}
|
||||
>
|
||||
<T>Run</T>
|
||||
</Button>
|
||||
@@ -338,7 +339,7 @@ function InstalledDetails({ plugin }: { plugin: PluginInfo }) {
|
||||
variant="ghost"
|
||||
disabled={!live}
|
||||
onClick={() => void selectPluginTheme(plugin.id, t.id)}
|
||||
aria-label={`Apply theme ${t.name}`}
|
||||
aria-label={tx`Apply theme ${t.name}`}
|
||||
>
|
||||
<T>Apply</T>
|
||||
</Button>
|
||||
@@ -407,7 +408,7 @@ function InstalledDetails({ plugin }: { plugin: PluginInfo }) {
|
||||
variant="primary"
|
||||
disabled={busy}
|
||||
onClick={() => void uninstallPlugin(plugin.id)}
|
||||
aria-label={`Confirm uninstall ${plugin.name}`}
|
||||
aria-label={tx`Confirm uninstall ${plugin.name}`}
|
||||
>
|
||||
<T>Uninstall</T>
|
||||
</Button>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { notifications } from "@/workbench/contributions";
|
||||
import { applyThemeAttributes, currentThemeId, syncWindowBackground, windowBackgroundHex } from "@/workbench/theme";
|
||||
import { pluginsBackend } from "./backend";
|
||||
import { refreshPlugins, runPluginCommand, usePlugins } from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export const pluginActionId = (pluginId: string, commandId: string) => `plugin.${pluginId}.${commandId}`;
|
||||
|
||||
@@ -25,7 +26,7 @@ export function pluginActions(plugins: readonly PluginInfo[]): Action[] {
|
||||
category: c.category ?? p.name,
|
||||
icon: "package",
|
||||
keywords: ["plugin", p.name, p.id],
|
||||
description: `From plugin ${p.name}`,
|
||||
description: tx`From plugin ${p.name}`,
|
||||
run: async ({ arg }) => {
|
||||
await runPluginCommand(p.id, c.id, typeof arg === "string" ? arg : null);
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import { call, commands, isTauri, type ProfilerInstall, type RunConfig } from "@
|
||||
import { notifications, toolWindowService, type ExecutorLaunch, type RunExecutorContribution } from "@/workbench/contributions";
|
||||
import { profilerSupports } from "./model";
|
||||
import { addSnapshot } from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const errorText = (e: unknown) => (e instanceof Error ? e.message : String(e));
|
||||
|
||||
@@ -35,7 +36,7 @@ function notifyMissing(tool: string, message: string, install: ProfilerInstall |
|
||||
level: "warning",
|
||||
key: `profiler-missing-${tool}`,
|
||||
title: `${tool} is not installed`,
|
||||
body: line ? `${message} Install it with: ${line}` : homepage ? `${message} ${homepage}` : message,
|
||||
body: line ? tx`${message} Install it with: ${line}` : homepage ? `${message} ${homepage}` : message,
|
||||
sticky: true,
|
||||
actions,
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ import clsx from "clsx";
|
||||
import { useEditorRegistry } from "@/editor/api";
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import { setContext } from "@/platform/context";
|
||||
import { useTxTemplate } from "@/platform/i18n";
|
||||
import { tx, useTxTemplate } from "@/platform/i18n";
|
||||
import { isTauri } from "@/platform/ipc";
|
||||
import type { RootFolder } from "@/platform/languageSettings";
|
||||
import { useEffectiveRoots } from "@/platform/projectRoots";
|
||||
@@ -460,7 +460,7 @@ export function ProjectView() {
|
||||
setDropTarget(valid ? target.key : null);
|
||||
setDrag({
|
||||
paths,
|
||||
label: paths.length === 1 ? nameOf(paths[0]!) : `${paths.length} items`,
|
||||
label: paths.length === 1 ? nameOf(paths[0]!) : tx`${paths.length} items`,
|
||||
x: ev.clientX,
|
||||
y: ev.clientY,
|
||||
copy: ev.altKey,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Checkbox, Dialog, Icon, fileIconName } from "@/ui";
|
||||
import { nameOf, relativeTo } from "./paths";
|
||||
import { useProjectTree } from "./store";
|
||||
import styles from "./FileRefactor.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
type EditFile = Extract<WorkspaceEdit["files"][number], { kind: "edit" }>;
|
||||
|
||||
@@ -67,7 +68,7 @@ function FileGroup({ file, root, included, onToggle }: { file: EditFile; root: s
|
||||
return (
|
||||
<li className={styles.previewFile}>
|
||||
<div className={styles.previewFileRow}>
|
||||
<Checkbox checked={included} onChange={onToggle} aria-label={`Include ${nameOf(file.path)}`} />
|
||||
<Checkbox checked={included} onChange={onToggle} aria-label={tx`Include ${nameOf(file.path)}`} />
|
||||
<Icon name={fileIconName(nameOf(file.path))} size={14} />
|
||||
<span className={styles.previewName}>{nameOf(file.path)}</span>
|
||||
<span className={styles.previewPath}>{relativeTo(root, file.path) || file.path}</span>
|
||||
@@ -117,7 +118,7 @@ function PreviewDialog({ request }: { request: PreviewRequest }) {
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
title={`Refactoring Preview: ${request.title}`}
|
||||
title={tx`Refactoring Preview: ${request.title}`}
|
||||
onClose={() => request.resolve(null)}
|
||||
width={680}
|
||||
buttons={[
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
setViewOption,
|
||||
useProjectTree,
|
||||
} from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const FOCUS = "projectViewFocus";
|
||||
const OPEN = "projectOpen";
|
||||
@@ -150,7 +151,7 @@ export function registerProjectActions(): () => void {
|
||||
|
||||
const toggles: Action[] = OPTION_LABELS.map(([key, label]) => ({
|
||||
id: `project.option.${key}`,
|
||||
title: `Project View: ${label}`,
|
||||
title: tx`Project View: ${label}`,
|
||||
category: "View",
|
||||
keywords: ["project", "tree", "toggle"],
|
||||
toggled: () => useProjectTree.getState().options[key],
|
||||
@@ -159,7 +160,7 @@ export function registerProjectActions(): () => void {
|
||||
|
||||
const modes: Action[] = VIEW_MODES.map(([mode, label]) => ({
|
||||
id: `project.viewMode.${mode}`,
|
||||
title: `Project View: Show ${label}`,
|
||||
title: tx`Project View: Show ${label}`,
|
||||
category: "View",
|
||||
when: OPEN,
|
||||
keywords: ["project", "pane", "scope", "view as"],
|
||||
@@ -335,7 +336,7 @@ export function registerProjectActions(): () => void {
|
||||
copyPath("name", "project.copyFileName", "Copy File Name"),
|
||||
{
|
||||
id: "project.revealInFinder",
|
||||
title: `Reveal in ${revealLabel()}`,
|
||||
title: tx`Reveal in ${revealLabel()}`,
|
||||
category: "Project View",
|
||||
icon: "external-link",
|
||||
when: OPEN,
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
vcsArg,
|
||||
} from "./operations";
|
||||
import { isSameOrInside } from "./paths";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Keys handled by the project view itself (the default keymap has no entries for them). */
|
||||
export const LOCAL_KEYS: Record<string, string> = {
|
||||
@@ -352,7 +353,7 @@ export function contextMenuItems(nodes: ProjectNode[]): MenuItem[] {
|
||||
{
|
||||
id: "unmark",
|
||||
label:
|
||||
current === "excluded" ? "Cancel Exclusion" : `Unmark as ${roles.find(([r]) => r === current)![1]}`,
|
||||
current === "excluded" ? "Cancel Exclusion" : tx`Unmark as ${roles.find(([r]) => r === current)![1]}`,
|
||||
run: () => void markDirectory(single.path, null),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -12,6 +12,7 @@ import { projectFs } from "./fs";
|
||||
import { isSameOrInside, nameOf, parentOf } from "./paths";
|
||||
import { previewFileRefactoring } from "./RefactoringPreview";
|
||||
import { useProjectTree } from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export interface FileRefactorOptions {
|
||||
/** Update references through the language servers. */
|
||||
@@ -262,7 +263,7 @@ export async function runFileRefactoring(
|
||||
notifications().notify({
|
||||
level: "info",
|
||||
title,
|
||||
body: edited ? `Updated references in ${edited} ${edited === 1 ? "file" : "files"}` : undefined,
|
||||
body: !edited ? undefined : edited === 1 ? tx`Updated references in ${edited} file` : tx`Updated references in ${edited} files`,
|
||||
actions: [{ label: "Undo", run: () => void undoFileRefactoring(id) }],
|
||||
key: "project.refactor.done",
|
||||
});
|
||||
@@ -296,7 +297,7 @@ export async function undoFileRefactoring(id = lastMove?.id): Promise<boolean> {
|
||||
level: outcome.conflicts.length ? "warning" : "info",
|
||||
title: "Refactoring undone",
|
||||
body: outcome.conflicts.length
|
||||
? `Changed since the refactoring, not reverted: ${outcome.conflicts.map(nameOf).join(", ")}`
|
||||
? tx`Changed since the refactoring, not reverted: ${outcome.conflicts.map(nameOf).join(", ")}`
|
||||
: undefined,
|
||||
key: "project.refactor.done",
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
startEditing,
|
||||
useProjectTree,
|
||||
} from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Nodes that may be renamed, moved or deleted (not roots, groups or library sources). */
|
||||
const mutable = (n: ProjectNode) => !n.root && !n.placeholder && !n.readOnly;
|
||||
@@ -367,7 +368,7 @@ export async function safeToDelete(files: string[]): Promise<boolean> {
|
||||
if (user.choice) return user.choice === "skip";
|
||||
if (result.status !== "ok" || !result.usages.length) return true;
|
||||
const name = files.length === 1 ? nameOf(files[0]!) : `${files.length} files`;
|
||||
const subject = files.length === 1 ? `File '${name}'` : `The ${name}`;
|
||||
const subject = files.length === 1 ? tx`File '${name}'` : tx`The ${files.length} files`;
|
||||
const answer = await askUsagesDetected(subject, result.usages, files.length > 1);
|
||||
if (answer === "view") viewUsages(name, result.usages);
|
||||
return answer === "delete";
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { RunConfig } from "@/platform/ipc";
|
||||
import { commandLine, configIcon, groupConfigs } from "./configs";
|
||||
import { canDeleteConfig, CURRENT_FILE, deleteConfig, saveTemporary, selectConfig, startRun, useRun } from "./store";
|
||||
import styles from "./Run.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
type Row =
|
||||
| { type: "section"; key: string; label: string }
|
||||
@@ -281,7 +282,7 @@ function ConfigChooser() {
|
||||
icon="run"
|
||||
size="small"
|
||||
iconSize={11}
|
||||
title={`Run '${c.name}'`}
|
||||
title={tx`Run '${c.name}'`}
|
||||
tabIndex={-1}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -292,7 +293,7 @@ function ConfigChooser() {
|
||||
icon="debug"
|
||||
size="small"
|
||||
iconSize={13}
|
||||
title={`Debug '${c.name}'`}
|
||||
title={tx`Debug '${c.name}'`}
|
||||
tabIndex={-1}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -545,7 +545,7 @@ export function ConfigForm({ config, all, type, errors, template, onChange, modi
|
||||
icon="close"
|
||||
size="small"
|
||||
iconSize={10}
|
||||
title={`Remove '${OPTION_LABELS[id]}'`}
|
||||
title={tx`Remove '${OPTION_LABELS[id]}'`}
|
||||
onClick={() => update(optionPatch(id, false))}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { TestTreeView, formatDuration } from "./TestTreeView";
|
||||
import { StackTracePanel, closeStackTrace, useStackTraces, type StackTraceTab } from "./stackTrace";
|
||||
import styles from "./Run.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function failed(s: RunSession): boolean {
|
||||
return s.status === "failed" || (s.status === "finished" && s.exitCode !== 0 && !s.stopped);
|
||||
@@ -81,7 +82,7 @@ function SessionPanel({ session }: { session: RunSession }) {
|
||||
cwd={cwd}
|
||||
onData={(data) => writeTerminal(session.id, data)}
|
||||
onResize={(cols, rows) => resizeTerminal(session.id, cols, rows)}
|
||||
aria-label={`${session.config.name} terminal`}
|
||||
aria-label={tx`${session.config.name} terminal`}
|
||||
/>
|
||||
) : (
|
||||
<ConsoleView
|
||||
@@ -102,7 +103,7 @@ function SessionPanel({ session }: { session: RunSession }) {
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
aria-label={`${session.config.name} output`}
|
||||
aria-label={tx`${session.config.name} output`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -196,7 +197,7 @@ function sessionItems(sessions: readonly RunSession[]): TabItem[] {
|
||||
closable: true,
|
||||
tooltip:
|
||||
s.status === "finished"
|
||||
? `Finished in ${formatDuration((s.endedAt ?? s.startedAt) - s.startedAt)}, exit code ${s.exitCode ?? "?"}`
|
||||
? tx`Finished in ${formatDuration((s.endedAt ?? s.startedAt) - s.startedAt)}, exit code ${s.exitCode ?? "?"}`
|
||||
: undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export function RunWidget() {
|
||||
size="large"
|
||||
iconSize={16}
|
||||
className={clsx(styles.widgetButton, styles.widgetFilled, styles.widgetFilledRun)}
|
||||
title={`Rerun '${compoundRunning.length && selected ? selected.name : target.config.name}'`}
|
||||
title={tx`Rerun '${compoundRunning.length && selected ? selected.name : target.config.name}'`}
|
||||
shortcut={shortcutLabel("run.run")}
|
||||
onClick={() => (compoundRunning.length && selected ? void startRun(selected) : rerunSession(target.id))}
|
||||
/>
|
||||
@@ -96,10 +96,10 @@ export function RunWidget() {
|
||||
className={clsx(styles.widgetButton, styles.widgetFilled, styles.widgetFilledStop)}
|
||||
title={
|
||||
compoundRunning.length && selected
|
||||
? `Stop '${selected.name}'`
|
||||
? tx`Stop '${selected.name}'`
|
||||
: running.length > 1
|
||||
? `Stop (${running.length} running)`
|
||||
: `Stop '${target.config.name}'`
|
||||
? tx`Stop (${running.length} running)`
|
||||
: tx`Stop '${target.config.name}'`
|
||||
}
|
||||
shortcut={shortcutLabel("run.stop")}
|
||||
onClick={() =>
|
||||
@@ -155,8 +155,8 @@ export function RunWidget() {
|
||||
title={
|
||||
selected
|
||||
? debugOnly
|
||||
? `'${selected.name}' can only be debugged`
|
||||
: `Run '${selected.name}'`
|
||||
? tx`'${selected.name}' can only be debugged`
|
||||
: tx`Run '${selected.name}'`
|
||||
: currentFile
|
||||
? "Run Current File"
|
||||
: "Run"
|
||||
@@ -170,7 +170,7 @@ export function RunWidget() {
|
||||
size="large"
|
||||
iconSize={18}
|
||||
className={styles.widgetButton}
|
||||
title={selected ? `Debug '${selected.name}'` : currentFile ? "Debug Current File" : "Debug"}
|
||||
title={selected ? tx`Debug '${selected.name}'` : currentFile ? "Debug Current File" : "Debug"}
|
||||
shortcut={shortcutLabel("run.debug")}
|
||||
disabled={!selected && !currentFile}
|
||||
onClick={() => void executeAction("run.debug")}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { openTarget } from "./navigate";
|
||||
import { nodeStatus, type TestNode, type TestResults, type TestStatus } from "./testing";
|
||||
import { parseComparison } from "./testing/comparison";
|
||||
import styles from "./Run.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Test tree filters, kept across runs and restarts (IDEA hides passed tests by default). */
|
||||
const FILTERS_KEY = "openidea.run.testFilters";
|
||||
@@ -39,7 +40,7 @@ export function showTestDiff(node: TestNode): boolean {
|
||||
if (!c) return false;
|
||||
void executeAction("diff.compareTexts", {
|
||||
path: `${node.path.join("/")}.txt`,
|
||||
title: `Comparison Failure: ${node.name}`,
|
||||
title: tx`Comparison Failure: ${node.name}`,
|
||||
left: { text: c.expected, label: c.labels?.expected ?? "Expected" },
|
||||
right: { text: c.actual, label: c.labels?.actual ?? "Actual" },
|
||||
});
|
||||
@@ -136,8 +137,8 @@ export function TestTreeView({ results, version, cwd, selected, onSelect, runnin
|
||||
const items: MenuItem[] = [];
|
||||
if (onRunTests) {
|
||||
items.push(
|
||||
{ label: `Run '${name}'`, verbatim: true, icon: "run", run: () => onRunTests(ids, false) },
|
||||
{ label: `Debug '${name}'`, verbatim: true, icon: "debug", run: () => onRunTests(ids, true) },
|
||||
{ label: tx`Run '${name}'`, verbatim: true, icon: "run", run: () => onRunTests(ids, false) },
|
||||
{ label: tx`Debug '${name}'`, verbatim: true, icon: "debug", run: () => onRunTests(ids, true) },
|
||||
{ kind: "separator" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import { runWithExecutor } from "./executors";
|
||||
import { goModulePath, loadGoModules } from "./goModules";
|
||||
import { toolEnv } from "./runtimes";
|
||||
import { adoptTemporary, loadConfigs, modifyTemporary, startRun, useRun } from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const CONTENT_LIMIT = 400_000;
|
||||
|
||||
@@ -180,7 +181,7 @@ export function choiceItems(choices: readonly Produced[], debug: boolean): MenuI
|
||||
{ kind: "header", label: debug ? "Choose configuration to debug" : "Choose configuration to run" },
|
||||
...choices.map(
|
||||
(p): MenuItem => ({
|
||||
label: `${debug ? "Debug" : "Run"} '${p.name}'`,
|
||||
label: debug ? tx`Debug '${p.name}'` : tx`Run '${p.name}'`,
|
||||
verbatim: true,
|
||||
icon: debug ? "debug" : "run",
|
||||
shortcut: p.detail,
|
||||
@@ -316,10 +317,10 @@ export function contextMenuItems(menu: ContextMenuId, ctx: MenuGroupContext): Me
|
||||
if (!p) return [];
|
||||
const arg: ContextArg = { path, line, dir, content: dir ? undefined : input.content };
|
||||
return [
|
||||
{ kind: "action", action: "run.runContext", arg, label: `Run '${p.name}'`, icon: "run" },
|
||||
{ kind: "action", action: "run.runContext", arg, label: tx`Run '${p.name}'`, icon: "run" },
|
||||
// Like IDEA, Debug only where a debugger can run it.
|
||||
...(p.debug || p.config?.debugAdapter || p.missing
|
||||
? ([{ kind: "action", action: "run.debugContext", arg, label: `Debug '${p.name}'`, icon: "debug" }] as MenuItem[])
|
||||
? ([{ kind: "action", action: "run.debugContext", arg, label: tx`Debug '${p.name}'`, icon: "debug" }] as MenuItem[])
|
||||
: []),
|
||||
{
|
||||
label: "More Run/Debug",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { loadSettings } from "@/platform/project";
|
||||
import { Dialog, Input } from "@/ui";
|
||||
import { confirmChecked } from "@/workbench/messages";
|
||||
import styles from "./Run.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
interface Pending {
|
||||
title: string;
|
||||
@@ -20,8 +21,11 @@ const useRunDialog = create<{ pending: Pending | null }>(() => ({ pending: null
|
||||
*/
|
||||
export async function confirmRerun(name: string, instances = 1): Promise<boolean> {
|
||||
const answer = await confirmChecked({
|
||||
title: `Process '${name}' Is Running`,
|
||||
message: `'${name}' is not allowed to run in parallel.\nWould you like to stop ${instances > 1 ? `${instances} running instances` : "the running one"}?`,
|
||||
title: tx`Process '${name}' Is Running`,
|
||||
message:
|
||||
instances > 1
|
||||
? tx`'${name}' is not allowed to run in parallel.\nWould you like to stop ${instances} running instances?`
|
||||
: tx`'${name}' is not allowed to run in parallel.\nWould you like to stop the running one?`,
|
||||
ok: "Stop and Rerun",
|
||||
checkbox: "Do not ask again",
|
||||
});
|
||||
@@ -39,8 +43,8 @@ export async function confirmRerun(name: string, instances = 1): Promise<boolean
|
||||
/** IDEA's question before closing a Run tab whose process still runs. Resolves true for Terminate. */
|
||||
export async function confirmTerminate(name: string): Promise<boolean> {
|
||||
const answer = await confirmChecked({
|
||||
title: `Process '${name}' Is Running`,
|
||||
message: `Do you want to terminate the process '${name}'?`,
|
||||
title: tx`Process '${name}' Is Running`,
|
||||
message: tx`Do you want to terminate the process '${name}'?`,
|
||||
ok: "Terminate",
|
||||
checkbox: "Do not ask again",
|
||||
});
|
||||
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
stopSession,
|
||||
useRun,
|
||||
} from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function hasProject() {
|
||||
return !!useProject.getState().project;
|
||||
@@ -117,7 +118,7 @@ async function debugConfig(config: RunConfig, chain: readonly string[] = []) {
|
||||
group: "run",
|
||||
level: "warning",
|
||||
title: "Debugger is not available",
|
||||
body: `Running '${config.name}' without the debugger.`,
|
||||
body: tx`Running '${config.name}' without the debugger.`,
|
||||
key: "run-no-debugger",
|
||||
});
|
||||
await startRun(config, undefined, undefined, { prepared: true });
|
||||
|
||||
@@ -3,6 +3,7 @@ import { call, commands, isTauri, nextRequestId } from "@/platform/ipc";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { editorService, notifications } from "@/workbench/contributions";
|
||||
import { joinPath, type FileTarget } from "./links";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
const slash = path.lastIndexOf("/");
|
||||
@@ -99,7 +100,7 @@ export async function openTarget(target: FileTarget, cwd: string, focus = true):
|
||||
group: "run",
|
||||
level: "info",
|
||||
title: "No sources for this class",
|
||||
body: `${target.className.split("$")[0]} is not part of the project, and no library sources are available for it.`,
|
||||
body: tx`${target.className.split("$")[0]!} is not part of the project, and no library sources are available for it.`,
|
||||
key: "run-link",
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { applicationConfig, jvmModuleDir } from "./application";
|
||||
import { formatArgs } from "./configs";
|
||||
import { contextConfig, fileRole, projectPython, testBasesFor, testConfigFor } from "./context";
|
||||
import { detectRunTargets, targetConfig, targetLanguage, type RunTarget } from "./targets";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export interface LocatedTool {
|
||||
path: string | null;
|
||||
@@ -300,7 +301,7 @@ const LANGUAGE_NAMES: Partial<Record<ScriptLanguage, string>> = {
|
||||
function missing(lang: ScriptLanguage, file: string, tools: string[]): Missing {
|
||||
const what = LANGUAGE_NAMES[lang] ?? tools[0] ?? lang;
|
||||
return {
|
||||
title: `Cannot run '${file}'`,
|
||||
title: tx`Cannot run '${file}'`,
|
||||
body: `${what} was not found (looked for ${tools.map((t) => `\`${t}\``).join(", ")}). ${INSTALL[lang] ?? ""}`.trim(),
|
||||
};
|
||||
}
|
||||
@@ -421,7 +422,7 @@ function jsRuntime(lang: "js" | "ts", env: ToolEnv, s: FileSite): Produced {
|
||||
name,
|
||||
scope: "file",
|
||||
missing: {
|
||||
title: `Cannot run '${name}'`,
|
||||
title: tx`Cannot run '${name}'`,
|
||||
body:
|
||||
`Running TypeScript needs Node.js 22.6+ (type stripping), tsx or ts-node. ${found}` +
|
||||
"Upgrade Node.js (https://nodejs.org), or install tsx: `npm install -D tsx` (or `npm install -g tsx`).",
|
||||
|
||||
@@ -22,6 +22,7 @@ import { onTestStatesChanged, testState, testStatesVersion, type TestState } fro
|
||||
/** Languages with gutter run targets (C / C++ `main` included). */
|
||||
const hasGutterTargets = (path: string) => !!targetLanguage(path) || /\.(c|cpp|cc|cxx)$/.test(path);
|
||||
import styles from "./Run.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
interface PlacedTarget extends RunTarget {
|
||||
/** Line start offset in the current state. */
|
||||
@@ -104,9 +105,9 @@ export function targetMenuItems(target: RunTarget, path: string, content: string
|
||||
// Like IDEA, Debug only where a debugger can run it.
|
||||
const debuggable = !p || !!(p.debug || p.config?.debugAdapter || p.missing);
|
||||
return [
|
||||
{ label: `Run '${name}'`, icon: "run", shortcut: shortcutLabel("run.runContext"), ...pick(false) } as MenuItem,
|
||||
{ label: tx`Run '${name}'`, icon: "run", shortcut: shortcutLabel("run.runContext"), ...pick(false) } as MenuItem,
|
||||
...(debuggable
|
||||
? [{ label: `Debug '${name}'`, icon: "debug", shortcut: shortcutLabel("run.debugContext"), ...pick(true) } as MenuItem]
|
||||
? [{ label: tx`Debug '${name}'`, icon: "debug", shortcut: shortcutLabel("run.debugContext"), ...pick(true) } as MenuItem]
|
||||
: []),
|
||||
...runExecutors()
|
||||
.filter((e) => !!p?.config && e.supports(p.config))
|
||||
|
||||
@@ -9,7 +9,7 @@ import { create } from "zustand";
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import type { RunConfig } from "@/platform/ipc";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { useT, useTxTemplate } from "@/platform/i18n";
|
||||
import { tx, useT, useTxTemplate } from "@/platform/i18n";
|
||||
import { Button, EmptyState, T } from "@/ui";
|
||||
import type { MenuItem } from "@/ui";
|
||||
import {
|
||||
@@ -120,7 +120,7 @@ export function runServiceItems(
|
||||
key: TYPE_PREFIX + kind,
|
||||
label: kindLabel(kind),
|
||||
icon: configIcon({ kind, isTest: false }),
|
||||
detail: running ? `${running} running` : undefined,
|
||||
detail: running ? tx`${running} running` : undefined,
|
||||
children,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ConsoleView } from "./ConsoleView";
|
||||
import { findOutputLinks } from "./links";
|
||||
import { useRun } from "./store";
|
||||
import styles from "./StackTrace.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export interface StackTraceTab {
|
||||
id: string;
|
||||
@@ -142,7 +143,7 @@ export function StackTracePanel({ tab }: { tab: StackTraceTab }) {
|
||||
follow={false}
|
||||
onFollowChange={() => {}}
|
||||
cwd={root}
|
||||
aria-label={`Stack trace ${tab.title}`}
|
||||
aria-label={tx`Stack trace ${tab.title}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { failedTestPaths, leafTestPaths, rerunFailedConfig, testsConfig } from "
|
||||
import { compoundChildren } from "./configs";
|
||||
import { TerminalOutput } from "./terminalOutput";
|
||||
import { typeFor } from "./types";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** The permanent "Current File" pseudo-configuration of the chooser (runs the active file's context). */
|
||||
export const CURRENT_FILE = "current-file";
|
||||
@@ -549,7 +550,7 @@ export async function startRun(
|
||||
}
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
enqueue(sessionId, { kind: "stderr", text: `Error running '${config.name}': ${message}\n` });
|
||||
enqueue(sessionId, { kind: "stderr", text: tx`Error running '${config.name}': ${message}\n` });
|
||||
flush(sessionId);
|
||||
patchSession(sessionId, { status: "failed", endedAt: Date.now() });
|
||||
notifications().notify({ group: "run", level: "error", title: `Error running '${config.name}'`, body: message });
|
||||
@@ -795,7 +796,7 @@ export async function sendStdin(sessionId: string, text: string): Promise<void>
|
||||
try {
|
||||
await call(commands.runStdin(session.runId, text + "\n"));
|
||||
} catch (e) {
|
||||
enqueue(sessionId, { kind: "system", text: `Cannot write to stdin: ${e instanceof Error ? e.message : String(e)}\n` });
|
||||
enqueue(sessionId, { kind: "system", text: tx`Cannot write to stdin: ${e instanceof Error ? e.message : String(e)}\n` });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type RunConfigTypeContribution,
|
||||
} from "@/workbench/contributions";
|
||||
import { formatArgs, parseArgs, uid, uniqueName } from "./configs";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export const COMPOUND = "compound";
|
||||
|
||||
@@ -203,21 +204,21 @@ function buildToolType(
|
||||
id: string,
|
||||
label: string,
|
||||
icon: string,
|
||||
tasksLabel: string,
|
||||
tasks: { label: string; description: string },
|
||||
wrapper: { unix: string; windows: string; plain: string; label: string; byDefault: boolean },
|
||||
): RunConfigTypeContribution {
|
||||
return {
|
||||
id,
|
||||
label,
|
||||
icon,
|
||||
description: `Run ${label} ${tasksLabel.toLowerCase()}`,
|
||||
description: tasks.description,
|
||||
defaults: wrapper.byDefault ? { wrapper: "true" } : {},
|
||||
ownsWorkingDirectory: true,
|
||||
fields: [
|
||||
{ key: "projectDir", label: `${label} project`, mono: true, placeholder: "Project root" },
|
||||
{ key: "projectDir", label: tx`${label} project`, mono: true, placeholder: "Project root" },
|
||||
{
|
||||
key: "tasks",
|
||||
label: tasksLabel,
|
||||
label: tasks.label,
|
||||
mono: true,
|
||||
required: true,
|
||||
suggestions: (configs) => argsOf(configs, id, 0),
|
||||
@@ -244,7 +245,7 @@ function buildToolType(
|
||||
};
|
||||
}
|
||||
|
||||
const gradle = buildToolType("gradle", "Gradle", "file-gradle", "Tasks", {
|
||||
const gradle = buildToolType("gradle", "Gradle", "file-gradle", { label: "Tasks", description: "Run Gradle tasks" }, {
|
||||
unix: "gradlew",
|
||||
windows: "gradlew.bat",
|
||||
plain: "gradle",
|
||||
@@ -252,7 +253,7 @@ const gradle = buildToolType("gradle", "Gradle", "file-gradle", "Tasks", {
|
||||
byDefault: true,
|
||||
});
|
||||
|
||||
const maven = buildToolType("maven", "Maven", "file-maven", "Goals", {
|
||||
const maven = buildToolType("maven", "Maven", "file-maven", { label: "Goals", description: "Run Maven goals" }, {
|
||||
unix: "mvnw",
|
||||
windows: "mvnw.cmd",
|
||||
plain: "mvn",
|
||||
|
||||
@@ -3,7 +3,7 @@ import clsx from "clsx";
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import { shortcutLabel } from "@/platform/keymap";
|
||||
import { confirm } from "@/workbench/messages";
|
||||
import { useT, useTx } from "@/platform/i18n";
|
||||
import { tx, useT, useTx } from "@/platform/i18n";
|
||||
import { EmptyState, Icon, IconButton, Tree, fileIconName, showContextMenu, type MenuItem } from "@/ui";
|
||||
import { basename, openLocation, relPath } from "../common/open";
|
||||
import {
|
||||
@@ -118,7 +118,10 @@ async function removeList(list: BookmarkList, count: number) {
|
||||
count &&
|
||||
!(await confirm({
|
||||
title: "Delete Bookmark List",
|
||||
message: `Delete the list '${list.name}' with its ${count} bookmark${count === 1 ? "" : "s"}?`,
|
||||
message:
|
||||
count === 1
|
||||
? tx`Delete the list '${list.name}' with its ${count} bookmark?`
|
||||
: tx`Delete the list '${list.name}' with its ${count} bookmarks?`,
|
||||
ok: "Delete",
|
||||
}))
|
||||
)
|
||||
@@ -263,7 +266,7 @@ export function BookmarksView() {
|
||||
...(bookmarks.length ? [{ label: "Move to Another List", submenu: moveMenu(bookmarks, lists) }] : []),
|
||||
{ kind: "separator" as const },
|
||||
{
|
||||
label: targets.length > 1 ? `Delete ${targets.length} Bookmarks` : "Delete Bookmark",
|
||||
label: targets.length > 1 ? tx`Delete ${targets.length} Bookmarks` : "Delete Bookmark",
|
||||
danger: true,
|
||||
run: () => removeNodes(targets),
|
||||
},
|
||||
|
||||
@@ -43,6 +43,7 @@ import { RecentPopupsOverlay } from "./recent/RecentPopups";
|
||||
import { cycleSwitcher, hideRecentPopup, showRecentPopup } from "./recent/store";
|
||||
import { FileStructureOverlay, useFileStructurePopup } from "./structure/StructureView";
|
||||
import { useOutline, useStructureOptions } from "./structure/store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** Single-line editor selection, used to prefill search fields. */
|
||||
function editorSelectionText(): string | null {
|
||||
@@ -452,7 +453,7 @@ export function activate(): void | (() => void) {
|
||||
...[..."0123456789"].flatMap((d) => [
|
||||
{
|
||||
id: `navigate.toggleBookmark${d}`,
|
||||
title: `Toggle Bookmark ${d}`,
|
||||
title: tx`Toggle Bookmark ${d}`,
|
||||
category: "Bookmarks",
|
||||
when: "projectOpen && textEditorActive",
|
||||
run: () => {
|
||||
@@ -462,7 +463,7 @@ export function activate(): void | (() => void) {
|
||||
},
|
||||
{
|
||||
id: `navigate.gotoBookmark${d}`,
|
||||
title: `Go to Bookmark ${d}`,
|
||||
title: tx`Go to Bookmark ${d}`,
|
||||
category: "Bookmarks",
|
||||
when: "projectOpen",
|
||||
run: () => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
DockerObject,
|
||||
DockerRegistry,
|
||||
} from "@/platform/ipc";
|
||||
import { useTx, useTxTemplate } from "@/platform/i18n";
|
||||
import { tx, useTx, useTxTemplate } from "@/platform/i18n";
|
||||
import { call, commands, isCancelled, nextRequestId } from "@/platform/ipc";
|
||||
import { Button, EmptyState, IconButton, Spinner, Tabs, Toggle, T } from "@/ui";
|
||||
import type { MenuItem } from "@/ui";
|
||||
@@ -168,7 +168,7 @@ async function removeConnectionPrompt(c: DockerConnection) {
|
||||
if (
|
||||
await confirm({
|
||||
title: "Remove Docker Connection",
|
||||
message: `Remove the connection '${c.name}' from Services?`,
|
||||
message: tx`Remove the connection '${c.name}' from Services?`,
|
||||
ok: "Remove",
|
||||
})
|
||||
)
|
||||
@@ -179,7 +179,7 @@ async function removeRegistryPrompt(r: DockerRegistry) {
|
||||
if (
|
||||
await confirm({
|
||||
title: "Remove Docker Registry",
|
||||
message: `Remove the registry '${r.name}' and its saved password?`,
|
||||
message: tx`Remove the registry '${r.name}' and its saved password?`,
|
||||
ok: "Remove",
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** What the Services toolbar, context menu and `docker.*` actions do with a node of a connection. */
|
||||
import { txTemplate } from "@/platform/i18n";
|
||||
import { tx, txTemplate } from "@/platform/i18n";
|
||||
import type { PruneTarget } from "@/platform/ipc";
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import type { MenuItem } from "@/ui";
|
||||
@@ -47,6 +47,24 @@ function composeAction(conn: string, n: Extract<ServiceNode, { kind: "compose" }
|
||||
return composeOp(conn, n.project, first?.composeFiles ?? [], first?.composeWorkingDir ?? null, op).then(() => undefined);
|
||||
}
|
||||
|
||||
function deleteQuestion(kind: "container" | "image" | "volume" | "network", label: string, running: boolean) {
|
||||
switch (kind) {
|
||||
case "container":
|
||||
return {
|
||||
title: "Delete Container",
|
||||
message: running
|
||||
? tx`Delete container '${label}'? It is running and will be stopped.`
|
||||
: tx`Delete container '${label}'?`,
|
||||
};
|
||||
case "image":
|
||||
return { title: "Delete Image", message: tx`Delete image '${label}'?` };
|
||||
case "volume":
|
||||
return { title: "Delete Volume", message: tx`Delete volume '${label}'?` };
|
||||
case "network":
|
||||
return { title: "Delete Network", message: tx`Delete network '${label}'?` };
|
||||
}
|
||||
}
|
||||
|
||||
export async function remove(t: DockerTarget | null) {
|
||||
if (!t || !canRemove(t)) return;
|
||||
const n = t.node;
|
||||
@@ -54,7 +72,7 @@ export async function remove(t: DockerTarget | null) {
|
||||
if (n.kind === "compose") {
|
||||
const ok = await confirm({
|
||||
title: "Compose Down",
|
||||
message: `Stop and remove the containers and networks of '${label}'?`,
|
||||
message: tx`Stop and remove the containers and networks of '${label}'?`,
|
||||
ok: "Down",
|
||||
});
|
||||
if (ok) await composeAction(t.conn, n, "down");
|
||||
@@ -64,8 +82,7 @@ export async function remove(t: DockerTarget | null) {
|
||||
const what = n.kind;
|
||||
const running = n.kind === "container" && isRunning(n.container);
|
||||
const answer = await confirmChecked({
|
||||
title: `Delete ${what[0]!.toUpperCase()}${what.slice(1)}`,
|
||||
message: `Delete ${what} '${label}'?${running ? " It is running and will be stopped." : ""}`,
|
||||
...deleteQuestion(what, label, running),
|
||||
ok: "Delete",
|
||||
checkbox: n.kind === "image" ? "Force (also untag / remove when used by stopped containers)" : undefined,
|
||||
});
|
||||
@@ -107,6 +124,23 @@ export async function prunePrompt(conn: string, target: PruneTarget) {
|
||||
if (ok) await prune(conn, target);
|
||||
}
|
||||
|
||||
function pruneLabel(target: PruneTarget): string {
|
||||
switch (target) {
|
||||
case "containers":
|
||||
return tx("Prune Unused Containers…");
|
||||
case "images":
|
||||
return tx("Prune Dangling Images…");
|
||||
case "allImages":
|
||||
return tx("Prune Unused Images…");
|
||||
case "volumes":
|
||||
return tx("Prune Unused Volumes…");
|
||||
case "networks":
|
||||
return tx("Prune Unused Networks…");
|
||||
case "system":
|
||||
return tx("Prune Everything Unused…");
|
||||
}
|
||||
}
|
||||
|
||||
export function pruneMenu(conn: string): MenuItem[] {
|
||||
return PRUNE.map(([target, label]) => ({ label, run: () => void prunePrompt(conn, target) }));
|
||||
}
|
||||
@@ -155,7 +189,7 @@ export function nodeMenu(t: DockerTarget): MenuItem[] {
|
||||
if (n.kind === "group") {
|
||||
const target: PruneTarget = n.group === "containers" ? "containers" : n.group === "images" ? "images" : n.group === "volumes" ? "volumes" : "networks";
|
||||
items.push({
|
||||
label: txTemplate(`Prune ${n.group === "images" ? "Dangling Images" : `Unused ${n.group[0]!.toUpperCase()}${n.group.slice(1)}`}…`),
|
||||
label: pruneLabel(target),
|
||||
run: () => void prunePrompt(conn, target),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { call, commands, type RunConfig } from "@/platform/ipc";
|
||||
import { registerRunConfigType, type RunConfigTypeContribution } from "@/workbench/contributions";
|
||||
import { imageTag, targetTitle, type DockerTarget } from "./compose";
|
||||
import { connections, endpointOf, loadSettings, useServices } from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export const DOCKERFILE = "docker-dockerfile";
|
||||
export const DOCKER_IMAGE = "docker-image";
|
||||
@@ -279,7 +280,7 @@ export function gutterActions(
|
||||
config: typedConfig(dockerfileType, `docker:dockerfile:${file}`, file, { ...params, runImage: "true" }),
|
||||
},
|
||||
{
|
||||
title: `Build Image '${params.tag}'`,
|
||||
title: tx`Build Image '${params.tag}'`,
|
||||
config: typedConfig(dockerfileType, `docker:build:${file}`, `Build ${file}`, params),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -146,7 +146,7 @@ export function NumberInput({ id, value, onChange, min, max, step = 1, integer,
|
||||
aria-label={aria["aria-label"]}
|
||||
aria-valuemin={min}
|
||||
aria-valuemax={max}
|
||||
title={invalid ? `Enter a value between ${min} and ${max}` : undefined}
|
||||
title={invalid ? tx`Enter a value between ${min} and ${max}` : undefined}
|
||||
onChange={(e) => {
|
||||
setDraft(e.target.value);
|
||||
const n = parseNumberInput(e.target.value, { min, max, integer });
|
||||
|
||||
@@ -52,6 +52,7 @@ import { KeymapNameDialog } from "./KeymapDialogs";
|
||||
import { RecordShortcutDialog } from "./RecordShortcutDialog";
|
||||
import { StrokeRecorder } from "./StrokeRecorder";
|
||||
import styles from "./Keymap.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
type KNode =
|
||||
| { kind: "group"; key: string; name: string; path: string[]; children: KNode[] }
|
||||
@@ -206,14 +207,14 @@ export function KeymapPage() {
|
||||
...Object.keys(PRESETS).map((id) => ({ value: id, label: PRESET_TITLES[id] ?? id })),
|
||||
...Object.entries(customs)
|
||||
.sort(([, a], [, b]) => a.name.localeCompare(b.name))
|
||||
.map(([id, k]) => ({ value: id, label: k.name, detail: `based on ${PRESET_TITLES[k.parent] ?? k.parent}` })),
|
||||
.map(([id, k]) => ({ value: id, label: k.name, detail: tx`based on ${PRESET_TITLES[k.parent] ?? k.parent}` })),
|
||||
];
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (
|
||||
!(await confirm({
|
||||
title: "Delete Keymap",
|
||||
message: `Delete keymap '${keymapName(selectedId, customs)}'?`,
|
||||
message: tx`Delete keymap '${keymapName(selectedId, customs)}'?`,
|
||||
ok: "Delete",
|
||||
}))
|
||||
)
|
||||
@@ -252,7 +253,7 @@ export function KeymapPage() {
|
||||
{ kind: "separator" },
|
||||
...(bindings.length
|
||||
? bindings.map((b) => ({
|
||||
label: `Remove ${formatBinding(b)}`,
|
||||
label: tx`Remove ${formatBinding(b)}`,
|
||||
icon: "minus",
|
||||
run: () => change(removeBinding(draft, action.id, b)),
|
||||
}))
|
||||
|
||||
@@ -12,6 +12,7 @@ import { findConflicts, resolveBindings, type Conflict, type KeymapDraft } from
|
||||
import { groupPath } from "./groups";
|
||||
import { StrokeRecorder } from "./StrokeRecorder";
|
||||
import styles from "./Keymap.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export interface RecordShortcutDialogProps {
|
||||
action: Pick<Action, "id" | "title" | "category" | "when">;
|
||||
@@ -61,7 +62,7 @@ export function RecordShortcutDialog({ action, actions, draft, onCancel, onSubmi
|
||||
<Dialog
|
||||
open
|
||||
onClose={onCancel}
|
||||
title={`Keyboard Shortcut: ${action.title}`}
|
||||
title={tx`Keyboard Shortcut: ${action.title}`}
|
||||
width={440}
|
||||
buttons={[
|
||||
{ label: "OK", role: "default", disabled: !binding || already, onClick: () => void ok() },
|
||||
|
||||
@@ -31,6 +31,7 @@ import { initProjectRoots, useDetectedRoots } from "@/platform/projectRoots";
|
||||
import { choosePath } from "./api";
|
||||
import { useProjectSetting } from "./components";
|
||||
import styles from "./Sdk.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
interface DirNode {
|
||||
path: string;
|
||||
@@ -136,7 +137,7 @@ function ContentRoots({ root }: { root: string }) {
|
||||
icon="close"
|
||||
size="small"
|
||||
iconSize={12}
|
||||
title={`Remove content root ${r}`}
|
||||
title={tx`Remove content root ${r}`}
|
||||
onClick={() => setRoots(list.filter((x) => x !== r))}
|
||||
/>
|
||||
</div>
|
||||
@@ -305,7 +306,7 @@ export function ProjectStructurePage() {
|
||||
icon="close"
|
||||
size="small"
|
||||
iconSize={12}
|
||||
title={`Unmark ${shown}`}
|
||||
title={tx`Unmark ${shown}`}
|
||||
onClick={() => setPs(markFolder(ps, x.path, null, detected))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { GlobListEditor, Row } from "../components/Form";
|
||||
import { useSetting } from "../components/context";
|
||||
import styles from "./FileTypes.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
function useLanguages(): LanguageInfo[] {
|
||||
const [langs, setLangs] = useState<LanguageInfo[]>([]);
|
||||
@@ -108,7 +109,7 @@ export function RecognizedFileTypes() {
|
||||
value={patterns}
|
||||
onChange={(next) => setAssoc(applyPatternEdit(current, langs, selected, next))}
|
||||
placeholder="*.ext"
|
||||
aria-label={`File name patterns of ${selected.name}`}
|
||||
aria-label={tx`File name patterns of ${selected.name}`}
|
||||
/>
|
||||
</Row>
|
||||
<Row
|
||||
@@ -120,7 +121,7 @@ export function RecognizedFileTypes() {
|
||||
value={hashbangs}
|
||||
onChange={(next) => setAssoc(setHashbangs(current, selected.id, next))}
|
||||
placeholder="interpreter"
|
||||
aria-label={`Hashbang patterns of ${selected.name}`}
|
||||
aria-label={tx`Hashbang patterns of ${selected.name}`}
|
||||
/>
|
||||
</Row>
|
||||
{selected.firstLine && (
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import { ACCENT_PRESETS, LIMITS, type ThemeId } from "../model/schema";
|
||||
import type { PageSpec } from "./spec";
|
||||
import styles from "./Behavior.module.css";
|
||||
import { useT } from "@/platform/i18n";
|
||||
import { tx, useT } from "@/platform/i18n";
|
||||
|
||||
import { BUILTIN_THEMES } from "@/platform/themes";
|
||||
|
||||
@@ -465,14 +465,14 @@ function NotificationGroupsTable() {
|
||||
value={v.display}
|
||||
options={DISPLAY_OPTIONS}
|
||||
onChange={(display) => update(g.id, { display })}
|
||||
aria-label={`${g.title} popup type`}
|
||||
aria-label={tx`${g.title} popup type`}
|
||||
/>
|
||||
</td>
|
||||
<td className={styles.center}>
|
||||
<Checkbox
|
||||
checked={v.log}
|
||||
onChange={(log) => update(g.id, { log })}
|
||||
aria-label={`Show ${g.title} in tool window`}
|
||||
aria-label={tx`Show ${g.title} in tool window`}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Row, Select } from "../../components/Form";
|
||||
import { useSetting } from "../../components/context";
|
||||
import { editorPage } from "./kit";
|
||||
import styles from "./Editor.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
type Overrides = Record<string, Record<string, TextAttributesOverride>>;
|
||||
|
||||
@@ -238,7 +239,7 @@ function ColorInput({
|
||||
<Input
|
||||
mono
|
||||
className={styles.swatchInput}
|
||||
aria-label={`${label} hex`}
|
||||
aria-label={tx`${label} hex`}
|
||||
value={draft ?? value}
|
||||
invalid={draft !== null && !HEX_COLOR.test(draft)}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { FONT_WEIGHTS, fontWeightOf, fontWeightVars } from "@/platform/editorSet
|
||||
import { LIMITS } from "../../model/schema";
|
||||
import { editorPage } from "./kit";
|
||||
import styles from "./Editor.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const CANDIDATES = [
|
||||
"JetBrains Mono",
|
||||
@@ -156,7 +157,7 @@ function WeightRow({ label, value, onChange, defaultWeight, hint }: { label: str
|
||||
aria-label={label}
|
||||
/>
|
||||
{custom && (
|
||||
<NumberInput value={weight} onChange={(n) => onChange(fontWeightOf(n, defaultWeight))} min={100} max={900} step={50} integer aria-label={`${label} custom value`} />
|
||||
<NumberInput value={weight} onChange={(n) => onChange(fontWeightOf(n, defaultWeight))} min={100} max={900} step={50} integer aria-label={tx`${label} custom value`} />
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
type LanguageServers,
|
||||
} from "./inspectionsModel";
|
||||
import styles from "./Inspections.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
let languagesCache: LanguageServers[] | null = null;
|
||||
|
||||
@@ -175,7 +176,7 @@ function InspectionsPage() {
|
||||
const isCopy = location.key !== null;
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (!(await confirm({ title: "Delete Profile", message: `Delete inspection profile '${label}'?`, ok: "Delete" })))
|
||||
if (!(await confirm({ title: "Delete Profile", message: tx`Delete inspection profile '${label}'?`, ok: "Delete" })))
|
||||
return;
|
||||
apply(deleteProfile(settings, profileId));
|
||||
};
|
||||
@@ -418,7 +419,7 @@ function InspectionRow({
|
||||
checked={state === "on"}
|
||||
indeterminate={state === "mixed"}
|
||||
onChange={() => onToggle(state !== "on")}
|
||||
aria-label={`Enable ${node.name}`}
|
||||
aria-label={tx`Enable ${node.name}`}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type ProfileOverrides,
|
||||
type SeenCode,
|
||||
} from "@/platform/inspections";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export interface InspectionNode {
|
||||
/** Unique in the tree (a server serving several languages appears under each). */
|
||||
@@ -45,14 +46,16 @@ function serverNode(group: string, server: string, codes: readonly SeenCode[], l
|
||||
key: `${group}/${id}`,
|
||||
id,
|
||||
name: server,
|
||||
description: `Problems the ${server} language server reports${languages ? ` in ${languages} files` : ""}. Severity "As reported" keeps the level the server assigns; a code below can override it.`,
|
||||
description: languages
|
||||
? tx`Problems the ${server} language server reports in ${languages} files. Severity "As reported" keeps the level the server assigns; a code below can override it.`
|
||||
: tx`Problems the ${server} language server reports. Severity "As reported" keeps the level the server assigns; a code below can override it.`,
|
||||
children: [...codes]
|
||||
.sort((a, b) => a.code.localeCompare(b.code, undefined, { numeric: true }))
|
||||
.map((c) => ({
|
||||
key: `${group}/${codeInspectionId(id, c.code)}`,
|
||||
id: codeInspectionId(id, c.code),
|
||||
name: c.code,
|
||||
description: `${server} problems with code ${c.code}${c.source && c.source !== server ? ` (${c.source})` : ""}.${c.sample ? `\n\nExample: ${c.sample}` : ""}`,
|
||||
description: codeDescription(server, c),
|
||||
children: [],
|
||||
})),
|
||||
};
|
||||
@@ -270,3 +273,11 @@ export function deleteProfile(s: InspectionSettingsValid, id: ProfileId): Inspec
|
||||
const profile = s.profile === id ? (level === "project" ? "project" : "default") : s.profile;
|
||||
return { ...s, [field]: rest, profile };
|
||||
}
|
||||
|
||||
function codeDescription(server: string, { code, source, sample }: SeenCode): string {
|
||||
const what =
|
||||
source && source !== server
|
||||
? tx`${server} problems with code ${code} (${source}).`
|
||||
: tx`${server} problems with code ${code}.`;
|
||||
return sample ? tx`${what}\n\nExample: ${sample}` : what;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { selectPage, setQuery } from "../model/staging";
|
||||
import type { PageSpec } from "./spec";
|
||||
import { BoundToggle, Grid, Indented, PageLink, TableEditor } from "./workflowControls";
|
||||
import styles from "./Workflow.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export const TOOLS_GROUP = "Tools";
|
||||
|
||||
@@ -468,7 +469,7 @@ function ExternalToolsEditor() {
|
||||
onMouseDown={() => setSelected(t.id)}
|
||||
>
|
||||
<Checkbox
|
||||
aria-label={`Enable ${t.name}`}
|
||||
aria-label={tx`Enable ${t.name}`}
|
||||
checked={t.enabled}
|
||||
onChange={(on) => setTools(tools.map((x) => (x.id === t.id ? { ...x, enabled: on } : x)))}
|
||||
/>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useSetting } from "../components/context";
|
||||
import type { PageSpec } from "./spec";
|
||||
import { BoundToggle, Grid, Indented, RadioRow, TableEditor, ToggleNumber } from "./workflowControls";
|
||||
import styles from "./Workflow.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
const L = WORKFLOW_LIMITS;
|
||||
|
||||
@@ -110,7 +111,7 @@ function GitExecutableRow() {
|
||||
mono
|
||||
className={styles.wide}
|
||||
value={path}
|
||||
placeholder={detected ? `Auto-detected: ${detected}` : "Auto-detected"}
|
||||
placeholder={detected ? tx`Auto-detected: ${detected}` : "Auto-detected"}
|
||||
onChange={(e) => {
|
||||
setPath(e.target.value);
|
||||
setResult(null);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createFromTemplate, packageFor } from "./create";
|
||||
import { planFile, withKind, type FileTemplate } from "./model";
|
||||
import { useFileTemplates } from "./store";
|
||||
import styles from "./NewFileDialog.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
interface Request {
|
||||
directory: string;
|
||||
@@ -185,17 +186,17 @@ function NameStep({
|
||||
<Dialog
|
||||
open
|
||||
onClose={close}
|
||||
title={`New ${template.name}`}
|
||||
title={tx`New ${template.name}`}
|
||||
width={380}
|
||||
position="top"
|
||||
aria-label={`New ${template.name}`}
|
||||
aria-label={tx`New ${template.name}`}
|
||||
>
|
||||
<div className={styles.form}>
|
||||
<Input
|
||||
data-autofocus
|
||||
value={name}
|
||||
invalid={!!error}
|
||||
placeholder={effective.extension ? `Name (.${effective.extension})` : "File name with extension"}
|
||||
placeholder={effective.extension ? tx`Name (.${effective.extension})` : "File name with extension"}
|
||||
aria-label="Name"
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
|
||||
@@ -22,6 +22,7 @@ import type { FileTemplate } from "./files/model";
|
||||
import { syncTemplatesFromStorage } from "./sync";
|
||||
import { tagAtCaret, updateTag } from "./emmet/editor";
|
||||
import { promptAbbreviation, UpdateTagDialogHost } from "./emmet/UpdateTagDialog";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export { EXTRA_COMPLETION_KEY } from "./editor";
|
||||
|
||||
@@ -42,7 +43,7 @@ async function updateTagWithEmmet() {
|
||||
notifications().notify({
|
||||
level: "warning",
|
||||
title: "Cannot update the tag",
|
||||
body: `'${abbreviation}' does not describe a single element.`,
|
||||
body: tx`'${abbreviation}' does not describe a single element.`,
|
||||
key: "emmet-update-tag",
|
||||
});
|
||||
}
|
||||
@@ -78,7 +79,7 @@ function directoryOf(arg: unknown): string | null {
|
||||
function newFileAction(t: FileTemplate): Action {
|
||||
return {
|
||||
id: `templates.new.${t.id}`,
|
||||
title: t.id === "file" ? "New Empty File…" : `New ${t.name}…`,
|
||||
title: t.id === "file" ? "New Empty File…" : tx`New ${t.name}…`,
|
||||
category: "File",
|
||||
icon: t.icon,
|
||||
when: "projectOpen",
|
||||
|
||||
@@ -136,7 +136,7 @@ function FilesTab() {
|
||||
<Checkbox
|
||||
checked={t.enabled !== false}
|
||||
onChange={(v) => saveFileTemplate({ ...t, enabled: v })}
|
||||
aria-label={`Enable ${t.name}`}
|
||||
aria-label={tx`Enable ${t.name}`}
|
||||
/>
|
||||
<Icon name={t.icon} size={14} />
|
||||
<span className={clsx(isModifiedFileTemplate(t.id) && styles.modified)}>{t.name}</span>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "../live/store";
|
||||
import { BUILTIN_IDS } from "../live/builtins";
|
||||
import styles from "./Settings.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function groupTemplates(templates: readonly LiveTemplate[], filter: string): Array<[string, LiveTemplate[]]> {
|
||||
const q = filter.trim().toLowerCase();
|
||||
@@ -106,7 +107,7 @@ export function LiveTemplatesPage() {
|
||||
<Checkbox
|
||||
checked={t.enabled !== false}
|
||||
onChange={(v) => saveTemplate({ ...t, enabled: v })}
|
||||
aria-label={`Enable ${t.abbrev}`}
|
||||
aria-label={tx`Enable ${t.abbrev}`}
|
||||
/>
|
||||
<span className={clsx(styles.itemLabel, isModifiedBuiltin(t.id) && styles.modified)}>
|
||||
{t.abbrev}
|
||||
@@ -243,7 +244,7 @@ function TemplateEditor({ template: t }: { template: LiveTemplate }) {
|
||||
value={v.expression ?? ""}
|
||||
placeholder="className()"
|
||||
onChange={(e) => setVar(v.name, { expression: e.target.value || undefined })}
|
||||
aria-label={`${v.name} expression`}
|
||||
aria-label={tx`${v.name} expression`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
@@ -252,14 +253,14 @@ function TemplateEditor({ template: t }: { template: LiveTemplate }) {
|
||||
value={v.defaultValue ?? ""}
|
||||
placeholder='"value"'
|
||||
onChange={(e) => setVar(v.name, { defaultValue: e.target.value || undefined })}
|
||||
aria-label={`${v.name} default value`}
|
||||
aria-label={tx`${v.name} default value`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Checkbox
|
||||
checked={!!v.skipIfDefined}
|
||||
onChange={(c) => setVar(v.name, { skipIfDefined: c })}
|
||||
aria-label={`Skip ${v.name} if defined`}
|
||||
aria-label={tx`Skip ${v.name} if defined`}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Checkbox, T } from "@/ui";
|
||||
import { POSTFIX_GROUPS, POSTFIX_TEMPLATES } from "../postfix/model";
|
||||
import { setPostfixTemplateEnabled, updatePostfixSettings, usePostfixSettings } from "../postfix/store";
|
||||
import styles from "./Settings.module.css";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export function PostfixPage() {
|
||||
const settings = usePostfixSettings();
|
||||
@@ -59,7 +60,7 @@ export function PostfixPage() {
|
||||
checked={!disabled.has(t.id)}
|
||||
disabled={!settings.enabled}
|
||||
onChange={(v) => setPostfixTemplateEnabled(t.id, v)}
|
||||
aria-label={`Enable .${t.key}`}
|
||||
aria-label={tx`Enable .${t.key}`}
|
||||
/>
|
||||
<span className={styles.mono}>.{t.key}</span>
|
||||
<span className={styles.itemDetail}>{t.description}</span>
|
||||
|
||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Заблокувати користувача