Applications HTML simples
Marquer le HTML pour la traduction
Pour les applications HTML simples (sans appels t("…") dans le balisage), marquez les éléments traduisibles avec des attributs et laissez extract capturer le texte anglais de l'élément lui-même — pas de littéraux de chaîne dupliqués.
Préférez la forme nue (l'attribut n'a pas de valeur ; le texte source est lu à partir de l'élément) :
data-i18n— la clé est letextContentde l'élément ; à l'exécution, vous définissezel.textContent = t(key).data-i18n-title— la clé est letitlede l'élément ; à l'exécution, vous définissez letitletraduit.data-i18n-placeholder— la clé est leplaceholderde l'élément.
Utilisez la forme avec valeur data-i18n="Some key" uniquement lorsque la forme nue ne peut pas fonctionner : éléments à contenu mixte (texte entrelacé avec des balises enfants), ou lorsque la clé doit différer du texte visible. Excluez un élément (et son sous-arbre) avec data-i18n-ignore.
Contrainte : la forme nue data-i18n est réservée aux éléments de texte feuilles uniquement (un seul nœud de texte, aucun élément enfant), car la définition de textContent remplace les enfants. Pour un paragraphe comme Run <code>build</code> now., encapsulez chaque segment de texte dans son propre marqueur à la place :
<p><span data-i18n>Run</span> <code>build</code> <span data-i18n>now.</span></p>Ajoutez les marqueurs manuellement, ou laissez la commande mark-html insérer les marqueurs nus pour vous. Elle s'exécute en mode simulation par défaut — elle indique combien de marqueurs elle ajouterait par fichier et liste les éléments à contenu mixte qui nécessitent un <span data-i18n> manuel — et n'écrit qu'avec --write :
# Preview (no changes written)
ai-i18n-tools mark-html public/index.html
# Apply the bare markers
ai-i18n-tools mark-html public/index.html --writemark-html est idempotent, respecte data-i18n-ignore, ne marque jamais les éléments de type code (code, pre, kbd, samp, var) ni le texte vide / uniquement numérique, et n'émet jamais de marqueur avec valeur. Après le marquage, encapsulez manuellement tous les fragments à contenu mixte signalés, puis ajoutez .html à ui.uiExtractor.extensions afin que extract capture les chaînes :
{
"ui": {
"sourceRoots": ["src", "public"],
"uiExtractor": { "extensions": [".ts", ".tsx", ".html"] }
}
}Exemple pratique : localisation d'une application HTML simple
L'exemple d'espace de travail examples/plain-html est une application statique exécutable qui utilise ces marqueurs de bout en bout. Clonez-le avec npx degit wsj-br/ai-i18n-tools/examples/plain-html plain-html, exécutez pnpm install et pnpm dev, puis ouvrez http://localhost:3090/?locale=pt-BR pour le portugais (Brésil).
Son public/index.html contient des marqueurs bruts comme :
<button type="button" id="btn-apply" data-i18n>Apply</button>
<input
type="text"
id="filter-filename"
placeholder="Filename (partial)"
title="Filter by filepath"
data-i18n-title
data-i18n-placeholder
/>
<p>
<span data-i18n>Run</span> <code>mark-html</code>
<span data-i18n>to add bare markers, then</span> <code>extract</code>
<span data-i18n>and</span> <code>translate-ui</code><span data-i18n>.</span>
</p>ai-i18n-tools.config.json dirige l'extraction vers public/ et écrit des bundles plats à côté des fichiers statiques :
{
"sourceLocale": "en",
"targetLocales": ["es", "fr", "pt-BR"],
"features": { "translateUIStrings": true },
"ui": {
"sourceRoots": ["public"],
"stringsJson": "public/strings.json",
"flatOutputDir": "public/locales",
"uiExtractor": { "extensions": [".html"] }
}
}extract écrit chaque chaîne source anglaise dans le catalogue (public/strings.json), et translate-ui remplit un bundle plat par locale, indexé par le texte source anglais :
pnpm i18n:extract # public/index.html markers → public/strings.json
pnpm i18n:translate-ui # strings.json → public/locales/{locale}.json// public/locales/pt-BR.json
{
"Apply": "Aplicar",
"Filename (partial)": "Nome do arquivo (parcial)",
"Filter by filepath": "Filtrar por caminho do arquivo",
"Run": "Execute",
"to add bare markers, then": "para adicionar marcadores simples, depois",
"and": "e",
".": "."
}Au moment de l'exécution, public/app.js charge /locales/ui-languages.json pour les métadonnées de la locale, résout la locale active (?locale= → localStorage → navigateur → en), récupère /locales/{locale}.json (ignoré pour l'anglais), puis parcourt les éléments marqués. La clé provient de la valeur du marqueur lorsqu'elle est présente, sinon du propre texte / titre / placeholder de l'élément (normalisé de la même manière que l'extracteur normalise les espaces blancs) :
function normalizeI18nText(s) {
return s.trim().replace(/\s+/g, " ");
}
function t(key) {
const raw = I18N.bundle[key];
return typeof raw === "string" && raw.length > 0 ? raw : key;
}
function applyStaticI18n() {
document.querySelectorAll("[data-i18n]").forEach((el) => {
const key = el.getAttribute("data-i18n") || normalizeI18nText(el.textContent || "");
if (key) el.textContent = t(key);
});
document.querySelectorAll("[data-i18n-title]").forEach((el) => {
const key = el.getAttribute("data-i18n-title") || normalizeI18nText(el.getAttribute("title") || "");
if (key) el.setAttribute("title", t(key));
});
document.querySelectorAll("[data-i18n-placeholder]").forEach((el) => {
const key =
el.getAttribute("data-i18n-placeholder") ||
normalizeI18nText(el.getAttribute("placeholder") || "");
if (key) el.setAttribute("placeholder", t(key));
});
}normalizeI18nText doit rester identique à normalizeI18nText dans src/extractors/html-i18n-marks.ts. Étant donné que le texte source anglais est la clé du catalogue, les chaînes non traduites reviennent automatiquement à l'anglais.
Le tableau de bord de traduction intégré utilise le même algorithme applyStaticI18n pour ses marqueurs HTML, mais sert les bundles de locale à partir de GET /api/ui-i18n au lieu de fichiers statiques /locales/{locale}.json. Consultez le README de l'exemple pour le workflow complet, la structure du projet et le tableau de comparaison.