Bugün 20% tasarruf edin Ödemede WELCOME kodunu kullanın. WELCOME

How To Translate Scripts Using AI (FiveM Guide)

Ücretsiz bir komut dosyasını mı test ediyorsunuz?

Hızlı kontroller için ücretsiz komut dosyaları uygundur. Üretim sunucuları için, çerçeveye ve kullanım senaryosuna göre tam sunucu paketlerini veya ücretli, bakımı yapılan komut dosyalarını karşılaştırın.

Kitle: FiveM server owners, scripters, and maintainers who want high‑quality translations without breaking placeholders or UI.


Özetle;

  • Centralize all text in locale files (JSON or Lua tables). Never hard‑code strings in game logic.
  • Protect placeholders (e.g., %s, %d, %{name}, {0}, ~r~, ^1) during translation.
  • Use AI for first‑pass translation + glossary + automated checks → quick human review → ship.
  • Keep a single source of truth (usually English), diff for changes, and regenerate only changed keys.

Why translate your FiveM scripts

  • Accessibility & growth: Localized servers attract more players and keep them engaged.
  • Profesyonellik: Consistent terminology across commands, UIs, and error messages.
  • Contributor‑friendly: Clear locale structure invites community PRs.

If you need a foundational refresher on structure and best practices, see: FiveM Scriptleri Nasıl Tercüme Edilir (Doğru Şekilde).


Architecture: the right way to localize

Amaç: Zero user‑visible strings inside gameplay code. Route everything through a locale layer.

Recommended resource layout

my_resource/
├─ fxmanifest.lua
├─ locales/
│  ├─ en.json        # source language (single source of truth)
│  ├─ de.json        # translated (generated/edited)
│  ├─ es.json        # translated (generated/edited)
│  └─ qa.rules.json  # optional: placeholder whitelist & checks
├─ client/
│  └─ main.lua
├─ server/
│  └─ main.lua
└─ shared/
   └─ i18n.lua       # translation helper

fxmanifest.lua (minimal example)

fx_version 'cerulean'
game 'gta5'
lua54 'yes'

shared_scripts {
  'shared/i18n.lua',
}

files {
  'locales/*.json'
}

shared/i18n.lua (lightweight loader + placeholder substitution)

local LOCALE = GetConvar('my_locale', 'en')
local CACHE = {}

local function loadJSON(path)
    local file = io.open(path, 'r')
    if not file then return {} end
    local content = file:read('*a')
    file:close()
    local ok, data = pcall(function() return json.decode(content) end)
    return ok and data or {}
end

local function readLocale(lang)
    if CACHE[lang] then return CACHE[lang] end
    local file = ('locales/%s.json'):format(lang)
    local dict = loadJSON(file)
    CACHE[lang] = dict
    return dict
end

local function interpolate(str, vars)
    if not vars then return str end
    for k, v in pairs(vars) do
        str = str:gsub('%%{'..k..'}', tostring(v)) -- %{name}
    end
    return str
end

function _U(key, vars)
    local dict = readLocale(LOCALE)
    local src  = dict[key]
    if not src then
        -- fallback to English if missing
        src = readLocale('en')[key] or key
    end
    return interpolate(src, vars)
end

exports('Translate', _U)

Usage in client/server code

-- Client
lib.notify({
  title = _U('notify_title'),
  description = _U('welcome_player', { name = GetPlayerName(PlayerId()) }),
})

-- Server
print(('[MyRes] %s'):format(_U('server_started')))

locales/en.json (source)

{
  "notify_title": "Server Message",
  "welcome_player": "Welcome, %{name}!",
  "server_started": "Server module is ready.",
  "no_permission": "You do not have permission.",
  "items_remaining": "%{count} items remaining"
}

AI translation workflow (fast and safe)

  1. Extract & freeze source
  • Kale İngilizce (or your source) as locales/en.json.
  • Enforce key naming: domain.action.subject (örneğin, inventory.drop.confirm).
  1. Create/extend a glossary
  • CSV or JSON map of canonical terms → target terms. Example:
