Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions scripts/generate-manual-eval-modes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { RUSSIAN_STYLES, TONE_PROMPTS } from "../src/tones.ts";
import { LANGUAGE_PRESETS } from "../src/tones.ts";
import type { EditingMode } from "../src/modes.ts";

type ManualMode = Omit<EditingMode, "lastUsedAt">;
Expand Down Expand Up @@ -71,7 +71,8 @@ export async function generateManualEvalModes(
const models = validateManualModels(
await readJsonFile(path.join(inputDir, "models.json")),
);
const modes = buildManualEvalModes(RUSSIAN_STYLES, TONE_PROMPTS, models);
const preset = LANGUAGE_PRESETS.ru;
const modes = buildManualEvalModes(preset.styles, preset.prompts, models);
const outputPath = path.join(inputDir, "modes.json");

await writeFile(outputPath, `${JSON.stringify(modes, null, 2)}\n`, "utf8");
Expand Down
166 changes: 166 additions & 0 deletions src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import type { Language } from "./language.ts";

type UiStrings = {
rewriteSelectedText: string;
rewriteFailed: string;
retry: string;
openSettings: string;
noModes: string;
noModesDescription: string;
cancel: string;
rewrite: string;
selectionMissing: string;
selectedTextTooLong: (length: number, maximum: number) => string;
configureApiKey: string;
rewriteCancelled: string;
editingModes: string;
language: string;
english: string;
russian: string;
currentPreset: (language: string) => string;
changeLanguage: string;
resetModes: string;
resetModesQuestion: string;
resetModesDescription: (language: string) => string;
reset: string;
createMode: string;
editMode: string;
deleteMode: string;
deleteModeQuestion: string;
deleteModeDescription: (title: string) => string;
moveUp: string;
moveDown: string;
orderInRewrite: string;
customOrder: string;
lastUsedOrder: string;
openPreferences: string;
modeFormInvalid: string;
newMode: string;
save: string;
title: string;
description: string;
provider: string;
model: string;
systemPrompt: string;
temperature: string;
maxTokens: string;
actionFailed: (action: string) => string;
damagedStorage: string;
invalidModeField: (field: string) => string;
unsupportedSort: string;
modeNotFound: string;
};

export const UI_STRINGS: Record<Language, UiStrings> = {
en: {
rewriteSelectedText: "Rewrite Selected Text",
rewriteFailed: "Could Not Rewrite Text",
retry: "Retry",
openSettings: "Open Settings",
noModes: "No Modes",
noModesDescription: "Create a mode in Settings to rewrite text.",
cancel: "Cancel",
rewrite: "Rewrite",
selectionMissing: "No text selected",
selectedTextTooLong: (length, maximum) =>
`Selected text is too long (${length} characters). Maximum: ${maximum}.`,
configureApiKey: "Configure an API key in Preferences",
rewriteCancelled: "Processing was cancelled or exceeded 60 seconds.",
editingModes: "Editing Modes",
language: "Language",
english: "English",
russian: "Russian",
currentPreset: (language) => `Current default preset: ${language}`,
changeLanguage: "Change Language",
resetModes: "Reset Modes",
resetModesQuestion: "Reset modes?",
resetModesDescription: (language) =>
`All saved modes will be replaced with the ${language} defaults.`,
reset: "Reset",
createMode: "Create Mode",
editMode: "Edit Mode",
deleteMode: "Delete Mode",
deleteModeQuestion: "Delete mode?",
deleteModeDescription: (title) =>
`Mode “${title}” will be permanently deleted.`,
moveUp: "Move Up",
moveDown: "Move Down",
orderInRewrite: "Order in Rewrite This",
customOrder: "Custom Order",
lastUsedOrder: "Last Used",
openPreferences: "Open Preferences",
modeFormInvalid: "Check mode settings",
newMode: "New Mode",
save: "Save",
title: "Title",
description: "Description",
provider: "Provider",
model: "Model",
systemPrompt: "System Prompt",
temperature: "Temperature",
maxTokens: "Maximum Tokens",
actionFailed: (action) => `Could not ${action.toLowerCase()}`,
damagedStorage: "Saved modes are corrupted. Edit them in Settings.",
invalidModeField: (field) => `Invalid mode ${field}.`,
unsupportedSort: "Choose a supported sort order.",
modeNotFound: "Mode not found.",
},
ru: {
rewriteSelectedText: "Переписать выделенный текст",
rewriteFailed: "Не удалось переписать текст",
retry: "Повторить",
openSettings: "Открыть Настройки",
noModes: "Нет режимов",
noModesDescription:
"Создайте режим в настройках, чтобы переписывать текст.",
cancel: "Отменить",
rewrite: "Переписать",
selectionMissing: "Не выделен текст",
selectedTextTooLong: (length, maximum) =>
`Выделенный текст слишком длинный (${length} символов). Максимум: ${maximum}.`,
configureApiKey: "Настройте API-ключ в Preferences",
rewriteCancelled: "Обработка отменена или превысила 60 секунд.",
editingModes: "Режимы редактирования",
language: "Язык",
english: "Английский",
russian: "Русский",
currentPreset: (language) => `Текущий стандартный набор: ${language}`,
changeLanguage: "Изменить язык",
resetModes: "Сбросить режимы",
resetModesQuestion: "Сбросить режимы?",
resetModesDescription: (language) =>
`Все сохранённые режимы будут заменены стандартным набором ${language}.`,
reset: "Сбросить",
createMode: "Создать режим",
editMode: "Изменить режим",
deleteMode: "Удалить режим",
deleteModeQuestion: "Удалить режим?",
deleteModeDescription: (title) =>
`Режим «${title}» будет удалён без возможности восстановления.`,
moveUp: "Переместить выше",
moveDown: "Переместить ниже",
orderInRewrite: "Порядок в Rewrite This",
customOrder: "Пользовательский порядок",
lastUsedOrder: "По последнему использованию",
openPreferences: "Открыть Preferences",
modeFormInvalid: "Проверьте параметры режима",
newMode: "Новый режим",
save: "Сохранить",
title: "Название",
description: "Описание",
provider: "Провайдер",
model: "Модель",
systemPrompt: "Системная инструкция",
temperature: "Температура",
maxTokens: "Максимум токенов",
actionFailed: (action) => `Не удалось: ${action.toLowerCase()}`,
damagedStorage: "Сохранённые режимы повреждены. Измените их в настройках.",
invalidModeField: (field) => `Некорректное поле режима: ${field}.`,
unsupportedSort: "Выберите поддерживаемый порядок сортировки.",
modeNotFound: "Режим не найден.",
},
};

export function getUiStrings(language: Language): UiStrings {
return UI_STRINGS[language];
}
40 changes: 21 additions & 19 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
type EditingMode,
} from "./modes";
import { getRewriteViewState } from "./rewrite-state";
import { DEFAULT_LANGUAGE, type Language } from "./language";
import { getUiStrings } from "./i18n";

