feat: add complete French localization (#1482)
* feat: add French locale runtime Add French to OpenChamber's shared i18n runtime, dictionaries, and parity tests so the existing language picker can load a complete fr locale across shared UI surfaces. * fix: localize shared UI formatting Remove remaining shared UI locale hardcodings so dates, numbers, and first-party helper copy follow the active app locale instead of leaking English on French surfaces. * feat: localize VS Code French surfaces Localize VS Code bootstrap, native runtime messages, panel titles, and manifest contribution strings so French users get consistent first-party copy across the extension experience. * fix: TASK-2026-05-30-008 correct French review findings Fix broken French relative-time and weekday strings reported on PR #1482 and restore proper import order in quota utils without broadening scope. * fix: TASK-2026-05-30-008 address final PR review comments Capture the localized More Info label once in the VS Code CLI-missing flow and replace the remaining inline French-only utility strings with dictionary-driven copy plus required locale keys. * fix: TASK-2026-05-30-008 normalize French glossary Correct glossary-level French terminology on the live PR branch, keeping canonical technical terms like PR, worktree, stash, HEAD, Mermaid, Markdown, remote, and session while replacing misleading literal translations. * fix: TASK-2026-05-30-008 refine French terminology pass Clean up remaining glossary mistakes on the French PR branch, especially around Mermaid, Markdown, PR, worktree, stash, branch, remote, and commit terminology, while keeping behavior unchanged. * fix: TASK-2026-05-30-008 clean remaining French false friends Correct the SOCKS5 mistranslation and a final small set of obvious false-friend technical nouns on the French branch without changing behavior. * fix: TASK-2026-05-30-008 correct French glossary terms Replace remaining false-friend translations in the French UI dictionaries and normalize technical labels for the French PR branch. * fix: TASK-2026-05-30-008 remove remaining French Mermaid false friend Replace the last confirmed Sirène translation with Mermaid and re-run the requested blacklist and build verification on the PR branch. * fix: TASK-2026-05-30-008 enforce French glossary policy Keep skill/PR/worktree/remote terminology developer-credible in French and remove remaining machine-translated Git and settings copy. * fix: TASK-2026-05-30-008 keep prompt terminology in French Replace remaining technical invite translations with prompt wording across scheduled tasks, multi-run, prompt templates, and Magic Prompts. * fix: TASK-2026-05-30-008 finalize French terminology cleanup Polish remaining worktree/remote wording, remove visible metadata leakage, and correct final Git and settings labels on the French PR branch. * fix: TASK-2026-05-30-008 polish final French strings Correct the last aria-like artifacts and awkward worktree/remote/GitHub URL phrasing in the French dictionaries. * fix: TASK-2026-05-30-008 normalize final French glossary framing Tighten the last worktree/remote/checkout wording and fix remaining French grammar around canonical technical terms. * fix: TASK-2026-05-30-008 align final developer glossary wording Normalize the last French framing around canonical developer terms like worktree, remote, prompt, and checkout. * fix: TASK-2026-05-30-008 harmonize final French sentence framing Replace the last raw franglais around checkout, remote, worktree, and prompt-facing labels with more natural French framing while keeping the chosen technical terms. * fix: TASK-2026-05-30-008 add compact relative date keys Replace French-specific prefix stripping in compact session date labels with dedicated i18n keys across locale dictionaries, preserving existing compact label output while making French wording robust. * docs: add French documentation * docs: mention French locale folder --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
85bf7c7563
commit
49a1424e5f
@@ -8,6 +8,8 @@ import { openSseProxy } from './sseProxy';
|
||||
import { resolveWebviewDevServerUrl } from './webviewDevServer';
|
||||
import { normalizeWindowsDriveLetter } from './pathUtils';
|
||||
|
||||
const t = vscode.l10n.t;
|
||||
|
||||
export class AgentManagerPanelProvider {
|
||||
public static readonly viewType = 'openchamber.agentManager';
|
||||
|
||||
@@ -40,7 +42,7 @@ export class AgentManagerPanelProvider {
|
||||
// Create new panel
|
||||
this._panel = vscode.window.createWebviewPanel(
|
||||
AgentManagerPanelProvider.viewType,
|
||||
'Agent Manager',
|
||||
t('Agent Manager'),
|
||||
vscode.ViewColumn.One,
|
||||
{
|
||||
enableScripts: true,
|
||||
|
||||
@@ -8,6 +8,8 @@ import { openSseProxy } from './sseProxy';
|
||||
import { resolveWebviewDevServerUrl } from './webviewDevServer';
|
||||
import { normalizeWindowsDriveLetter } from './pathUtils';
|
||||
|
||||
const t = vscode.l10n.t;
|
||||
|
||||
type SessionPanelState = {
|
||||
panel: vscode.WebviewPanel;
|
||||
sseStreams: Map<string, AbortController>;
|
||||
@@ -62,7 +64,7 @@ export class SessionEditorPanelProvider {
|
||||
public createOrShowNewSession(): void {
|
||||
// Generate unique panel ID for new session drafts
|
||||
const panelId = `new_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
||||
this._createPanel(panelId, 'New Session', null);
|
||||
this._createPanel(panelId, t('New Session'), null);
|
||||
}
|
||||
|
||||
public createOrShow(sessionId: string, title?: string): void {
|
||||
@@ -70,7 +72,7 @@ export class SessionEditorPanelProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionTitle = title && title.trim().length > 0 ? title.trim() : 'Session';
|
||||
const sessionTitle = title && title.trim().length > 0 ? title.trim() : t('Session');
|
||||
|
||||
const existing = this._panels.get(sessionId);
|
||||
if (existing) {
|
||||
@@ -134,7 +136,7 @@ export class SessionEditorPanelProvider {
|
||||
if (message.type === 'vscode:command') {
|
||||
const { command, args } = (message.payload || {}) as { command?: unknown; args?: unknown[] };
|
||||
if (command === 'openchamber.updateSessionEditorTitle') {
|
||||
const title = typeof args?.[1] === 'string' && args[1].trim().length > 0 ? args[1].trim() : 'Session';
|
||||
const title = typeof args?.[1] === 'string' && args[1].trim().length > 0 ? args[1].trim() : t('Session');
|
||||
state.panel.title = title;
|
||||
state.panel.webview.postMessage({ id: message.id, type: message.type, success: true, data: { result: true } });
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,8 @@ let outputChannel: vscode.OutputChannel | undefined;
|
||||
let activeSessionId: string | null = null;
|
||||
let activeSessionTitle: string | null = null;
|
||||
|
||||
const t = vscode.l10n.t;
|
||||
|
||||
const SETTINGS_KEY = 'openchamber.settings';
|
||||
const CHAT_VIEW_BOOTSTRAP_DELAY_MS = 80;
|
||||
|
||||
@@ -147,13 +149,13 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
await vscode.commands.executeCommand('openchamber.chatView.focus');
|
||||
} catch (e) {
|
||||
outputChannel?.appendLine(`[OpenChamber] openchamber.chatView.focus failed: ${e}`);
|
||||
vscode.window.showErrorMessage(`OpenChamber: Failed to open sidebar - ${e}`);
|
||||
vscode.window.showErrorMessage(t('OpenChamber: Failed to open sidebar - {0}', String(e)));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chatViewProvider?.hasResolvedView()) {
|
||||
outputChannel?.appendLine('[OpenChamber] Chat sidebar focus completed before the webview was resolved');
|
||||
vscode.window.showWarningMessage('OpenChamber: Chat sidebar is not ready');
|
||||
vscode.window.showWarningMessage(t('OpenChamber: Chat sidebar is not ready'));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -170,7 +172,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
await waitForChatViewBootstrap();
|
||||
if (!chatViewProvider?.hasResolvedView()) {
|
||||
outputChannel?.appendLine('[OpenChamber] Chat sidebar webview was disposed before payload delivery');
|
||||
vscode.window.showWarningMessage('OpenChamber: Chat sidebar is not ready');
|
||||
vscode.window.showWarningMessage(t('OpenChamber: Chat sidebar is not ready'));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -227,7 +229,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('openchamber.openActiveSessionInEditor', () => {
|
||||
if (!activeSessionId) {
|
||||
vscode.window.showInformationMessage('OpenChamber: No active session');
|
||||
vscode.window.showInformationMessage(t('OpenChamber: No active session'));
|
||||
return;
|
||||
}
|
||||
sessionEditorProvider?.createOrShow(activeSessionId, activeSessionTitle ?? undefined);
|
||||
@@ -270,9 +272,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
return;
|
||||
}
|
||||
await openCodeManager?.restart();
|
||||
vscode.window.showInformationMessage('OpenChamber: API connection restarted');
|
||||
vscode.window.showInformationMessage(t('OpenChamber: API connection restarted'));
|
||||
} catch (e) {
|
||||
vscode.window.showErrorMessage(`OpenChamber: Failed to restart API - ${e}`);
|
||||
vscode.window.showErrorMessage(t('OpenChamber: Failed to restart API - {0}', String(e)));
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -281,7 +283,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand('openchamber.addToContext', async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
vscode.window.showWarningMessage('OpenChamber [Add to Context]:No active editor');
|
||||
vscode.window.showWarningMessage(t('OpenChamber [Add to Context]: No active editor'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -289,7 +291,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const selectedText = editor.document.getText(selection);
|
||||
|
||||
if (!selectedText) {
|
||||
vscode.window.showWarningMessage('OpenChamber [Add to Context]: No text selected');
|
||||
vscode.window.showWarningMessage(t('OpenChamber [Add to Context]: No text selected'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -370,7 +372,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
if (attachedFiles.length === 0) {
|
||||
vscode.window.showWarningMessage('OpenChamber: No file selected to mention');
|
||||
vscode.window.showWarningMessage(t('OpenChamber: No file selected to mention'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -382,7 +384,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
if (skippedEntries.length > 0) {
|
||||
vscode.window.showInformationMessage('OpenChamber: Some selected entries were skipped (folders or unsupported resources)');
|
||||
vscode.window.showInformationMessage(t('OpenChamber: Some selected entries were skipped (folders or unsupported resources)'));
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -391,7 +393,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand('openchamber.explain', async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
vscode.window.showWarningMessage('OpenChamber [Explain]: No active editor');
|
||||
vscode.window.showWarningMessage(t('OpenChamber [Explain]: No active editor'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -407,10 +409,10 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const startLine = selection.start.line + 1;
|
||||
const endLine = selection.end.line + 1;
|
||||
const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`;
|
||||
prompt = `Explain the following Code / Text:\n\n${filePath}:${lineRange}\n\`\`\`${languageId}\n${selectedText}\n\`\`\``;
|
||||
prompt = `${t('Explain the following Code / Text:')}\n\n${filePath}:${lineRange}\n\`\`\`${languageId}\n${selectedText}\n\`\`\``;
|
||||
} else {
|
||||
// No selection - explain the entire file
|
||||
prompt = `Explain the following Code / Text:\n\n${filePath}`;
|
||||
prompt = `${t('Explain the following Code / Text:')}\n\n${filePath}`;
|
||||
}
|
||||
|
||||
if (!sessionEditorProvider?.createSessionWithPromptInActivePanel(prompt)) {
|
||||
@@ -426,7 +428,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand('openchamber.improveCode', async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
vscode.window.showWarningMessage('OpenChamber [Improve Code]: No active editor');
|
||||
vscode.window.showWarningMessage(t('OpenChamber [Improve Code]: No active editor'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -434,7 +436,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const selectedText = editor.document.getText(selection);
|
||||
|
||||
if (!selectedText) {
|
||||
vscode.window.showWarningMessage('OpenChamber [Improve Code]: No text selected');
|
||||
vscode.window.showWarningMessage(t('OpenChamber [Improve Code]: No text selected'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -444,7 +446,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const endLine = selection.end.line + 1;
|
||||
const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`;
|
||||
|
||||
const prompt = `Improve the following Code:\n\n${filePath}:${lineRange}\n\`\`\`${languageId}\n${selectedText}\n\`\`\``;
|
||||
const prompt = `${t('Improve the following Code:')}\n\n${filePath}:${lineRange}\n\`\`\`${languageId}\n${selectedText}\n\`\`\``;
|
||||
|
||||
if (!sessionEditorProvider?.createSessionWithPromptInActivePanel(prompt)) {
|
||||
if (!(await revealChatViewForPayload())) {
|
||||
|
||||
@@ -9,6 +9,8 @@ import { spawn } from 'child_process';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { normalizeWindowsDriveLetter } from './pathUtils';
|
||||
|
||||
const t = vscode.l10n.t;
|
||||
|
||||
const READY_CHECK_TIMEOUT_MS = 30000;
|
||||
const WINDOWS_EXECUTABLE_EXTENSIONS = (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
|
||||
.split(';')
|
||||
@@ -931,17 +933,18 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
||||
if (!cliPath) {
|
||||
cliPath = resolveOpencodeCliPath();
|
||||
}
|
||||
setStatus('error', 'OpenCode CLI not found. Install it and ensure it\'s in PATH.');
|
||||
const moreInfoLabel = t('More Info');
|
||||
setStatus('error', t('OpenCode CLI not found. Install it and ensure it\'s in PATH.'));
|
||||
vscode.window.showErrorMessage(
|
||||
'OpenCode CLI not found. Please install it and ensure it\'s in PATH.',
|
||||
'More Info'
|
||||
t('OpenCode CLI not found. Please install it and ensure it\'s in PATH.'),
|
||||
moreInfoLabel
|
||||
).then(selection => {
|
||||
if (selection === 'More Info') {
|
||||
if (selection === moreInfoLabel) {
|
||||
vscode.env.openExternal(vscode.Uri.parse('https://github.com/anomalyco/opencode'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setStatus('error', `Failed to start OpenCode: ${message}`);
|
||||
setStatus('error', t('Failed to start OpenCode: {0}', message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
|
||||
</svg>
|
||||
<!-- Status text stays empty while things are fine; populated only on error. -->
|
||||
<div class="status-text" id="loading-status"></div>
|
||||
${!cliAvailable ? `<div class="error-text">OpenCode CLI not found. Please install it first.</div>` : ''}
|
||||
${!cliAvailable ? `<div class="error-text" id="cli-missing-text">OpenCode CLI not found. Please install it first.</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div id="root"></div>
|
||||
@@ -188,16 +188,62 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
|
||||
};
|
||||
window.__OPENCHAMBER_HOME__ = "${workspaceFolder.replace(/\\/g, '\\\\')}";
|
||||
|
||||
function getBootstrapMessages() {
|
||||
var locale = 'en';
|
||||
try {
|
||||
var rawLocale = window.localStorage.getItem('openchamber.i18n.v1');
|
||||
if (rawLocale) {
|
||||
var parsedLocale = JSON.parse(rawLocale);
|
||||
if (parsedLocale && typeof parsedLocale.locale === 'string' && parsedLocale.locale.toLowerCase().indexOf('fr') === 0) {
|
||||
locale = 'fr';
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return locale === 'fr'
|
||||
? {
|
||||
startingApi: 'Démarrage de l’API OpenCode…',
|
||||
initializing: 'Initialisation…',
|
||||
connecting: 'Connexion…',
|
||||
connected: 'Connecté !',
|
||||
connectionError: 'Erreur de connexion',
|
||||
reconnecting: 'Reconnexion…',
|
||||
cliNotFound: 'L’interface en ligne de commande OpenCode est introuvable. Veuillez l’installer d’abord.',
|
||||
}
|
||||
: {
|
||||
startingApi: 'Starting OpenCode API…',
|
||||
initializing: 'Initializing…',
|
||||
connecting: 'Connecting…',
|
||||
connected: 'Connected!',
|
||||
connectionError: 'Connection error',
|
||||
reconnecting: 'Reconnecting…',
|
||||
cliNotFound: 'OpenCode CLI not found. Please install it first.',
|
||||
};
|
||||
}
|
||||
|
||||
(function applyBootstrapLocale() {
|
||||
var statusEl = document.getElementById('loading-status');
|
||||
var cliMissingEl = document.getElementById('cli-missing-text');
|
||||
var messages = getBootstrapMessages();
|
||||
if (cliMissingEl) {
|
||||
cliMissingEl.textContent = messages.cliNotFound;
|
||||
}
|
||||
if (statusEl) {
|
||||
statusEl.textContent = '';
|
||||
}
|
||||
})();
|
||||
|
||||
// Handle connection status updates to update loading screen
|
||||
window.addEventListener('message', function(event) {
|
||||
var msg = event.data;
|
||||
if (msg && msg.type === 'connectionStatus') {
|
||||
var messages = getBootstrapMessages();
|
||||
var statusEl = document.getElementById('loading-status');
|
||||
if (statusEl) {
|
||||
// Only show text when something is wrong — progress states stay silent
|
||||
// (the animated logo already signals "working").
|
||||
if (msg.status === 'error') {
|
||||
statusEl.textContent = msg.error || 'Connection error';
|
||||
statusEl.textContent = msg.error || messages.connectionError;
|
||||
statusEl.classList.add('error-text');
|
||||
} else {
|
||||
statusEl.textContent = '';
|
||||
@@ -224,6 +270,24 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
|
||||
const baseUrl = devServerUrl;
|
||||
|
||||
const statusEl = document.getElementById('loading-status');
|
||||
const getDevMessages = () => {
|
||||
try {
|
||||
const rawLocale = window.localStorage.getItem('openchamber.i18n.v1');
|
||||
if (rawLocale) {
|
||||
const parsedLocale = JSON.parse(rawLocale);
|
||||
if (parsedLocale && typeof parsedLocale.locale === 'string' && parsedLocale.locale.toLowerCase().indexOf('fr') === 0) {
|
||||
return {
|
||||
startingDevServer: (host) => 'Démarrage du serveur de développement de la webview (' + host + ')...',
|
||||
waitingDevServer: (host, attempt) => 'En attente du serveur de développement de la webview (' + host + ')... tentative ' + attempt,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return {
|
||||
startingDevServer: (host) => 'Starting webview dev server (' + host + ')...',
|
||||
waitingDevServer: (host, attempt) => 'Waiting for webview dev server (' + host + ')... attempt ' + attempt,
|
||||
};
|
||||
};
|
||||
const setStatus = (text) => {
|
||||
if (statusEl) {
|
||||
statusEl.textContent = text;
|
||||
@@ -272,7 +336,8 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
|
||||
}
|
||||
})();
|
||||
|
||||
setStatus('Starting webview dev server (' + hostLabel + ')...');
|
||||
const devMessages = getDevMessages();
|
||||
setStatus(devMessages.startingDevServer(hostLabel));
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => import(viteClientUrl))
|
||||
@@ -296,7 +361,7 @@ export function getWebviewHtml(options: WebviewHtmlOptions): string {
|
||||
.catch((error) => {
|
||||
attempt += 1;
|
||||
console.warn('[OpenChamber] VS Code webview dev bundle unavailable, retrying...', error);
|
||||
setStatus('Waiting for webview dev server (' + hostLabel + ')... attempt ' + attempt);
|
||||
setStatus(devMessages.waitingDevServer(hostLabel, attempt));
|
||||
window.setTimeout(() => {
|
||||
tryLoadDevBundle();
|
||||
}, retryDelayMs);
|
||||
|
||||
Reference in New Issue
Block a user