Skip to content
Open
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
79 changes: 79 additions & 0 deletions i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";

type Locale = "es" | "fr" | "pt-BR";
type Key = keyof typeof fallback;
type Params = Record<string, string | number>;

const namespace = "pi-mermaid";

const fallback = {
"notify.parserUnavailable": "Mermaid parser validation isn’t usable right now{suffix}. Will try again next time; rendering anyway.",
"notify.tooManyBlocks": "Found {count} mermaid blocks, rendering first {max}.",
"notify.blockTooLarge": "Mermaid block {index} too large ({lines} lines, {chars} chars).",
"notify.unsupportedType": "pi-mermaid can't render type \"{type}\"{blockLabel}. Supported: {supported}.",
"command.description": "Render mermaid in last assistant message as ASCII",
"command.noAssistant": "No assistant message found",
"command.noBlocks": "No mermaid blocks found",
} as const;

const translations: Record<Locale, Partial<Record<Key, string>>> = {
es: {
"notify.parserUnavailable": "La validación del parser de Mermaid no está disponible ahora{suffix}. Se intentará de nuevo la próxima vez; se renderizará de todos modos.",
"notify.tooManyBlocks": "Se encontraron {count} bloques mermaid; se renderizarán los primeros {max}.",
"notify.blockTooLarge": "El bloque Mermaid {index} es demasiado grande ({lines} líneas, {chars} caracteres).",
"notify.unsupportedType": "pi-mermaid no puede renderizar el tipo \"{type}\"{blockLabel}. Compatible: {supported}.",
"command.description": "Renderizar mermaid del último mensaje del asistente como ASCII",
"command.noAssistant": "No se encontró ningún mensaje del asistente",
"command.noBlocks": "No se encontraron bloques mermaid",
},
fr: {
"notify.parserUnavailable": "La validation par le parseur Mermaid n’est pas disponible pour l’instant{suffix}. Nouvelle tentative la prochaine fois ; rendu quand même.",
"notify.tooManyBlocks": "{count} blocs mermaid trouvés, rendu des {max} premiers.",
"notify.blockTooLarge": "Le bloc Mermaid {index} est trop grand ({lines} lignes, {chars} caractères).",
"notify.unsupportedType": "pi-mermaid ne peut pas afficher le type \"{type}\"{blockLabel}. Pris en charge : {supported}.",
"command.description": "Afficher en ASCII le mermaid du dernier message assistant",
"command.noAssistant": "Aucun message assistant trouvé",
"command.noBlocks": "Aucun bloc mermaid trouvé",
},
"pt-BR": {
"notify.parserUnavailable": "A validação do parser Mermaid não está disponível agora{suffix}. Tentará novamente na próxima vez; renderizando mesmo assim.",
"notify.tooManyBlocks": "Encontrados {count} blocos mermaid; renderizando os primeiros {max}.",
"notify.blockTooLarge": "O bloco Mermaid {index} é grande demais ({lines} linhas, {chars} caracteres).",
"notify.unsupportedType": "pi-mermaid não consegue renderizar o tipo \"{type}\"{blockLabel}. Compatível: {supported}.",
"command.description": "Renderizar mermaid da última mensagem do assistente como ASCII",
"command.noAssistant": "Nenhuma mensagem do assistente encontrada",
"command.noBlocks": "Nenhum bloco mermaid encontrado",
},
};

let currentLocale: string | undefined;

function format(template: string, params: Params = {}): string {
return template.replace(/\{(\w+)\}/g, (_match, key) => String(params[key] ?? `{${key}}`));
}

export function t(key: Key, params?: Params): string {
const locale = currentLocale as Locale | undefined;
const template = locale ? translations[locale]?.[key] : undefined;
return format(template ?? fallback[key], params);
}