source,target
EMS,Rettungsdienst
PD,Polizei
Mechanic,Mechaniker

  1. Protect placeholders & markup
  • Placeholders: %{name}, %s, %d, {0}
  • FiveM color codes: ~r~, ~g~, ~s~; chat codes: ^1, ^2
  • NUI/HTML tags: <b>, <span>
  1. Translate via API (batch)
  • Göndermek values only, keep keys unchanged.
  • Supply glossary Ve stil (tone) to the model/engine.
  1. Automated QA
  • Validate JSON.
  • Doğrulamak placeholder parity (every placeholder in source exists in target).
  • Flag forbidden changes (e.g., altered color codes or added punctuation when disallowed).
  1. Human spot‑check (5–10 minutes)
  • Review commands, error messages, and long UI strings.
  1. Ship & iterate
  • Keep a translation memory (previous outputs) to avoid re‑translating unchanged keys.

Guardrails: prompt & rules that actually work

LLM prompt for JSON batch translation

Task: Translate JSON values from English to <TARGET_LANGUAGE> for a FiveM/GTA RP context.
Rules:
- KEEP KEYS UNCHANGED.
- PRESERVE all placeholders exactly: %{var}, %s, %d, {0}, ~r~, ~g~, ^1, ^2, etc.
- Keep capitalization and code-style tokens (commands, /slash commands) unchanged.
- Do not add quotes, extra punctuation, or change meaning.
- Return ONLY valid JSON with the same structure.

JSON to translate:
<PASTE en.json CONTENT HERE>

Regex you can use in a QA script

  • Placeholders: %%\{[A-Za-z0-9_]+\}
  • C printf: %(?:\d+\$)?[sdif]
  • Chat codes: \^\d
  • Tilde color codes: ~[rgbso]~

Example: translate with DeepL (Node.js)

Works great for one‑off jobs or CI.

package.json (scripts)

{
  "type": "module",
  "scripts": {
    "i18n:translate:de": "node tools/translate-deepl.js en de",
    "i18n:check": "node tools/i18n-check.js"
  }
}

tools/translate-deepl.js

import fs from 'fs';
import path from 'path';
import assert from 'assert';
import fetch from 'node-fetch';

const [,, srcLang, dstLang] = process.argv;
const apiKey = process.env.DEEPL_API_KEY; // set in CI/ENV
assert(apiKey, 'DEEPL_API_KEY is required');

const src = JSON.parse(fs.readFileSync('locales/en.json', 'utf8'));
const out = {};

const GLOSSARY = {
  'EMS': 'Rettungsdienst',
  'PD': 'Polizei',
};

function protect(str){
  // Replace placeholders with tokens DeepL won't alter
  return str
    .replace(/%\{([^}]+)\}/g, '⟦$1⟧')
    .replace(/%s/g, '⟪S⟫')
    .replace(/%d/g, '⟪D⟫');
}
function restore(str){
  return str
    .replace(/⟦([^⟧]+)⟧/g, '%{$1}')
    .replace(/⟪S⟫/g, '%s')
    .replace(/⟪D⟫/g, '%d');
}

async function translate(text){
  const res = await fetch('https://api.deepl.com/v2/translate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      auth_key: apiKey,
      text: text,
      source_lang: srcLang.toUpperCase(),
      target_lang: dstLang.toUpperCase(),
      formality: 'prefer_more'
    })
  });
  const json = await res.json();
  if (!json.translations) throw new Error(JSON.stringify(json));
  return json.translations[0].text;
}

for (const [k, v] of Object.entries(src)) {
  const protectedText = protect(v);
  // Glossary pre-pass (simple):
  let glossed = protectedText;
  for (const [from, to] of Object.entries(GLOSSARY)) {
    glossed = glossed.replace(new RegExp(`\\b${from}\\b`, 'g'), to);
  }
  // Translate
  // eslint-disable-next-line no-await-in-loop
  const tr = await translate(glossed);
  out[k] = restore(tr);
}

fs.writeFileSync(`locales/${dstLang}.json`, JSON.stringify(out, null, 2));
console.log(`Wrote locales/${dstLang}.json`);

tools/i18n-check.js (placeholder parity)

import fs from 'fs';

const src = JSON.parse(fs.readFileSync('locales/en.json', 'utf8'));
const dst = JSON.parse(fs.readFileSync('locales/de.json', 'utf8'));

const reVar = /%\{[^}]+\}/g;
const reS   = /%s/g;
const reD   = /%d/g;

let ok = true;
for (const k of Object.keys(src)) {
  const a = (src[k].match(reVar)||[]).length === (dst[k]?.match(reVar)||[]).length;
  const b = (src[k].match(reS)||[]).length   === (dst[k]?.match(reS)||[]).length;
  const c = (src[k].match(reD)||[]).length   === (dst[k]?.match(reD)||[]).length;
  if (!(a && b && c)) {
    console.error('Placeholder mismatch for key:', k);
    ok = false;
  }
}
process.exit(ok ? 0 : 1);

