Zurück zum Blog
Tutorial6 Min. Lesezeit

FiveM-Lokalisierung und i18n: Mehrsprachige Scripts

Veröffentliche FiveM-Scripts in mehreren Sprachen. Sprachdateien, Ersatzschlüssel, Werkzeuge und Muster von ox_lib und anderen großen Open-Source-Ressourcen.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Warum Lokalisierung für FiveM-Server wichtig ist

FiveM-Rollenspiel-Communities gibt es auf der ganzen Welt, mit großen Spielerzahlen in Deutschland, Frankreich, Brasilien, der Türkei und Dutzenden weiteren Ländern. Wenn deine Scripts nur Englisch unterstützen, schließt du einen großen Teil potenzieller Kunden aus und schränkst die Communities ein, die deine Ressourcen nutzen können. Ein gut lokalisiertes Script passt alle sichtbaren Texte, Benachrichtigungen, Menüs und Fehlermeldungen an die bevorzugte Sprache des Spielers an. Das ist nicht nur eine praktische Zusatzfunktion, sondern auch ein Wettbewerbsvorteil, der Amateur-Scripts von professionellen unterscheidet. Die gute Nachricht: Internationalisierung (i18n) in FiveM lässt sich einfach umsetzen, sobald du das Prinzip verstanden hast.

Das Locale-System einrichten

Die Grundlage jedes Lokalisierungssystems ist eine strukturierte Möglichkeit, übersetzte Zeichenfolgen zu speichern und abzurufen. In FiveM ist der häufigste Ansatz die Verwendung von Locale-Dateien, jeweils eine pro Sprache, die in einem locales Verzeichnis innerhalb deiner Resource gespeichert werden. Jede Datei stellt eine Tabelle mit Schlüssel-Wert-Paaren bereit, wobei der Schlüssel eine eindeutige Kennung und der Wert die übersetzte Zeichenfolge ist. So strukturierst du dein Locale-Modul:

-- locales/en.lua
Locales = Locales or {}
Locales['en'] = {
    ['job_started']       = 'You have started your shift as %s.',
    ['job_ended']         = 'You have ended your shift. Earnings: $%d',
    ['not_enough_money']  = 'You do not have enough money. You need $%d.',
    ['inventory_full']    = 'Your inventory is full. Free up some space first.',
    ['vehicle_spawned']   = 'Your vehicle has been spawned nearby.',
    ['access_denied']     = 'You do not have permission to do that.',
    ['cooldown_active']   = 'Please wait %d seconds before doing that again.',
    ['item_received']     = 'You received %dx %s.',
}
-- locales/de.lua
Locales = Locales or {}
Locales['de'] = {
    ['job_started']       = 'Du hast deine Schicht als %s begonnen.',
    ['job_ended']         = 'Du hast deine Schicht beendet. Verdienst: $%d',
    ['not_enough_money']  = 'Du hast nicht genug Geld. Du brauchst $%d.',
    ['inventory_full']    = 'Dein Inventar ist voll. Schaffe zuerst Platz.',
    ['vehicle_spawned']   = 'Dein Fahrzeug wurde in der Naehe gespawnt.',
    ['access_denied']     = 'Du hast keine Berechtigung dafuer.',
    ['cooldown_active']   = 'Bitte warte %d Sekunden, bevor du das erneut tust.',
    ['item_received']     = 'Du hast %dx %s erhalten.',
}

Die Übersetzungsfunktion erstellen

Das Herzstück des Systems ist eine Übersetzungsfunktion, die Schlüssel nachschlägt und die Formatierung von Zeichenfolgen mit variablen Argumenten unterstützt. Wenn ein Schlüssel im aktiven Locale fehlt, sollte die Funktion auf eine Standardsprache zurückgreifen. Während der Entwicklung sollte sie Entwickler auf fehlende Übersetzungen hinweisen, statt Spielern rohe Schlüssel anzuzeigen.

-- shared/locale.lua
local currentLocale = 'en'
local fallbackLocale = 'en'

