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_fullsind selbsterklärend und erleichtern die Wartung mehr alsmsg_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 dedamit Spieler die automatisch erkannte Sprache jederzeit überschreiben können.