Locale System
Carplay V2 ships with 7 pre-translated language files and supports adding new ones. Every user-facing string in the script — Lua-side or React-side — flows through the locale system.
Built-In Locales
Located in locales/:
| File | Language |
|---|---|
en.lua | English |
tr.lua | Turkish |
es.lua | Spanish |
de.lua | German |
fr.lua | French |
nl.lua | Dutch |
pt.lua | Portuguese |
Choosing the Active Locale
Config.Locale = "en" -- en | tr | es | de | fr | nl | pt
The script loads the matching file at boot. If the file is missing or malformed, it falls back to en.lua.
Locale File Format
Each locale file returns a flat key→string table:
return {
home = "Home",
settings = "Settings",
confirm = "Confirm",
cancel = "Cancel",
-- ...
setup_welcome_title = "Welcome to Carplay",
setup_welcome_desc = "Let's set up your experience",
-- ...
music_no_music = "No music playing",
music_loading = "Loading...",
-- ...
autopilot_remaining = "Remaining Distance",
autopilot_eco = "Eco",
-- ...
}
Keys are stable identifiers — never change the keys, only the values when translating.
How Strings Are Looked Up
Lua Side
Use the global Lang(key, ...) helper:
ShowNotification(Lang("notification_engine_on"))
-- with format args (string.format)
ShowNotification(Lang("notification_fuel_low", fuelPercent))
-- where the locale value is e.g. "Fuel low: %d%%"
Behavior:
- Returns the string for
keyfrom the loaded locale - If the key doesn't exist, returns the key itself (as a fallback so missing strings are visible)
- Format args are applied via
string.formatif provided
React Side
The React app receives the locale table on resource start via SendReactMessage("language", localeTable) and stores it in the LanguageContext. Use the useLanguage hook (or directly via context) to look up strings.
import { useLanguage } from "../hooks/useLanguage"
function MyComponent() {
const { language } = useLanguage()
return <h1>{language.home || "Home"}</h1>
}
Always provide an English fallback as the second arg in case the key isn't yet translated in the active locale.
Adding a New Language
- Copy
locales/en.luatolocales/<your-locale>.lua— e.g.locales/it.luafor Italian - Translate every value (keep keys unchanged)
- Set in
shared/config.lua:
Config.Locale = "it"
- Restart the resource
The new locale takes effect immediately. No code changes, no React rebuild required.
String Categories
The locale file is grouped into logical sections (the order is just for readability):
- General — confirm, cancel, save, delete, close, edit, back, unknown, loading
- Setup wizard — first-run wizard text (welcome, profile, avatar, theme, done)
- Notifications — in-game notification text (engine on/off, vehicle locked, etc.)
- Vehicle controls — dock button labels and tooltips
- Music player — every UI label in the music app
- Map — places, favorites, recents, autopilot, waypoints
- Autopilot — drive modes, settings, navigation HUD
- Weather — conditions, descriptions, units, location label
- Cameras — security cam and dashcam labels
- Settings app — every option label in settings
- Errors — common error messages
Adding a New Key
If you add a feature that needs a new translatable string:
- Add the key to every locale file in
locales/— even if you only have an English value, add the same English text in all files (translators can pick it up later) - Reference it via
Lang("your_new_key")(Lua) orlanguage.your_new_key(React)
If a key is missing in the active locale, the fallback chain is:
- Active locale value
- (Lua) the key string itself — useful as a placeholder
- (React) whatever fallback you provide as a second arg
Locale Tips
- Keys should be
snake_caseand prefixed by area (music_,map_,autopilot_,setup_,weather_) - Don't use punctuation in keys — only in values
- For format strings (
%s,%d), preserve the placeholders in every translated value - Test your translation by setting
Config.Localeand using all features end-to-end