function SetLocale(locale)
    if Locales[locale] then
        currentLocale = locale
    else
        print(('[^1LOCALE^0] Language "%s" not found, falling back to "%s"'):format(locale, fallbackLocale))
        currentLocale = fallbackLocale
    end
end

function L(key...)
    local str = nil

    if Locales[currentLocale] and Locales[currentLocale][key] then
        str = Locales[currentLocale][key]
    elseif Locales[fallbackLocale] and Locales[fallbackLocale][key] then
        print(('[^3LOCALE^0] Missing key "%s" for locale "%s", using fallback'):format(key, currentLocale))
        str = Locales[fallbackLocale][key]
    end

    if not str then
        print(('[^1LOCALE^0] Missing translation key: "%s"'):format(key))
        return key
    end

    if ... then
        return str:format(...)
    end

    return str
end

Übersetzungen in deinen Scripts verwenden

Sobald das Locale-Modul geladen ist, kannst du Übersetzungen in deinem Script ganz einfach verwenden, indem du die L() Funktion mit dem Schlüssel und beliebigen Formatierungsargumenten aufrufst. So bleibt dein Script-Code sauber und Inhalte sind vollständig von der Logik getrennt.

-- server/main.lua
RegisterNetEvent('myresource:startJob', function(jobName)
    local src = source
    local xPlayer = ESX.GetPlayerFromId(src) -- or your framework equivalent

    if not xPlayer then return end

    if not HasPermission(src, jobName) then
        TriggerClientEvent('ox_lib:notify', src, {
            title = L('access_denied'),
            type = 'error'
        })
        return
    end

    ActiveJobs[src] = { name = jobName, started = os.time() }

    TriggerClientEvent('ox_lib:notify', src, {
        title = L('job_started', jobName),
        type = 'success'
    })
end)

Sprache pro Spieler erkennen

Ein wirklich professionelles Lokalisierungssystem erkennt die Sprache jedes Spielers automatisch. Dazu kannst du die Spieleinstellung für die Sprache des Clients auslesen oder Spielern ermöglichen, ihre Sprache über eine Config oder einen Befehl auszuwählen. Bei der clientseitigen Erkennung verwendest du den GetCurrentLanguage Native, der die Sprache des Spiels als zweistelligen Code zurückgibt. Du kannst diesen anschließend an den Server senden, damit alle Benachrichtigungen für den Spieler in seiner bevorzugten Sprache ausgegeben werden.

-- client/locale_detect.lua
CreateThread(function()
    local gameLang = GetCurrentLanguage()

    -- Map GTA language codes to your locale codes
    local langMap = {
        ['en-us'] = 'en',
        ['de-de'] = 'de',
        ['fr-fr'] = 'fr',
        ['es-es'] = 'es',
        ['pt-br'] = 'pt',
        ['it-it'] = 'it',
        ['pl-pl'] = 'pl',
        ['tr-tr'] = 'tr',
        ['ru-ru'] = 'ru',
        ['zh-cn'] = 'zh',
        ['ja-jp'] = 'ja',
        ['ko-kr'] = 'ko',
    }

    local detected = langMap[gameLang] or 'en'
    SetLocale(detected)

    TriggerServerEvent('myresource:setPlayerLocale', detected)
end)

Locale-Einstellungen pro Spieler serverseitig speichern

Speichere serverseitig die Locale-Einstellung jedes Spielers, damit du Nachrichten vor dem Senden in der richtigen Sprache formatieren kannst. Das ist entscheidend für serverseitig ausgelöste Benachrichtigungen und Chatnachrichten, die aus serverseitiger Logik stammen, wo du keinen direkten Zugriff auf die Locale-Einstellung des Clients hast.

-- server/locale_manager.lua
local PlayerLocales = {}

RegisterNetEvent('myresource:setPlayerLocale', function(locale)
    local src = source
    if Locales[locale] then
        PlayerLocales[src] = locale
    else
        PlayerLocales[src] = 'en'
    end
end)

