> For the complete documentation index, see [llms.txt](https://tyrix.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tyrix.gitbook.io/docs/script-resources/tyrix-multi-job/locales.md).

# Locales

Every player-visible line of text in Tyrix Multijob—notifications, dialogs, and every interface label—comes from one JSON file per language:

```
locales/
├── localesManager.lua      The loader. Rarely needs touching.
└── json/
    ├── en.json             English
    ├── es.json             Spanish
    └── fr.json             French
```

Three languages ship with the resource. Adding a fourth is a copy-and-translate job.

The JSON files are listed in `escrow_ignore`, so they stay plain readable files after the resource is escrowed. You can edit them and add languages freely; the loader itself is compiled and needs no changes.

## Selecting a language

Set the locale in `shared/config.lua`:

```lua
Config.Locale = 'en'  -- 'en', 'es', 'fr', or any file you add
```

The value is the filename without its extension. Restart the resource to apply it.

## Adding a language

{% stepper %}
{% step %}

### Copy the English file

Copy `locales/json/en.json` to `locales/json/<code>.json`—`fr.json`, `de.json`, `es.json`, or whatever you like.
{% endstep %}

{% step %}

### Translate the values

Translate the **values**. Leave every key exactly as it is.
{% endstep %}

{% step %}

### Select the new locale

Set `Config.Locale` to your new code and restart.
{% endstep %}
{% endstepper %}

That is the whole process. There is no manifest entry to add—`fxmanifest.lua` already declares `locales/json/*.json`, so any file you drop in that folder is picked up.

Keeping your translation a structural clone of `en.json`—with the same keys in the same order—makes it trivial to diff the two files and see what a resource update added.

## How the file is organised

The JSON is nested, and the code looks keys up by dot path. `notify.clocked_in` means the `clocked_in` entry inside the `notify` object.

There are two top-level groups, split by what consumes them:

| Group    | What it covers                                                                                         |
| -------- | ------------------------------------------------------------------------------------------------------ |
| `notify` | Everything Lua displays: toast notifications, confirmation dialogs, and the FiveM keybind label        |
| `web`    | Everything the interface draws: menu titles, tab names, buttons, column headings, empty states, modals |

`web` is subdivided by screen:

* `web.jobs`
* `web.sidebar`
* `web.overview`
* `web.employees`
* `web.goals`
* `web.wash`
* `web.transactions`
* `web.funds`
* `web.rank`
* `web.salaries`
* `web.modals`
* `web.time`

It also includes `web.common` for text reused across several screens, including Cancel, Confirm, Loading, hour and currency formats, and pagination.

Because `web.common` entries appear in many places, changing one changes all of them. That is usually what you want. If you need one screen to differ, add a new key to that screen's own group.

## Placeholders

Values that receive a runtime number or name use positional placeholders—`{1}`, `{2}`, and so on:

```json
"max_jobs_desc": "You cannot have more than {1} jobs.",
"bonus_received_desc": "You were granted a ${1} employment bonus from {2}."
```

The first receives the player's job limit. The second receives the amount first and the paying society second.

Keep the same placeholders, and keep each one pointing at the same value. Word order is yours to change—that is the point of translating—but `{1}` always means the first value the code passes, wherever you move it in the sentence.

This is a correct translation:

```json
"bonus_received_desc": "Vous avez reçu une prime de ${1} de la part de {2}."
```

Swapping them to `{2}` … `{1}` would announce the society's name as the amount.

{% hint style="warning" %}

* **Dropping a placeholder** loses that value silently—a player is told they received a bonus without being told how much.
* **Adding a placeholder with no value behind it** leaves the literal `{3}` visible in the text.

Neither one errors or stops the resource.
{% endhint %}

Percent signs are literal here—write `%`, not `%%`.

## Money and units

Currency symbols live inside the strings rather than in a separate setting. `web.common.currency_amount` is `"${1}"`, and several `notify` strings carry a `$` in their own text.

To change currency, search `en.json` for `$` and edit those values.

The same applies to unit suffixes: `web.common.hours_suffix` is `"{1}h"` and `web.common.hours_of_goal` is `"{1}/{2}h"`. Change the `h` there and it changes everywhere hours are displayed.

Number *grouping* is not translatable—thousands are separated with commas by the interface's own formatter, independently of the locale file.

## Missing keys and fallbacks

The loader is deliberately hard to break:

* A key missing from your language falls back to the English entry.
* If your whole file fails to load or parse, the resource logs a warning and runs entirely in English rather than starting broken.
* If a key is missing from English too, the key path itself is displayed, for example `web.jobs.title`. That is your signal that a key was renamed or removed.

Watch the server console after a restart. A yellow `[tyrix_multijob]` line reports a locale that could not be loaded; individual missing keys are reported only when `Config.Debug = true`, so one bad key cannot flood your console.

{% hint style="warning" %}
Because these are JSON files, a syntax error—a trailing comma, a missing brace, or an unescaped quote—makes the whole file unparseable. Run it through any JSON validator if a restart drops you back to English unexpectedly.
{% endhint %}

## Applying changes

Locale edits need a `restart tyrix_multijob` and nothing more.

{% hint style="info" %}
No rebuild of the interface is required—the interface receives its text from the same JSON at runtime, so translating the UI is exactly as easy as translating a notification.
{% endhint %}

## Swapping the notification system

Translating text is separate from choosing how it is displayed. Every toast is fired as a `tyrix_multijob:notify` event carrying a title, a description, and a style (`'success'` or `'error'`).

`client/editable.lua` handles that event and renders it with `lib.notify` from ox\_lib by default.

That file is outside escrow, so if your server uses a different notification script, replace the body of the handler and every toast in the resource follows. See [Integration](broken://spaces/nbepUC6poQxMqxuJkcj7/pages/d64454a88bf46d671c15b40643e268259ea7243b) for the event signature and a worked example.
