Localisation
Loreline intègre nativement la traduction de vos scripts en plusieurs langues.
Comment ça marche
La localisation dans Loreline suit trois étapes :
- Annoter le texte traduisible avec des commentaires
#keydans votre script. - Créer un fichier de traduction (
.lang.lor) pour chaque langue cible. - Charger le fichier de traduction à l'exécution et le passer à l'interpréteur.
L'interpréteur cherche chaque #key à l'exécution et remplace le texte original par sa traduction. Si une clé n'a pas de traduction, le texte original est utilisé par défaut.
Annoter le texte pour la traduction
Pas besoin d'ajouter ces clés à la main : le CLI peut les générer automatiquement. Voir Utiliser le CLI ci-dessous.
Pour marquer du texte comme traduisible, ajoutez un commentaire #key après l'élément concerné. Trois types de contenu peuvent être annotés :
Les textes narratifs :
"Le café sent très bon." #intro
Les dialogues :
barista: "Bienvenue ! Qu'est-ce que je te sers ?" #welcome
Les options de choix :
choice
Expresso #opt-espresso
Latté #opt-latte
Les clés peuvent contenir des lettres, chiffres, tirets et underscores, par exemple #intro, #opt-espresso ou #chapitre2_salut.
Caractères dièse littéraux
Si vous avez besoin d'un # littéral dans votre texte, utilisez ## (doublé) ou \# (échappé) :
Ce texte contient un ##hashtag littéral.
barista: "Utilisez \#échappé pour un dièse littéral."
Exemple complet
Voici un script complet avec des clés de localisation :
character barista
name: Alex
beat Menu
"The cafe smells wonderful." #intro
barista: "Welcome! What can I get you?" #welcome
choice
Espresso #opt-espresso
barista: One espresso! #espresso-reply
Latte #opt-latte
barista: One latte! #latte-reply
Peur de perdre en lisibilité ? Dans l'extension VS Code, appuyez sur
Cmd+Shift+H(Mac) ouCtrl+Shift+H(Windows/Linux), ou via la palette de commandes, pour masquer toutes les clés de localisation afin qu'elles ne nuisent en aucun cas à la lisibilité de votre script. L'Atelier sur ce site propose aussi cette option.
Les fichiers de traduction
Les fichiers de traduction suivent la convention de nommage Fichier.lang.lor, par exemple CoffeeShop.fr.lor pour le français ou CoffeeShop.de.lor pour l'allemand.
Chaque entrée dans un fichier de traduction se compose de :
- Une
#keysuivie de//et du texte original en référence - Le texte traduit sur la ligne suivante
Voici la traduction française de l'exemple précédent (CoffeeShop.fr.lor) :
#intro // "The cafe smells wonderful."
Le café sent très bon.
#welcome // "Welcome! What can I get you?"
Bienvenue ! Qu'est-ce que je te sers ?
#opt-espresso // Espresso
Expresso
#espresso-reply // One espresso!
Un expresso !
#opt-latte // Latte
Latté
#latte-reply // One latte!
Un latté
Le commentaire // sur chaque ligne de clé est purement indicatif. L'interpréteur l'ignore. Les traducteurs peuvent voir le texte original sans avoir à ouvrir le fichier source.
Formats de traduction alternatifs
Par défaut Loreline ne cherche que les fichiers .<locale>.lor, mais quatre autres formats sont supportés comme remplaçants directs : GNU gettext PO (.po), XLIFF 1.2 / 2.x (.xliff, .xlf), CSV (.csv) et TSV (.tsv). Pratique quand votre équipe utilise déjà un outil de traduction standard, quand vous déléguez la traduction à des gens qui s'attendent à l'un de ces formats, ou quand vous voulez manipuler les données dans un tableur.
Chaque format représente les mêmes six entrées que l'exemple CoffeeShop.fr.lor ci-dessus. Choisissez celui qui correspond le mieux à votre workflow.
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Language: fr\n"
msgctxt "intro"
msgid "The cafe smells wonderful."
msgstr "Le café sent très bon."
msgctxt "welcome"
msgid "Welcome! What can I get you?"
msgstr "Bienvenue ! Qu'est-ce que je te sers ?"
msgctxt "opt-espresso"
msgid "Espresso"
msgstr "Expresso"
msgctxt "espresso-reply"
msgid "One espresso!"
msgstr "Un expresso !"
msgctxt "opt-latte"
msgid "Latte"
msgstr "Latté"
msgctxt "latte-reply"
msgid "One latte!"
msgstr "Un latté"
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" target-language="fr" datatype="plaintext">
<body>
<trans-unit id="intro">
<source>The cafe smells wonderful.</source>
<target>Le café sent très bon.</target>
</trans-unit>
<trans-unit id="welcome">
<source>Welcome! What can I get you?</source>
<target>Bienvenue ! Qu'est-ce que je te sers ?</target>
</trans-unit>
<trans-unit id="opt-espresso">
<source>Espresso</source>
<target>Expresso</target>
</trans-unit>
<trans-unit id="espresso-reply">
<source>One espresso!</source>
<target>Un expresso !</target>
</trans-unit>
<trans-unit id="opt-latte">
<source>Latte</source>
<target>Latté</target>
</trans-unit>
<trans-unit id="latte-reply">
<source>One latte!</source>
<target>Un latté</target>
</trans-unit>
</body>
</file>
</xliff>
key,source,fr
intro,The cafe smells wonderful.,Le café sent très bon.
welcome,Welcome! What can I get you?,Bienvenue ! Qu'est-ce que je te sers ?
opt-espresso,Espresso,Expresso
espresso-reply,One espresso!,Un expresso !
opt-latte,Latte,Latté
latte-reply,One latte!,Un latté
key source fr
intro The cafe smells wonderful. Le café sent très bon.
welcome Welcome! What can I get you? Bienvenue ! Qu'est-ce que je te sers ?
opt-espresso Espresso Expresso
espresso-reply One espresso! Un expresso !
opt-latte Latte Latté
latte-reply One latte! Un latté
#intro // "The cafe smells wonderful."
Le café sent très bon.
#welcome // "Welcome! What can I get you?"
Bienvenue ! Qu'est-ce que je te sers ?
#opt-espresso // Espresso
Expresso
#espresso-reply // One espresso!
Un expresso !
#opt-latte // Latte
Latté
#latte-reply // One latte!
Un latté
Règle de validité
Tout ce que vous mettez dans msgstr, <target> ou la colonne de locale est réécrit dans un corps .lor synthétisé puis re-parsé par Loreline. La chaîne doit donc être un contenu de corps Loreline valide, avec les mêmes règles que le texte d'un fichier source .lor :
$varet${expr}déclenchent l'interpolation ; utilisez$$pour un$littéral.<tag>et</tag>sont du markup ; utilisez\<pour un<littéral.\n,\t,\r,\\sont les séquences d'échappement habituelles pour saut de ligne, tabulation, retour chariot et antislash.
Les traducteurs qui veulent utiliser ces fonctionnalités écrivent simplement la syntaxe Loreline correspondante dans leur chaîne traduite et tout fait l'aller-retour intact. Ceux qui veulent des caractères littéraux appliquent les mêmes échappements que dans le .lor source.
Activer les formats
Les formats alternatifs sont opt-in à l'exécution, pour que les projets qui n'utilisent que des traductions .lor ne paient pas le coût de requêtes de fichiers supplémentaires pour des formats qu'ils n'utilisent pas. Activez chaque famille que vous voulez avant d'appeler loadLocale :
Loreline.translationFormat('po', true);
Loreline.translationFormat('xliff', true);
Loreline.translationFormat('csv', true);
Le switch "po" couvre .po. Le switch "xliff" couvre à la fois .xliff et .xlf. Le switch "csv" couvre à la fois .csv et .tsv. Les noms inconnus sont acceptés silencieusement pour la compatibilité future.
Fichiers multi-locales
XLIFF et CSV/TSV peuvent transporter plusieurs locales dans un seul fichier. Quand le nom du fichier n'a pas d'infixe .<locale> (donc CoffeeShop.xliff ou CoffeeShop.csv plutôt que CoffeeShop.fr.xliff), Loreline le trouve quand même et sélectionne uniquement les entrées qui correspondent à la locale demandée.
Pour XLIFF, c'est l'attribut target-language de chaque bloc <file> qui décide. Pour CSV/TSV, c'est l'en-tête de la colonne de locale : un fichier avec les colonnes key,source,fr,de,es sert les trois langues depuis un seul endroit.
Traduire les imports
Chaque fichier .lor peut avoir son propre fichier de traduction. Si votre histoire est répartie sur plusieurs fichiers via import, chaque fichier importé peut avoir son propre .lang.lor à côté de lui.
Par exemple, si votre histoire ressemble à ceci :
// CoffeeShop.lor
import characters
beat Menu
"The cafe smells wonderful." #intro
barista: "Welcome!" #welcome
// characters.lor
character barista
name: Alex
greeting: "Hi there!" #barista-greet
Vous pouvez traduire chaque fichier indépendamment :
// CoffeeShop.fr.lor
#intro // "The cafe smells wonderful."
Le café sent très bon.
#welcome // "Welcome!"
Bienvenue !
// characters.fr.lor
#barista-greet // "Hi there!"
Salut !
À l'exécution, Loreline parcourt tous les fichiers accessibles depuis le script racine et charge le fichier de traduction correspondant pour chacun. Les fichiers sans traduction sont ignorés, et le texte original est utilisé en repli.
Si deux fichiers utilisent par hasard la même #key (par exemple #intro à la fois dans CoffeeShop.lor et dans un prologue.lor importé), chaque fichier de traduction contrôle ses propres lignes indépendamment. Pas besoin de s'inquiéter des collisions de clés entre fichiers importés.
Utiliser le CLI
La commande loreline translate automatise le processus de localisation. Elle peut générer des clés, créer des fichiers de traduction et nettoyer le tout.
Générer des clés automatiquement
Si votre script n'a pas encore de clés #key, le CLI peut les ajouter automatiquement :
loreline translate CoffeeShop.lor --auto-ids
Cette commande parcourt tous les textes, dialogues et options de choix du script et insère des clés aléatoires (comme #a7k2m) sur chaque ligne qui n'en a pas encore. Vos commentaires et votre mise en forme sont préservés.
Générer un fichier de traduction
Pour créer ou mettre à jour un fichier de traduction pour une langue donnée :
loreline translate CoffeeShop.lor --lang fr
Cela crée CoffeeShop.fr.lor avec toutes les entrées traduisibles. Si le fichier existe déjà, les traductions existantes sont conservées et les nouvelles entrées sont ajoutées.
Pour générer l'un des formats alternatifs à la place, passez --format :
loreline translate CoffeeShop.lor --lang fr --format po
loreline translate CoffeeShop.lor --lang fr --format xliff
loreline translate CoffeeShop.lor --lang fr --format csv
loreline translate CoffeeShop.lor --lang fr --format tsv
Le flag accepte lor (par défaut), po, xliff, csv et tsv. La logique de merge est identique pour tous les formats : relancer la commande sur un fichier de traduction existant conserve les éditions de l'utilisateur, ajoute les nouvelles entrées venues du script source, et met à jour la référence du texte original quand il a changé.
Supprimer les clés
Pour retirer tous les commentaires #key d'un fichier source :
loreline translate CoffeeShop.lor --clear
Workflow type
# 1. Ajouter des clés au script
loreline translate CoffeeShop.lor --auto-ids
# 2. Générer un fichier de traduction français
# (ou utilisez `--format po` / `--format xliff` / `--format csv` pour un format alternatif)
loreline translate CoffeeShop.lor --lang fr
# 3. Éditer CoffeeShop.fr.lor, remplacer chaque placeholder par le texte français
# 4. Ajouter d'autres langues au besoin
loreline translate CoffeeShop.lor --lang de
loreline translate CoffeeShop.lor --lang es
Quand vous ajoutez du texte à votre script, relancez --auto-ids pour annoter les nouvelles lignes, puis --lang fr pour mettre à jour le fichier de traduction. Les traductions existantes ne seront pas écrasées.
Interpolation dans les traductions
Les traductions fonctionnent de manière transparente avec l'interpolation de texte. Les variables comme $count ou ${expression} sont préservées dans le fichier de traduction et évaluées à l'exécution :
Script source :
state
count: 3
beat Start
You have $count items #item-count
barista: You ordered $count drinks #drink-reply
Fichier de traduction :
#item-count // You have $count items
Vous avez $count articles
#drink-reply // You ordered $count drinks
Vous avez commandé $count boissons
msgctxt "item-count"
msgid "You have $count items"
msgstr "Vous avez $count articles"
msgctxt "drink-reply"
msgid "You ordered $count drinks"
msgstr "Vous avez commandé $count boissons"
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" target-language="fr" datatype="plaintext">
<body>
<trans-unit id="item-count">
<source>You have $count items</source>
<target>Vous avez $count articles</target>
</trans-unit>
<trans-unit id="drink-reply">
<source>You ordered $count drinks</source>
<target>Vous avez commandé $count boissons</target>
</trans-unit>
</body>
</file>
</xliff>
key,source,fr
item-count,You have $count items,Vous avez $count articles
drink-reply,You ordered $count drinks,Vous avez commandé $count boissons
key source fr
item-count You have $count items Vous avez $count articles
drink-reply You ordered $count drinks Vous avez commandé $count boissons
À l'exécution, $count est remplacé par sa valeur courante (3), ce qui produit « Vous avez 3 articles ».
Charger les traductions à l'exécution
Pour utiliser les traductions dans votre application, appelez loadLocale() avec la langue voulue. Loreline parcourt les imports du script et charge chaque fichier .lang.lor disponible, puis vous renvoie une table de traductions unique à passer à l'interpréteur. Voici un exemple en JavaScript :
import { Loreline } from 'loreline';
// Activer les formats de traduction alternatifs (ignorez ceci si vous n'utilisez que des fichiers .fr.lor) :
Loreline.translationFormat('po', true);
// Parser d'abord le script principal pour que loadLocale puisse parcourir les imports
const script = Loreline.parse(sourceContent, 'CoffeeShop.lor', handleFile);
// Charger toutes les traductions françaises à travers l'arbre des imports
const translations = await new Promise(resolve =>
Loreline.loadLocale('fr', script, 'CoffeeShop.lor', handleFile, resolve));
// Jouer avec les traductions françaises
Loreline.play(script, onDialogue, onChoice, onFinish, null, {
translations: translations
});
Le même callback handleFile que vous passez à parse() est réutilisé ici pour lire chaque fichier de traduction. Les fichiers de traduction absents sont ignorés, donc les traductions partielles fonctionnent sans souci.
Attraper les erreurs de traduction
Un fichier de traduction absent n'est pas une erreur : loadLocale le saute silencieusement et le runtime retombe sur le texte source. Un fichier de traduction qui existe mais qui est invalide (du XML cassé dans un .xliff, une chaîne entre guillemets non fermée dans un .po, un .csv sans colonne key dans l'en-tête, un .lor avec une erreur de syntaxe) est une autre histoire et fait surface sous la forme d'une loreline.Error.
En mode synchrone (sans callback), loadLocale lève l'erreur et vous l'attrapez avec un try/catch. En mode asynchrone (avec callback), l'appel ne lève jamais : le callback est invoqué avec null et vous lisez Loreline.lastError() pour savoir ce qui a échoué :
Loreline.loadLocale('fr', script, 'CoffeeShop.lor', handleFile, (translations) => {
if (translations == null) {
console.error('Échec du chargement des traductions :', Loreline.lastError().message);
return;
}
// ... utiliser les traductions
});
Les mêmes patterns d'opt-in, de --format et de gestion d'erreur s'appliquent dans tous les langages supportés, avec le nom idiomatique propre à chacun (TranslationFormat et LastError en C#, translation_format et last_error en Python et Lua, etc.).
Le même principe s'applique dans tous les langages supportés. Consultez les guides d'intégration pour les détails en C#, C++, Python, Lua, Haxe et Godot.