AddEventHandler('playerDropped', function()
    PlayerLocales[source] = nil
end)

function GetPlayerLocale(src)
    return PlayerLocales[src] or 'en'
end

function LForPlayer(src, key...)
    local locale = GetPlayerLocale(src)
    local str = nil

    if Locales[locale] and Locales[locale][key] then
        str = Locales[locale][key]
    elseif Locales['en'] and Locales['en'][key] then
        str = Locales['en'][key]
    end

    if not str then return key end
    if ... then return str:format(...) end
    return str
end

NUI- und JavaScript-Oberflächen lokalisieren

Viele FiveM-Scripts verwenden NUI (HTML/JS) für ihre Benutzeroberflächen, die ebenfalls lokalisiert werden müssen. Am besten sendest du beim Initialisieren die gesamte Locale-Tabelle an den NUI-Frame und verwendest anschließend eine JavaScript-Übersetzungsfunktion, die der Lua-Funktion entspricht. Dadurch vermeidest du ständige NUI-Callbacks für jede einzelne Zeichenfolge.

// nui/js/locale.js
let currentLocale = {};
let fallbackLocale = {};

window.addEventListener('message', (event) => {
    if (event.data.action === 'setLocale') {
        currentLocale = event.data.locale || {};
        fallbackLocale = event.data.fallback || {};
        updateAllTranslations();
    }
});

function L(key...args) {
    let str = currentLocale[key] || fallbackLocale[key] || key;

    if (args.length > 0) {
        let i = 0;
        str = str.replace(/%[sd]/g, () => args[i++] ?? '');
    }

    return str;
}

function updateAllTranslations() {
    document.querySelectorAll('[data-locale]').forEach((el) => {
        const key = el.getAttribute('data-locale');
        el.textContent = L(key);
    });
}

Resource-Manifest konfigurieren

Dein fxmanifest.lua muss alle Locale-Dateien enthalten, damit sie beim Start der Resource geladen werden. Verwende ein Glob-Muster, damit neue Locale-Dateien automatisch eingebunden werden und du das Manifest nicht jedes Mal aktualisieren musst. Achte darauf, dass das gemeinsame Locale-Modul vor den Locale-Datendateien geladen wird.

-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

shared_scripts {
    'shared/locale.lua',
    'locales/*.lua',
}

client_scripts {
    'client/locale_detect.lua',
    'client/main.lua',
}

server_scripts {
    'server/locale_manager.lua',
    'server/main.lua',
}

ui_page 'nui/index.html'

files {
    'nui/**/*',
}

Best Practices für die Lokalisierung von FiveM

  • Verwende aussagekräftige Schlüssel anstelle numerischer IDs. Schlüssel wie inventory_full sind selbsterklärend und erleichtern die Wartung mehr als msg_042.
  • Verwende immer Format-Platzhalter (%s, %d ) für dynamische Werte anstelle von String-Verkettungen. In verschiedenen Sprachen ist die Wortstellung unterschiedlich, daher müssen sich die Werte an verschiedenen Positionen einsetzen lassen.
  • Füge Kontextkommentare hinzu in deinen Sprachdateien, damit Übersetzer verstehen, wo die einzelnen Zeichenfolgen angezeigt werden und wofür die Formatargumente stehen.
  • Teste mit langen Texten. Deutsche Texte sind typischerweise 30% länger als englische. Stelle sicher, dass deine UI-Elemente längere Übersetzungen verarbeiten können, ohne dass das Layout beschädigt wird.
  • Hardcode niemals nutzerseitig sichtbare Texte. Jede Benachrichtigung, Menübeschriftung, jeder Hilfetext und jede Fehlermeldung sollte über die L() Funktion laufen, auch wenn du zunächst nur eine Sprache unterstützt.
  • Stelle einen Sprachbefehl bereit wie /lang de damit Spieler die automatisch erkannte Sprache jederzeit überschreiben können.

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.