# Translations

Phrase files are the only copy of a plugin's user-facing strings. Operators reword or recolour a message by editing JSON — no rebuild, no fork. Keys are checked at build against the files that plugin actually loads.

## Load, then reply

Nothing is loaded automatically, including the shared `common` set. `s2s build` reads `translations.load(...)` to work out which keys `cmd.replyT` and `Translations.translate` accept; a key from a file you did not load is a compile error.

```ts
import { command, ADMFLAG, Chat, Translations, translations, HookResult } from '@s2script/sdk';

export function OnPluginStart(): void {
	translations.load('basecomm', 'common');

	command.admin('sm_gag', ADMFLAG.CHAT, (cmd) => {
		cmd.replyT('Usage Gag');
		Chat.toSlot(cmd.callerSlot, Translations.translate(cmd.callerSlot, 'Gagged Player', 1));
		return HookResult.Handled;
	});
}
```

`translations.load` is load-window only — it throws after settle. Order is significant: `translate` takes the first hit within each of its two passes (the client's language, then English). List your own set before any shared one if you want to override a shared phrase.

A plugin that loads nothing widens `PhraseKey` to `string` and compiles unchecked.

## Phrase files

One file per set, at `translations/<set>.phrases.json` in the addon (for example `addons/s2script/translations/basecomm.phrases.json`). Flat key → English template. `{1}`, `{2}`, … are positional slots; `{green}`, `{default}`, … are colour tags expanded on output.

```json
{
	"Usage Gag": "Usage: sm_gag <target>",
	"Gagged Player": "{green}[SM]{default} Gagged {1} player."
}
```

Keys are human-readable English on purpose: an unresolved key renders as the key itself.

A translator adds `translations/<code>/<set>.phrases.json` — no code change, no rebuild. English (the root file) is the fallback when a language file omits a key.

A missing phrase file renders raw keys. `Translations.load` warns, naming the set, when a seedless load finds no file.

## Colour tags

On CS2, tags match `ChatColors` names in lowercase (`{green}`, `{default}`, `{lightred}`, …). Expansion happens at output: chat keeps the control byte; console expands then strips, so tags never leak as literal `{green}`. Unknown tags are deleted (players never see stray braces) and warned once each to the server console.

`{1}` arguments cannot inject tags — braces are stripped from substituted values.

`Chat.toAll` is not per-client localized; it uses the operator-settable server default (`Translations.setDefaultLanguage`). `cmd.replyT` and `Translations.translate(slot, …)` resolve for that caller's language.

## Lower-level load

Prefer `translations.load(...)` — that is what the build reads. `Translations.load(name, seed?)` is the lower-level form, for a name computed at runtime or a set with an in-code default. A `seed` is starting content; the file overrides it.

See the [translations module](https://s2script.com/docs/api/modules/translations) and [commands](https://s2script.com/docs/concepts/commands).
