Theme
Localization
Every piece of text a player can read goes through one lookup.
**Keys.** A key starting with '@' is a stable ID ("@camo.page.visibility"); anything else is a literal and is its own key. IDs survive a renamed row or menu title - which is what the old "[lang.<menu title>]" scheme could not - and a literal is how a plugin whose source you do not have is translated.
**Sources**, in order:
- plugins\<owner>\lang.ini, section [<language>] (the owner's)
- <gamedir>\lang.ini, section [<language>] (shared override)
- the compiled-in baseline this module declared with ShLangDeclare, for the active language
- the same baseline, for "en-US"
- an ID with no text anywhere is shown readable ("@a.b" -> "A B") and logged once; a literal falls through to itself
A lang.ini row overrides the baseline for that one key, so a file only needs the lines it changes. Nothing in the framework ever writes a lang.ini: the settings file is ours, the text is yours.
Language codes are standard BCP-47 tags ("zh-CN", "en-US") and are compared case-insensitively and exactly. [Settings] Language is the active language, [Settings] Languages the list the picker shows (default: the languages this build ships text for).
| Export | Summary |
|---|---|
ShLangDeclare | Declare this module's text for ONE language; call once per language (call again later to add a language - code that already declares two needs no change). |
ShLangBuiltin | One of the languages this build ships text for, in declaration order. |
ShLangExportFramework | Write the framework's own menu text out as lang.ini in the game folder, in the language in force. |
ShLangExportPlugins | The same, for every installed plugin's own lang.ini, beside its .asi. |
ShLangLabel | The label to show for a language code: its [LanguageNames] entry when a lang.ini carries one, else the code itself. |
ShLangText | Translate one key for one owner (NULL or "" = framework text). |
ShLangHas | 1 when a source can answer for this key: a lang.ini row or a baseline row, in the active language or in en-US. |
ShLang | Translate framework text: the same as ShLangText(NULL, text). |
ShLangFor | Retained spellings for callers that still pass a scope. |
ShLangForOwned | See ShLangFor. |
ShLangMatch | The framework's own language-code comparison, for callers that keep a code of their own: standard BCP-47 tags, case-insensitive. |
ShLangGet | The active language, from [Settings] Language. |
ShLangPickLine | Internal, for the loader: one line saying where this session's menu language came from, on the run that picked it because the settings file had no [Settings] Language= row. |
ShLangSet | Switch the active language now: what was read for the old language is dropped and the next lookup reads the files again, so the menu - which translates as it captures - is in the new language on the next frame. |
ShLangDeclare
c
SH_API int ShLangDeclare(const char *owner, const char *lang,
const ShText *rows, int n);Declare this module's text for ONE language; call once per language (call again later to add a language - code that already declares two needs no change). rows must stay alive for the life of the process: a static const array is what this is for. owner NULL or "" means the framework. The languages declared here are what ShLangBuiltin reports and what [Settings] Languages falls back to. Returns 1 when kept.
scripthook.h, line 3650
ShLangBuiltin
c
SH_API int ShLangBuiltin(int i, char *buf, int size);One of the languages this build ships text for, in declaration order. Fills the tab-separated pair "code<TAB>label" and returns 1; 0 when i is out of range or buf is too small.
scripthook.h, line 3655
ShLangExportFramework
c
SH_API int ShLangExportFramework(void);Write the framework's own menu text out as lang.ini in the game folder, in the language in force.
Filled in, never rewritten. A key already in that section keeps whatever value it has - that row is the player's translation, and pressing this a second time must not cost it. Only what is missing is appended, so a file that is complete is not touched at all: no write, no changed timestamp. Every other language section is left alone.
The write lands through lang.ini.tmp and a rename, so a failure part way leaves the file as it was. There is no .bak, because there is no state in which the original is half-written.
A key with no text in this language falls back to en-US, then to the key itself - for a plugin with no compiled-in table at all the key IS the English word, so that last step is what fills its rows.
Returns the number of files written; 0 when everything was already complete, or when there is nothing to write.
scripthook.h, line 3675
ShLangExportPlugins
c
SH_API int ShLangExportPlugins(void);The same, for every installed plugin's own lang.ini, beside its .asi. Plugins that share a folder share one file, which is how the loader reads them anyway.
scripthook.h, line 3679
ShLangLabel
c
SH_API const char *ShLangLabel(const char *code);The label to show for a language code: its [LanguageNames] entry when a lang.ini carries one, else the code itself. Never NULL.
scripthook.h, line 3682
ShLangText
c
SH_API const char *ShLangText(const char *owner, const char *key);Translate one key for one owner (NULL or "" = framework text). Never NULL and never a failure: text that is missing everywhere comes back readable rather than empty.
scripthook.h, line 3687
ShLangHas
c
SH_API int ShLangHas(const char *owner, const char *key);1 when a source can answer for this key: a lang.ini row or a baseline row, in the active language or in en-US. Ask this before showing text that must stay empty when there is none (a hint line, say): ShLangText always returns something, which is right for a label and wrong for a line that takes room.
scripthook.h, line 3694
ShLang
c
SH_API const char *ShLang(const char *text);Translate framework text: the same as ShLangText(NULL, text).
scripthook.h, line 3697
ShLangFor
c
SH_API const char *ShLangFor(const char *scope, const char *text);Retained spellings for callers that still pass a scope. The scope is ignored - a key says what text a row gets, not a menu path.
scripthook.h, line 3700
ShLangForOwned
c
SH_API const char *ShLangForOwned(const char *owner,
const char *scope,
const char *text);Documented together with ShLangFor just above.
scripthook.h, line 3701
ShLangMatch
c
SH_API int ShLangMatch(const char *a, const char *b);The framework's own language-code comparison, for callers that keep a code of their own: standard BCP-47 tags, case-insensitive.
scripthook.h, line 3706
ShLangGet
c
SH_API const char *ShLangGet(void);The active language, from [Settings] Language.
scripthook.h, line 3708
ShLangPickLine
c
SH_API int ShLangPickLine(char *buf, int size);Internal, for the loader: one line saying where this session's menu language came from, on the run that picked it because the settings file had no [Settings] Language= row. Returns 0 - and empties buf - on every other run.
scripthook.h, line 3713
ShLangSet
c
SH_API int ShLangSet(const char *code);Switch the active language now: what was read for the old language is dropped and the next lookup reads the files again, so the menu - which translates as it captures - is in the new language on the next frame. Text that was already written into a line (a status line, a toast) is put away instead of left in the old language; a page that keeps its own line current fills it again on the next tick. This does not write scripthook.ini: the caller decides whether to persist the choice.
scripthook.h, line 3721