mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
fix(i18n): Translate UI text built from template literals
Template literals such as `Remove data source '${name}'?` reached props and
calls that look text up exactly (dialog messages, labels, aria-labels, menu
items, action titles), so they always showed in English. Some notification
titles also spliced lowercase English words into a sentence.
- `tx` also works as a tag: tx`Remove data source '${name}'?` looks up
"Remove data source '{name}'?" by its literal parts, fills in the values
(translated when the catalog has them) and remembers the result, so the
primitives that pass strings through `tx` translate it again after a locale
change or once the catalog has loaded (actions registered at startup).
- 269 template literals at exact-lookup sites now use the tag; text that
spliced words or plural suffixes is written as one sentence per variant
(safe delete, Docker delete/prune, rebase, conflicts, VCS "No … in
progress", database copy errors, inspection descriptions, ...).
- zh: 280 new entries; stale and duplicate-shape entries removed.
- textCatalog guard: untagged template literals at exact-lookup sites fail
(with an allowlist for code-like text), tagged text is checked against the
catalog by shape, case-changing values and `c ? "a" : "b"` suffixes inside
translated templates fail, and a catalog may hold one entry per template shape.
Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
This commit is contained in:
@@ -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" />;
|
||||
@@ -291,8 +292,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")
|
||||
@@ -314,7 +315,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";
|
||||
@@ -442,7 +442,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"],
|
||||
run: () => setViewOption(key, !useProjectTree.getState().options[key]),
|
||||
@@ -158,7 +159,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"],
|
||||
@@ -334,7 +335,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";
|
||||
|
||||
export const APPEARANCE_GROUP = "Appearance & Behavior";
|
||||
|
||||
@@ -467,14 +467,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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user