Localisation des blocs
Ceci est un autre exemple d’utilisation du LLM pour produire la traduction de chaînes naturelles avec un DSL intégré, de manière similaire au guide Traduction de documentation.
MakeCode utilise un microformat pour définir la forme des blocs de codage. Lors de la traduction des chaînes de format, il est crucial de conserver les propriétés des blocs, comme le nombre d’arguments, leurs types et l’ordre des arguments.
Ne cassez pas les blocs !
Section intitulée « Ne cassez pas les blocs ! »Les chaînes de localisation pour la bibliothèque buzzer sont :
{ "jacdac.BuzzerCmd.PlayNote": "Play a note at the given frequency and volume.", "jacdac.BuzzerCmd.PlayTone": "Play a PWM tone with given period and duty for given duration.\nThe duty is scaled down with `volume` register.\nTo play tone at frequency `F` Hz and volume `V` (in `0..1`) you will want\nto send `P = 1000000 / F` and `D = P * V / 2`.\n* ```\nconst [period, duty, duration] = jdunpack<[number, number, number]>(buf, \"u16 u16 u16\")\n```", "jacdac.BuzzerReg.Volume": "Read-write ratio u0.8 (uint8_t). The volume (duty cycle) of the buzzer.\n* ```\nconst [volume] = jdunpack<[number]>(buf, \"u0.8\")\n```", "modules.BuzzerClient.playTone|block": "play %music tone|at %note|for %duration", "{id:category}Jacdac": "Jacdac", "{id:category}Modules": "Modules", "{id:group}Music": "Music"}
Par exemple, la chaîne pour le bloc jouer une note du buzzer Jacdac contient une référence à des variables (%music
) qui doivent être conservées dans la chaîne traduite.
{ ... "modules.BuzzerClient.playTone|block": "play %music tone|at %note|for %duration", ...}
et Bing Traduction nous donne la traduction suivante
%Musikton|bei %Note|für %Dauer abspielen
Comme on peut le voir, Bing a traduit le nom de la variable %variable
ce qui cassera la définition du bloc.
La traduction GenAIScript est correcte.
spiele %music Ton|bei %note|für %duration
Si vous regardez attentivement dans le code source du script, vous trouverez dans l’invite des instructions pour manipuler correctement les variables.
$`...- Every variable name is prefixed with a '%' or a '$', like %foo or $bar.- Do NOT translate variable names....`;
Format de données personnalisé
Section intitulée « Format de données personnalisé »Un autre défi avec les traductions est que la chaîne localisée contient souvent des caractères échappés qui cassent des formats comme JSON ou YAML. Par conséquent, nous utilisons un format simple personnalisé key=value
pour encoder les chaînes, afin d’éviter les problèmes d’encodage. Nous utilisons la fonctionnalité defFileMerge
pour convertir le fichier clé-valeur analysé et les fusionner avec les traductions existantes.
// register a callback to custom merge filesdefFileMerge((filename, label, before, generated) => { if (!filename.endsWith("-strings.json")) return undefined;
// load existing translatins const olds = JSON.parse(before || "{}");
// parse out key-value lines into a JavaScript record object const news = generated .split(/\n/g) .map((line) => /^([^=]+)=(.+)$/.exec(line)) .filter((m) => !!m) .reduce((o, m) => { const [, key, value] = m; // assign o[key] = value; return o; }, {});
// merge new translations with olds ones Object.assign(olds, news);
// return stringified json return JSON.stringify(olds, null, 2);});
Paramétrage pour l’automatisation
Section intitulée « Paramétrage pour l’automatisation »Le code de langue langCode
est récupéré depuis les variables env.vars
ou par défaut à de
.
const langCode = env.vars.lang || "de";
Cette technique permet de reconfigurer ces variables depuis la ligne de commande en utilisant l’argument --vars lang=fr
.
Le script complet est montré ci-dessous.
script({ title: "MakeCode Blocks Localization", description: "Translate block strings that define blocks in MakeCode", group: "MakeCode", temperature: 0,})
// language parameterizationconst langCode = (env.vars.lang || "de") + ""
// given a language code, refer to the full name to help the LLMconst langName = { fr: "French", "es-ES": "Spanish", de: "German", sr: "Serbian", vi: "Vietnamese", it: "Italian",}[langCode]if (!langName) cancel("unknown language")
// assume we've been pointed at the .json fileconst file = env.files[0]if (!file) cancel("no strings file found")
const { filename, content } = fileconst dir = path.dirname(filename)
// read the stings, which are stored as a JSON recordconst strings = JSON.parse(content)
// find the existing translation and remove existing translationsconst trfn = path.join(dir, langCode, path.basename(filename))const translated = await workspace.readJSON(trfn)if (translated) for (const k of Object.keys(strings)) if (translated[k]) delete strings[k]
// shortcut: all translation is doneif (Object.keys(strings).length === 0) cancel(`no strings to translate`)
// use simple .env format key=value formatconst contentToTranslate = Object.entries(strings) .map(([k, v]) => `${k}=${v.replace(/(\.|\n).*/s, ".").trim()}`) .join("\n")
// the prompt engineering piece$`## Role
You are an expert at Computer Science education.You are an expert TypeScript coder.You are an expert at Microsoft MakeCode.You are an expert ${langName} translator.
## Task
Translate the content of ORIGINAL to ${langName} (lang-iso '${langCode}').The ORIGINAL files are formatted with one key and localized value pair per line as follows.
\`\`\`key1=en value1key2=en value2...\`\`\`
Write the translation to file ${trfn} formatted with one key and localized value pair per line as follows (DO NOT use JSON).
\`\`\` file="${trfn}"key1=${langCode} value1key2=${langCode} value2...\`\`\`
## Recommendations
- DO NOT translate the keys- DO translate the values to ${langName} (lang-iso '${langCode}')- DO NOT use foul language.
### Block Strings
The value for keys ending with "|block" are MakeCode block strings (https://makecode.com/defining-blocks)and should be translated following these rules:
- Every variable name is prefixed with a '%' or a '$', like %foo or $bar.- Do NOT translate variable names.- Some variable names have a value, like '%foo=toggleOnOff'. The value should be NOT translated.- All variables in the original string should be in the translated string.- Make sure to translate '\\%' to '\\%' and '\\$' to '\\$' if they are not variables.- Event string starts with 'on', like 'on pressed'. Interpret 'on' as 'when' when, like 'when pressed', when translating.- The translations of "...|block" string should be short.
`
// add to prompt contextdef( "ORIGINAL", { filename, content: contentToTranslate, }, { language: "txt" })
// merge the translations with the old one and marshal yaml to jsondefFileMerge((filename, label, before, generated) => { if (!filename.endsWith("-strings.json")) return undefined
// existing translatins const olds = JSON.parse(before || "{}")
// parse out kv const news = generated .split(/\n/g) .map((line) => /^([^=]+)=(.+)$/.exec(line)) .filter((m) => !!m) .reduce((o, m) => { const [, key, value] = m // assign o[key] = value return o }, {})
// merge new translations with olds ones Object.assign(olds, news)
// return stringified json return JSON.stringify(olds, null, 2)})
Le résultat de ce script peut être consulté dans cette pull request.