const MAX_TEXT_LENGTH = 20_000;
const REQUEST_TIMEOUT_MS = 60_000;
Expand All @@ -34,6 +36,7 @@ export default function Command() {
const [text, setText] = useState<string>();
const [modes, setModes] = useState<EditingMode[]>([]);
const [sortMode, setSortMode] = useState<"custom" | "last-used">("custom");
const [language, setLanguage] = useState<Language>(DEFAULT_LANGUAGE);
const [error, setError] = useState<string>();
const [isTextLoading, setIsTextLoading] = useState(true);
const [isModesLoading, setIsModesLoading] = useState(true);
Expand All @@ -43,7 +46,8 @@ export default function Command() {
useEffect(() => {
selectedTextPromise
.then((value) => {
if (!value.trim()) throw new Error("Не выделен текст");
if (!value.trim())
throw new Error(getUiStrings(language).selectionMissing);
setText(value);
})
.catch((reason) => setError(String(reason)))
Expand All @@ -53,9 +57,10 @@ export default function Command() {
useEffect(() => {
async function loadSavedModes() {
try {
const { modes, sortMode } = await loadModeSettings();
const { modes, sortMode, language } = await loadModeSettings();
setModes(modes);
setSortMode(sortMode);
setLanguage(language);
} catch (reason) {
setError(String(reason));
} finally {
Expand All @@ -74,13 +79,12 @@ export default function Command() {
error,
});
const sortedModes = sortModes(modes, sortMode);
const ui = getUiStrings(language);

async function rewrite(mode: EditingMode) {
if (!text || viewState !== "ready") return;
if (text.length > MAX_TEXT_LENGTH) {
setError(
`Выделенный текст слишком длинный (${text.length} символов). Максимум: ${MAX_TEXT_LENGTH}.`,
);
setError(ui.selectedTextTooLong(text.length, MAX_TEXT_LENGTH));
return;
}

Expand All @@ -101,7 +105,7 @@ export default function Command() {
};
const apiKey = apiKeys[mode.provider];
if (mode.provider !== "ollama" && !apiKey)
throw new Error("Настрой API-ключ в Preferences");
throw new Error(ui.configureApiKey);
const options: RewriteOptions = {
text,
mode,
Expand All @@ -125,9 +129,7 @@ export default function Command() {
await Clipboard.paste(result);
} catch (reason) {
setError(
controller.signal.aborted
? "Обработка отменена или превысила 60 секунд."
: String(reason),
controller.signal.aborted ? ui.rewriteCancelled : String(reason),
);
} finally {
clearTimeout(timeoutId);
Expand All @@ -144,17 +146,17 @@ export default function Command() {
<List>
<List.EmptyView
icon={Icon.ExclamationMark}
title="Не удалось переписать текст"
title={ui.rewriteFailed}
description={error}
actions={
<ActionPanel>
<Action
title="Повторить"
title={ui.retry}
icon={Icon.ArrowClockwise}
onAction={() => setError(undefined)}
/>
<Action
title="Открыть Настройки"
title={ui.openSettings}
icon={Icon.Gear}
onAction={openExtensionPreferences}
/>
Expand All @@ -165,15 +167,15 @@ export default function Command() {
);
if (viewState === "empty")
return (
<List navigationTitle="Переписать выделенный текст">
<List navigationTitle={ui.rewriteSelectedText}>
<List.EmptyView
icon={Icon.Text}
title="Нет режимов"
description="Создайте режим в настройках, чтобы переписывать текст."
title={ui.noModes}
description={ui.noModesDescription}
actions={
<ActionPanel>
<Action
title="Открыть Настройки"
title={ui.openSettings}
icon={Icon.Gear}
onAction={openExtensionPreferences}
/>
Expand All @@ -185,7 +187,7 @@ export default function Command() {
return (
<List
isLoading={viewState === "loading" || isRewriting}
navigationTitle="Переписать выделенный текст"
navigationTitle={ui.rewriteSelectedText}
>
{viewState === "ready"
? sortedModes.map((mode) => (
Expand All @@ -198,13 +200,13 @@ export default function Command() {
<ActionPanel>
{isRewriting ? (
<Action
title="Отменить"
title={ui.cancel}
icon={Icon.XMarkCircle}
onAction={cancelRewrite}
/>
) : (
<Action
title="Переписать"
title={ui.rewrite}
icon={Icon.Wand}
onAction={() => rewrite(mode)}
/>
Expand Down
7 changes: 7 additions & 0 deletions src/language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const DEFAULT_LANGUAGE = "en" as const;

export type Language = "en" | "ru";

export function isLanguage(value: unknown): value is Language {
return value === "en" || value === "ru";
}
Loading
Loading