export function initI18n(pi: ExtensionAPI): void {
pi.events?.emit?.("pi-core/i18n/registerBundle", {
namespace,
defaultLocale: "en",
fallback,
translations,
});
pi.events?.on?.("pi-core/i18n/localeChanged", (event: unknown) => {
currentLocale = event && typeof event === "object" && "locale" in event
? String((event as { locale?: unknown }).locale ?? "")
: undefined;
});
pi.events?.emit?.("pi-core/i18n/requestApi", {
namespace,
onApi(api: { getLocale?: () => string | undefined }) {
currentLocale = api.getLocale?.();
},
});
}
20 changes: 10 additions & 10 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getMarkdownTheme, keyHint } from "@mariozechner/pi-coding-agent";
import { Box, Spacer, Text, type Component, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
import { createHash } from "node:crypto";
import { renderMermaidAscii } from "beautiful-mermaid";
import { initI18n, t } from "./i18n.ts";

const MESSAGE_TYPE = "pi-mermaid";
const MERMAID_BLOCK_RE = /```mermaid\s*([\s\S]*?)```/gi;
Expand Down Expand Up @@ -406,6 +407,8 @@ async function processBlock(
}

export default function (pi: ExtensionAPI) {
initI18n(pi);

const renderMermaidMessage: MessageRenderer<MermaidDetails> = (message, { expanded }, theme) => {
const details = message.details as MermaidDetails | undefined;
const contentText = extractText(message.content);
Expand Down Expand Up @@ -486,18 +489,15 @@ export default function (pi: ExtensionAPI) {
if (!ctx.hasUI || mermaidParserWarned) return;
const suffixSource = errorMessage ?? mermaidParserError;
const suffix = suffixSource ? ` (${suffixSource})` : "";
notify(
`Mermaid parser validation isn’t usable right now${suffix}. Will try again next time; rendering anyway.`,
"warning",
);
notify(t("notify.parserUnavailable", { suffix }), "warning");
mermaidParserWarned = true;
};

let parser = await getMermaidParser();
if (!parser) warnParserUnavailable();

if (blocks.length > MAX_BLOCKS) {
notify(`Found ${blocks.length} mermaid blocks, rendering first ${MAX_BLOCKS}.`, "warning");
notify(t("notify.tooManyBlocks", { count: blocks.length, max: MAX_BLOCKS }), "warning");
}

for (const [index, block] of blocks.slice(0, MAX_BLOCKS).entries()) {
Expand All @@ -506,7 +506,7 @@ export default function (pi: ExtensionAPI) {
const sourceLines = block.split(/\r?\n/);
if (sourceLines.length > MAX_SOURCE_LINES || block.length > MAX_SOURCE_CHARS) {
notify(
`Mermaid block ${blockIndex} too large (${sourceLines.length} lines, ${block.length} chars).`,
t("notify.blockTooLarge", { index: blockIndex, lines: sourceLines.length, chars: block.length }),
"warning",
);
continue;
Expand All @@ -516,7 +516,7 @@ export default function (pi: ExtensionAPI) {
if (!normalized) {
const typeLabel = token ?? "unknown";
notify(
`pi-mermaid can't render type "${typeLabel}"${blockLabel}. Supported: ${SUPPORTED_TYPE_LABEL}.`,
t("notify.unsupportedType", { type: typeLabel, blockLabel, supported: SUPPORTED_TYPE_LABEL }),
"info",
);
continue;
Expand Down Expand Up @@ -576,17 +576,17 @@ export default function (pi: ExtensionAPI) {
});

pi.registerCommand("pi-mermaid", {
description: "Render mermaid in last assistant message as ASCII",
description: t("command.description"),
handler: async (_args, ctx) => {
const lastAssistant = getLastAssistantText(ctx.sessionManager.getBranch());
if (!lastAssistant) {
if (ctx.hasUI) ctx.ui.notify("No assistant message found", "warning");
if (ctx.hasUI) ctx.ui.notify(t("command.noAssistant"), "warning");
return;
}

const blocks = extractMermaidBlocks(lastAssistant, MAX_BLOCKS + 1);
if (blocks.length === 0) {
if (ctx.hasUI) ctx.ui.notify("No mermaid blocks found", "warning");
if (ctx.hasUI) ctx.ui.notify(t("command.noBlocks"), "warning");
return;
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"files": [
"index.ts",
"i18n.ts",
"README.md",
"LICENSE",
"package.json"
Expand Down