{"id":193183,"date":"2025-08-17T20:30:07","date_gmt":"2025-08-17T18:30:07","guid":{"rendered":"https:\/\/fivemx.com\/?p=193183"},"modified":"2026-07-23T09:28:35","modified_gmt":"2026-07-23T07:28:35","slug":"traduire-les-scripts-fivem-avec-lia","status":"publish","type":"post","link":"https:\/\/fivemx.com\/fr\/traduire-les-scripts-fivem-avec-lia\/","title":{"rendered":"Comment traduire FiveM Scripts en utilisant l'IA"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>Audience:<\/strong> FiveM server owners, scripters, and maintainers who want high\u2011quality translations without breaking placeholders or UI.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">TL;DR<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Centralize all text in locale files (JSON or Lua tables). Never hard\u2011code strings in game logic.<\/li>\n\n\n\n<li>Protect placeholders (e.g., <code>%s<\/code>, <code>%d<\/code>, <code>%{name}<\/code>, <code>{0}<\/code>, <code>~r~<\/code>, <code>^1<\/code>) during translation.<\/li>\n\n\n\n<li>Use AI for first\u2011pass translation + glossary + automated checks \u2192 quick human review \u2192 ship.<\/li>\n\n\n\n<li>Keep a single source of truth (usually English), diff for changes, and regenerate only changed keys.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Why translate your FiveM scripts<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Accessibility &amp; growth:<\/strong> Localized servers attract more players and keep them engaged.<\/li>\n\n\n\n<li><strong>Professionalism:<\/strong> Consistent terminology across commands, UIs, and error messages.<\/li>\n\n\n\n<li><strong>Contributor\u2011friendly:<\/strong> Clear locale structure invites community PRs.<\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">If you need a foundational refresher on structure and best practices, see: <strong><a href=\"https:\/\/fivemx.com\/fivem-scripts-translation\/\">How To Translate FiveM Scripts (The Right Way)<\/a><\/strong>.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Architecture: the right way to localize<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Goal:<\/strong> Zero user\u2011visible strings inside gameplay code. Route everything through a locale layer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Recommended resource layout<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">my_resource\/\n\u251c\u2500 fxmanifest.lua\n\u251c\u2500 locales\/\n\u2502 \u251c\u2500 en.json # source language (single source of truth)\n\u2502 \u251c\u2500 de.json # translated (generated\/edited)\n\u2502 \u251c\u2500 es.json # translated (generated\/edited)\n\u2502 \u2514\u2500 qa.rules.json # optional: placeholder whitelist &amp; checks\n\u251c\u2500 client\/\n\u2502 \u2514\u2500 main.lua\n\u251c\u2500 server\/\n\u2502 \u2514\u2500 main.lua\n\u2514\u2500 shared\/\n \u2514\u2500 i18n.lua # translation helper\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>fxmanifest.lua (minimal example)<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">fx_version 'cerulean'\ngame 'gta5'\nlua54 'yes'\n\nshared_scripts {\n 'shared\/i18n.lua',\n}\n\nfiles {\n 'locales\/*.json'\n}\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>shared\/i18n.lua<\/strong> (lightweight loader + placeholder substitution)<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">local LOCALE = GetConvar('my_locale', 'en')\nlocal CACHE = {}\n\nlocal function loadJSON(path)\n local file = io.open(path, 'r')\n if not file then return {} end\n local content = file:read('*a')\n file:close()\n local ok, data = pcall(function() return json.decode(content) end)\n return ok and data or {}\nend\n\nlocal function readLocale(lang)\n if CACHE[lang] then return CACHE[lang] end\n local file = ('locales\/%s.json'):format(lang)\n local dict = loadJSON(file)\n CACHE[lang] = dict\n return dict\nend\n\nlocal function interpolate(str, vars)\n if not vars then return str end\n for k, v in pairs(vars) do\n str = str:gsub('%%{'..k..'}', tostring(v)) -- %{name}\n end\n return str\nend\n\nfunction _U(key, vars)\n local dict = readLocale(LOCALE)\n local src = dict[key]\n if not src then\n -- fallback to English if missing\n src = readLocale('en')[key] or key\n end\n return interpolate(src, vars)\nend\n\nexports('Translate', _U)\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Usage in client\/server code<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- Client\nlib.notify({\n title = _U('notify_title'),\n description = _U('welcome_player', { name = GetPlayerName(PlayerId()) }),\n})\n\n-- Server\nprint(('[MyRes] %s'):format(_U('server_started')))\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>locales\/en.json (source)<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n \"notify_title\": \"Server Message\",\n \"welcome_player\": \"Welcome, %{name}!\",\n \"server_started\": \"Server module is ready.\",\n \"no_permission\": \"You do not have permission.\",\n \"items_remaining\": \"%{count} items remaining\"\n}\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">AI translation workflow (fast and safe)<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Extract &amp; freeze source<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Keep <strong>English<\/strong> (or your source) as <code>locales\/en.json<\/code>.<\/li>\n\n\n\n<li>Enforce key naming: <code>domain.action.subject<\/code> (e.g., <code>inventory.drop.confirm<\/code>).<\/li>\n<\/ul>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>Create\/extend a glossary<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CSV or JSON map of canonical terms \u2192 target terms. Example:<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">source,target\nEMS,Rettungsdienst\nPD,Polizei\nMechanic,Mechaniker\n\n<\/pre>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li><strong>Protect placeholders &amp; markup<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Placeholders: <code>%{name}<\/code>, <code>%s<\/code>, <code>%d<\/code>, <code>{0}<\/code><\/li>\n\n\n\n<li>FiveM color codes: <code>~r~<\/code>, <code>~g~<\/code>, <code>~s~<\/code>; chat codes: <code>^1<\/code>, <code>^2<\/code><\/li>\n\n\n\n<li>NUI\/HTML tags: <code>&lt;b><\/code>, <code>&lt;span><\/code> \u2026<\/li>\n<\/ul>\n\n\n\n<ol start=\"4\" class=\"wp-block-list\">\n<li><strong>Translate via API<\/strong> (batch)<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Send <strong>values<\/strong> only, keep <strong>keys<\/strong> unchanged.<\/li>\n\n\n\n<li>Supply <strong>glossary<\/strong> and <strong>style<\/strong> (tone) to the model\/engine.<\/li>\n<\/ul>\n\n\n\n<ol start=\"5\" class=\"wp-block-list\">\n<li><strong>Automated QA<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Validate JSON.<\/li>\n\n\n\n<li>Verify <strong>placeholder parity<\/strong> (every placeholder in source exists in target).<\/li>\n\n\n\n<li>Flag forbidden changes (e.g., altered color codes or added punctuation when disallowed).<\/li>\n<\/ul>\n\n\n\n<ol start=\"6\" class=\"wp-block-list\">\n<li><strong>Human spot\u2011check<\/strong> (5\u201310 minutes)<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Review commands, error messages, and long UI strings.<\/li>\n<\/ul>\n\n\n\n<ol start=\"7\" class=\"wp-block-list\">\n<li><strong>Ship &amp; iterate<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Keep a <strong>translation memory<\/strong> (previous outputs) to avoid re\u2011translating unchanged keys.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Guardrails: prompt &amp; rules that actually work<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>LLM prompt for JSON batch translation<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Task: Translate JSON values from English to &lt;TARGET_LANGUAGE> for a FiveM\/GTA RP context.\nRules:\n- KEEP KEYS UNCHANGED.\n- PRESERVE all placeholders exactly: %{var}, %s, %d, {0}, ~r~, ~g~, ^1, ^2, etc.\n- Keep capitalization and code-style tokens (commands, \/slash commands) unchanged.\n- Do not add quotes, extra punctuation, or change meaning.\n- Return ONLY valid JSON with the same structure.\n\nJSON to translate:\n&lt;PASTE en.json CONTENT HERE>\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Regex you can use in a QA script<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Placeholders: <code>%%{[A-Za-z0-9_]+}<\/code><\/li>\n\n\n\n<li>C printf: <code>%(?:d+$)?[sdif]<\/code><\/li>\n\n\n\n<li>Chat codes: <code>^d<\/code><\/li>\n\n\n\n<li>Tilde color codes: <code>~[rgbso]~<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Example: translate with DeepL (Node.js)<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Works great for one\u2011off jobs or CI.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>package.json (scripts)<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n \"type\": \"module\",\n \"scripts\": {\n \"i18n:translate:de\": \"node tools\/translate-deepl.js en de\",\n \"i18n:check\": \"node tools\/i18n-check.js\"\n }\n}\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>tools\/translate-deepl.js<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import fs from 'fs';\nimport path from 'path';\nimport assert from 'assert';\nimport fetch from 'node-fetch';\n\nconst [,, srcLang, dstLang] = process.argv;\nconst apiKey = process.env.DEEPL_API_KEY; \/\/ set in CI\/ENV\nassert(apiKey, 'DEEPL_API_KEY is required');\n\nconst src = JSON.parse(fs.readFileSync('locales\/en.json', 'utf8'));\nconst out = {};\n\nconst GLOSSARY = {\n 'EMS': 'Rettungsdienst',\n 'PD': 'Polizei',\n};\n\nfunction protect(str){\n \/\/ Replace placeholders with tokens DeepL won't alter\n return str\n .replace(\/%{([^}]+)}\/g, '\u27e6$1\u27e7')\n .replace(\/%s\/g, '\u27eaS\u27eb')\n .replace(\/%d\/g, '\u27eaD\u27eb');\n}\nfunction restore(str){\n return str\n .replace(\/\u27e6([^\u27e7]+)\u27e7\/g, '%{$1}')\n .replace(\/\u27eaS\u27eb\/g, '%s')\n .replace(\/\u27eaD\u27eb\/g, '%d');\n}\n\nasync function translate(text){\n const res = await fetch('https:\/\/api.deepl.com\/v2\/translate', {\n method: 'POST',\n headers: { 'Content-Type': 'application\/x-www-form-urlencoded' },\n body: new URLSearchParams({\n auth_key: apiKey,\n text: text,\n source_lang: srcLang.toUpperCase(),\n target_lang: dstLang.toUpperCase(),\n formality: 'prefer_more'\n })\n });\n const json = await res.json();\n if (!json.translations) throw new Error(JSON.stringify(json));\n return json.translations[0].text;\n}\n\nfor (const [k, v] of Object.entries(src)) {\n const protectedText = protect(v);\n \/\/ Glossary pre-pass (simple):\n let glossed = protectedText;\n for (const [from, to] of Object.entries(GLOSSARY)) {\n glossed = glossed.replace(new RegExp(`b${from}b`, 'g'), to);\n }\n \/\/ Translate\n \/\/ eslint-disable-next-line no-await-in-loop\n const tr = await translate(glossed);\n out[k] = restore(tr);\n}\n\nfs.writeFileSync(`locales\/${dstLang}.json`, JSON.stringify(out, null, 2));\nconsole.log(`Wrote locales\/${dstLang}.json`);\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>tools\/i18n-check.js<\/strong> (placeholder parity)<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import fs from 'fs';\n\nconst src = JSON.parse(fs.readFileSync('locales\/en.json', 'utf8'));\nconst dst = JSON.parse(fs.readFileSync('locales\/de.json', 'utf8'));\n\nconst reVar = \/%{[^}]+}\/g;\nconst reS = \/%s\/g;\nconst reD = \/%d\/g;\n\nlet ok = true;\nfor (const k of Object.keys(src)) {\n const a = (src[k].match(reVar)||[]).length === (dst[k]?.match(reVar)||[]).length;\n const b = (src[k].match(reS)||[]).length === (dst[k]?.match(reS)||[]).length;\n const c = (src[k].match(reD)||[]).length === (dst[k]?.match(reD)||[]).length;\n if (!(a &amp;&amp; b &amp;&amp; c)) {\n console.error('Placeholder mismatch for key:', k);\n ok = false;\n }\n}\nprocess.exit(ok ? 0 : 1);\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Using LLMs (OpenAI\/others) effectively<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Chunk by topic\/domain<\/strong> for better context (e.g., inventory, police, jobs).<\/li>\n\n\n\n<li>Provide <strong>short descriptions<\/strong> per group (two lines) to define tone and audience.<\/li>\n\n\n\n<li><strong>Few\u2011shot examples<\/strong>: 2\u20133 correctly translated pairs with placeholders improve consistency.<\/li>\n\n\n\n<li><strong>Retry policy<\/strong>: re-run only failed keys flagged by <code>i18n-check<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Few\u2011shot template (system + user)<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">System: You translate FiveM game UI strings for &lt;TARGET_LANGUAGE>.\n- Keep keys unchanged, preserve placeholders, keep tone concise.\n\nUser examples:\nEN: \"You have %{count} fines.\"\nDE: \"Du hast %{count} Strafzettel.\"\n\nEN: \"~r~Error:~s~ You lack permission.\"\nDE: \"~r~Fehler:~s~ Dir fehlt die Berechtigung.\"\n\nNow translate the following JSON values from English to &lt;TARGET_LANGUAGE>. Return valid JSON only:\n&lt;PASTE JSON HERE>\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">NUI (HTML\/JS) translations<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For browser UIs, a client\u2011side library is practical.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Recommended approach<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use a JSON bundle per language in <code>web\/locales\/&lt;lang>.json<\/code>.<\/li>\n\n\n\n<li>Load with your UI framework and expose a <code>t(key, vars)<\/code> helper.<\/li>\n\n\n\n<li>Keep the <strong>same keys<\/strong> as server locales to reduce cognitive load.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Minimal JS helper<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">const dict = await (await fetch(`\/locales\/${lang}.json`)).json();\nexport function t(key, vars){\n let s = dict[key] || key;\n for (const [k,v] of Object.entries(vars||{})) s = s.replace(`%{${k}}`, v);\n return s;\n}\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">ESX\/QBCore specifics<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Many ESX scripts ship <code>locales\/en.lua<\/code>, <code>locales\/de.lua<\/code> with a <code>_U<\/code> helper.<\/li>\n\n\n\n<li>If you use Lua tables for locales, keep <strong>one style<\/strong> across your repo. Mixing JSON and Lua for the same resource increases maintenance cost.<\/li>\n\n\n\n<li>QBCore often uses config\u2011driven messages. Migrate repeated strings to locale files to avoid divergence.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Lua table locale (if you prefer Lua over JSON)<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Locales = Locales or {}\nLocales['en'] = {\n no_permission = 'You do not have permission.',\n welcome_player = 'Welcome, %{name}!'\n}\nLocales['de'] = {\n no_permission = 'Du hast keine Berechtigung.',\n welcome_player = 'Willkommen, %{name}!'\n}\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Quality gates before you ship<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>JSON\/Lua parse check<\/strong> in CI.<\/li>\n\n\n\n<li><strong>Placeholder parity<\/strong> (regex checks as shown).<\/li>\n\n\n\n<li><strong>Forbidden changes<\/strong>: disallow edits to <code>\/commands<\/code>, keybind letters, color\/chat codes.<\/li>\n\n\n\n<li><strong>Length deltas<\/strong>: flag +40% growth for UI buttons; may break layout.<\/li>\n\n\n\n<li><strong>Smoke test<\/strong>: spin up your server and spot\u2011check critical flows.<\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">New to setting up a server for testing? Follow this starter: <strong><a href=\"https:\/\/fivemx.com\/how-to-create-a-fivem-server\/\">How To Create a FiveM Server<\/a><\/strong>.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Maintenance strategy<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Treat <code>en.json<\/code> as <strong>source of truth<\/strong>; create a CI job that diffs <code>en.json<\/code> and updates only changed keys in targets.<\/li>\n\n\n\n<li>Keep a <code>CHANGELOG.i18n.md<\/code> for translators.<\/li>\n\n\n\n<li>Encourage community to contribute via PRs; document your <strong>style guide<\/strong> and <strong>glossary<\/strong> in <code>\/docs\/i18n.md<\/code>.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Common pitfalls (and fixes)<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Broken placeholders<\/strong> \u2192 Use automated checks and protection tokens.<\/li>\n\n\n\n<li><strong>Inconsistent terminology<\/strong> \u2192 Maintain a glossary and enforce it in prompts and pre\u2011processing.<\/li>\n\n\n\n<li><strong>Mixed locales in code<\/strong> \u2192 Fail CI if strings are detected outside <code>locales\/<\/code>.<\/li>\n\n\n\n<li><strong>RTL languages<\/strong> \u2192 Ensure your NUI CSS sets <code>direction: rtl;<\/code> and uses fonts with RTL support.<\/li>\n\n\n\n<li><strong>Casing &amp; punctuation drift<\/strong> \u2192 Instruct AI explicitly, and run a linter to normalize punctuation.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">External resources<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>DeepL API<\/strong> \u2014 developer docs: <a href=\"https:\/\/www.deepl.com\/docs-api\" target=\"_blank\" rel=\"noopener\">https:\/\/www.deepl.com\/docs-api<\/a><\/li>\n\n\n\n<li><strong>Google Cloud Translation<\/strong> \u2014 docs &amp; best practices: <a href=\"https:\/\/cloud.google.com\/translate\/docs\" target=\"_blank\" rel=\"noopener\">https:\/\/cloud.google.com\/translate\/docs<\/a><\/li>\n\n\n\n<li><strong>FiveM Resource Manifest (fxmanifest.lua)<\/strong> \u2014 reference: <a href=\"https:\/\/docs.fivem.net\/docs\/scripting-reference\/resource-manifest\/resource-manifest\/\" target=\"_blank\" rel=\"noopener\">https:\/\/docs.fivem.net\/docs\/scripting-reference\/resource-manifest\/resource-manifest\/<\/a><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Internal resources (related reading)<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>How To Translate FiveM Scripts (The Right Way)<\/strong> \u2014 workflow &amp; patterns: <a href=\"https:\/\/fivemx.com\/fivem-scripts-translation\/\">https:\/\/fivemx.com\/fivem-scripts-translation\/<\/a><\/li>\n\n\n\n<li><strong>How To Create a FiveM Server<\/strong> \u2014 spin up a test bed for QA: <a href=\"https:\/\/fivemx.com\/how-to-create-a-fivem-server\/\">https:\/\/fivemx.com\/how-to-create-a-fivem-server\/<\/a><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Copy\u2011paste checklists<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pre\u2011translation<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>All strings centralized in <code>locales\/en.json<\/code> (or Lua table)<\/li>\n\n\n\n<li>Keys follow a naming convention<\/li>\n\n\n\n<li>Glossary prepared<\/li>\n\n\n\n<li>Placeholders audited<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Batch translate with glossary<\/li>\n\n\n\n<li>Save output to <code>locales\/&lt;lang>.json<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>QA<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>JSON\/Lua valid<\/li>\n\n\n\n<li>Placeholder parity OK<\/li>\n\n\n\n<li>Forbidden tokens unchanged<\/li>\n\n\n\n<li>Length deltas acceptable<\/li>\n\n\n\n<li>Human spot\u2011check done<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ship<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CI green<\/li>\n\n\n\n<li>Changelog updated<\/li>\n\n\n\n<li>Invite community feedback<\/li>\n<\/ul>\n\n\n<section class=\"fivemx-ai-cluster-links\" aria-label=\"AI for FiveM next steps\">\n<h2>AI for FiveM next steps<\/h2>\n<ul>\n  <li><a href=\"https:\/\/fivemx.com\/using-ai-to-improve-fivem-server\/\">How to use AI with FiveM<\/a><\/li>\n  <li><a href=\"https:\/\/fivemx.com\/write-fivem-scripts-using-ai\/\">Write FiveM scripts using AI<\/a><\/li>\n  <li><a href=\"https:\/\/fivemx.com\/best-chatgpt-prompts-for-fivem\/\">ChatGPT prompts for FiveM<\/a><\/li>\n  <li>Translate FiveM scripts with AI<\/li>\n  <li><a href=\"https:\/\/fivemx.com\/awesome-tools-for-fivem-development\/\">FiveM development tools<\/a><\/li>\n  <li><a href=\"https:\/\/fivemx.com\/\">FiveM scripts<\/a>, <a href=\"https:\/\/fivemx.com\/esx-scripts\/\">ESX scripts<\/a>, <a href=\"https:\/\/fivemx.com\/qbcore-scripts\/\">QBCore scripts<\/a>, and <a href=\"https:\/\/fivemx.com\/qbox-scripts\/\">QBOX scripts<\/a><\/li>\n<\/ul>\n<\/section>\n","protected":false},"excerpt":{"rendered":"<p>Utilisez l'IA pour traduire les cha\u00eenes de scripts FiveM, les menus et les \u00e9tiquettes de configuration tout en pr\u00e9servant les espaces r\u00e9serv\u00e9s, les noms d'\u00e9l\u00e9ments, les commandes et les noms d'\u00e9v\u00e9nements.<\/p>","protected":false},"author":1,"featured_media":193184,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2883],"tags":[3001,2884],"class_list":["post-193183","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-buying-testing-maintaining-fivem-scripts","tag-fivem-script","tag-maintain-fivem-scripts"],"_links":{"self":[{"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/posts\/193183","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/comments?post=193183"}],"version-history":[{"count":4,"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/posts\/193183\/revisions"}],"predecessor-version":[{"id":214132,"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/posts\/193183\/revisions\/214132"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/media\/193184"}],"wp:attachment":[{"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/media?parent=193183"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/categories?post=193183"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemx.com\/fr\/wp-json\/wp\/v2\/tags?post=193183"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}