{"id":193183,"date":"2025-08-17T20:30:07","date_gmt":"2025-08-17T18:30:07","guid":{"rendered":"https:\/\/fivemx.com\/?p=193183"},"modified":"2025-12-23T13:27:50","modified_gmt":"2025-12-23T12:27:50","slug":"traduzir-scripts-fivem-com-ia","status":"publish","type":"post","link":"https:\/\/fivemx.com\/pt\/translate-fivem-scripts-with-ai\/","title":{"rendered":"Como traduzir scripts usando IA (Guia FiveM)"},"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","protected":false},"excerpt":{"rendered":"<p>Audience: FiveM server owners, scripters, and maintainers who want high\u2011quality translations without breaking placeholders or UI. TL;DR Why translate your FiveM scripts If you need a foundational refresher on structure and best practices, see: How To Translate FiveM Scripts (The Right Way). Architecture: the right way to localize Goal: Zero user\u2011visible strings inside gameplay code. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":193184,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2883],"tags":[],"class_list":["post-193183","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-buying-testing-maintaining-fivem-scripts"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts\/193183","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/comments?post=193183"}],"version-history":[{"count":0,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts\/193183\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/media\/193184"}],"wp:attachment":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/media?parent=193183"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/categories?post=193183"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/tags?post=193183"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}