Using LLMs (OpenAI/others) effectively

  • Chunk by topic/domain for better context (e.g., inventory, police, jobs).
  • Provide short descriptions per group (two lines) to define tone and audience.
  • Few‑shot examples: 2–3 correctly translated pairs with placeholders improve consistency.
  • Retry policy: re-run only failed keys flagged by i18n-check.

Few‑shot template (system + user)

System: You translate FiveM game UI strings for <TARGET_LANGUAGE>.
- Keep keys unchanged, preserve placeholders, keep tone concise.

User examples:
EN: "You have %{count} fines."
DE: "Du hast %{count} Strafzettel."

EN: "~r~Error:~s~ You lack permission."
DE: "~r~Fehler:~s~ Dir fehlt die Berechtigung."

Now translate the following JSON values from English to <TARGET_LANGUAGE>. Return valid JSON only:
<PASTE JSON HERE>

NUI (HTML/JS) translations

For browser UIs, a client‑side library is practical.

Recommended approach

  • Use a JSON bundle per language in web/locales/<lang>.json.
  • Load with your UI framework and expose a t(key, vars) helper.
  • Keep the same keys as server locales to reduce cognitive load.

Minimal JS helper

const dict = await (await fetch(`/locales/${lang}.json`)).json();
export function t(key, vars){
  let s = dict[key] || key;
  for (const [k,v] of Object.entries(vars||{})) s = s.replace(`%{${k}}`, v);
  return s;
}

ESX/QBCore specifics

  • Many ESX scripts ship locales/en.lua, locales/de.lua bir ile _U helper.
  • If you use Lua tables for locales, keep one style across your repo. Mixing JSON and Lua for the same resource increases maintenance cost.
  • QBCore often uses config‑driven messages. Migrate repeated strings to locale files to avoid divergence.

Lua table locale (if you prefer Lua over JSON)

Locales = Locales or {}
Locales['en'] = {
  no_permission = 'You do not have permission.',
  welcome_player = 'Welcome, %{name}!'
}
Locales['de'] = {
  no_permission = 'Du hast keine Berechtigung.',
  welcome_player = 'Willkommen, %{name}!'
}

Quality gates before you ship

  • JSON/Lua parse check in CI.
  • Placeholder parity (regex checks as shown).
  • Forbidden changes: disallow edits to /commands, keybind letters, color/chat codes.
  • Length deltas: flag +40% growth for UI buttons; may break layout.
  • Smoke test: spin up your server and spot‑check critical flows.

New to setting up a server for testing? Follow this starter: FiveM Sunucusu Nasıl Oluşturulur.


Maintenance strategy

  • Treat en.json as source of truth; create a CI job that diffs en.json and updates only changed keys in targets.
  • Keep a CHANGELOG.i18n.md for translators.
  • Encourage community to contribute via PRs; document your style guide Ve glossary içinde /docs/i18n.md.

Common pitfalls (and fixes)

  • Broken placeholders → Use automated checks and protection tokens.
  • Inconsistent terminology → Maintain a glossary and enforce it in prompts and pre‑processing.
  • Mixed locales in code → Fail CI if strings are detected outside locales/.
  • RTL languages → Ensure your NUI CSS sets direction: rtl; and uses fonts with RTL support.
  • Casing & punctuation drift → Instruct AI explicitly, and run a linter to normalize punctuation.

External resources



Copy‑paste checklists

Pre‑translation

  • All strings centralized in locales/en.json (or Lua table)
  • Keys follow a naming convention
  • Glossary prepared
  • Placeholders audited

Koşmak

  • Batch translate with glossary
  • Save output to locales/<lang>.json

QA

  • JSON/Lua valid
  • Placeholder parity OK
  • Forbidden tokens unchanged
  • Length deltas acceptable
  • Human spot‑check done

Ship

  • CI green
  • Changelog updated
  • Invite community feedback
Luka
Luka

Ben Luke, bir oyuncuyum ve FiveM, GTA ve rol yapma hakkında yazmayı seviyorum. Bir rol yapma topluluğu yönetiyorum ve sunucuları yönetme konusunda yaklaşık 10 yıllık deneyimim var.

Articles: 436