NPC Helper 2/Docs
You are reading the documentation for v2.4.1. Switch to v2.4.2

Create an NPC

Localization

Translate NPC names, dialogue, schedules, marriage text, heart events, and festival lines, ship translations as separate packs, and read the startup checks that tell you when a translation is out of date.

An NPC pack is the original version of the character. A locale folder is a small set of overrides for one game language. You do not need to copy every original file: add only the values you want to translate.

Start with one translated name#

If the NPC is called Rei, add a locale/th/Rei.toml file beside the original Rei.toml:

text
NPCs/Rei/
├── Rei.toml
└── locale/
    └── th/
        └── Rei.toml

Put this in the locale file:

toml
DisplayName = "เรย์"

That is a complete translation file. Keep the internal NPC name (Name = "Rei") unchanged; translate DisplayName, which is what players see. A locale file does not create another NPC.

Choosing the language folder#

The folder name is a language code, not a translated language name:

text
locale/th/       Thai
locale/es-ES/    Spanish (Spain)
locale/pt-BR/    Portuguese (Brazil)

Locale folders are layered. For es-ES, NPC Helper reads locale/es/ first and then locale/es-ES/ on top of it, so a regional folder only needs the texts that differ from its language folder. Base text comes underneath both. If no folder has a translation for a key, the original value remains.

Before 2.3.0 only the most specific folder was read, so a partial pt-BR folder hid the rest of pt. That is no longer the case.

English does not use a locale overlay. Folder matching is case-insensitive.

Which files go where?#

The locale folder mirrors the NPC's original files. Use the same filenames:

File What you can translate
Rei.toml DisplayName, and selected NPC data such as gift tastes
Dialogue.toml NPC dialogue
Schedule.toml A language-specific daily schedule, if you really need one
Marriage.toml Engagement dialogue, spouse dialogue, and wedding vows

For example:

text
NPCs/Rei/
├── Rei.toml
├── Dialogue.toml
├── Schedule.toml
├── Marriage.toml
└── locale/
    └── th/
        ├── Rei.toml
        ├── Dialogue.toml
        └── Marriage.toml

There is deliberately no Schedule.toml in this example: the original schedule is already suitable for Thai, so it does not need an override.

Translating dialogue#

Create locale/th/Dialogue.toml. Use the same keys as the original [Dialogue] table, and include only the lines you are translating:

toml
[Dialogue]
Introduction = "ไง... ฉันชื่อเรย์ เป็นผู้ช่วยหมออยู่ที่คลินิก ถ้าจะมาตรวจร่างกายก็ไม่ต้องเกร็งหรอกน่า || แต่ถ้าฉุกเฉิน ก็รีบพุ่งไปห้องฉุกเฉินนู่น... ให้ไวเลยนะ"
Mon2 = "เจอกันอีกแล้วนะ วันนี้พยายามอย่าหาเรื่องเจ็บตัวกลับมาล่ะ"
Rainy = "ฝนตกทีไรกลิ่นทุกอย่างดูสะอาดขึ้น... แต่บางทีก็เหม็นกว่าเดิม พกร่มมาด้วยล่ะ"

The keys above are examples from the example translation. Introduction, Mon2, and Rainy are keys, not labels to translate. The text to the right of = is the part players read. Any original dialogue key you leave out continues to use the original line.

Keep the game's dialogue tokens when translating a line. For example, @ is replaced with the player's name, $h is a dialogue mood token, and || separates alternative text. Do not remove or translate a token unless you intend to change its behavior.

Translating the NPC file and gift tastes#

You can change a display name without copying the rest of Rei.toml:

toml
DisplayName = "เรย์"

You can also override a complete gift-taste category. A category you include replaces that whole category; its list is not appended to the original list:

toml
[GiftTastes]
Love = ["Diamond", "Coffee", "Starfruit", "Peach"]

This example replaces Rei's entire Love list. If you only want to translate dialogue and the displayed name, leave [GiftTastes] out. Keep item names and other game data valid for the game; do not translate internal identifiers just because they appear in a TOML file.

Schedules: the important exception#

Most translated values override matching keys and leave all other original values alone. Schedules do not work that way. If a locale Schedule.toml contains at least one valid entry in [Schedule], that localized schedule replaces the original schedule. It does not add a Monday route to the original week's routes.

toml
[Schedule]
Mon = [
  "600 Hospital 1 18 1",
  "1100 Hospital 9 9 2",
]

This is a complete schedule override containing only the Monday route shown. Do not use a partial example like this if the NPC also needs their original Tuesday-to-Sunday routes: copy and provide all routes you want the NPC to keep. An empty [Schedule] table, or one without valid entries, does not replace the original schedule.

Usually, dialogue is what needs translation; leave Schedule.toml out of the locale folder unless the schedule itself should differ by language. The married schedule follows the same replacement rule and is the [Marriage] table in Schedule.toml.

Translating engagement, spouse dialogue, and vows#

These are all in locale/th/Marriage.toml, but each kind of text has its own table:

toml
[Engagement]
AcceptMermaidPendant = "นี่มัน... จี้นางเงือกใช่ไหม? @... เธอกำลังขอฉันแต่งงานเหรอ? ฉัน... ตอบตกลง"

[Dialogue]
Rei0 = "ฉันยังไม่ชินเลยว่าเราหมั้นกันแล้ว ฉันยังนึกถึงตอนนั้นซ้ำๆ อยู่เลย.$l"
Good_0 = "ชีวิตกับเธอยังเหมือนฝันอยู่เลย... ในทางที่ดีนะ.$l"

[Wedding]
Vows = [
  "หึ... ไม่นึกเลยว่าวันนี้จะเป็นฉันเองที่ต้องจับชีพจรตัวเอง มันเต้นแรงชะมัด$h",
  "ฉันเย็บแผลให้คนมานับไม่ถ้วน สั่งให้ทุกคนหายใจลึกๆ... แต่ไม่เคยบอกใครเลยว่า ฉันเป็นของเธอแล้วนะ @$l",
  "คำสัญญาของฉันง่ายๆ เธอล้มเมื่อไหร่ฉันจะรักษา ทำงานหนักเกินไปฉันจะดุ แล้วฉันก็จะอยู่ข้างๆ เธอตลอด... นี่แหละใบสั่งยาของฉัน$l",
]

[Engagement] and [Dialogue] override matching lines; spouse dialogue you do not include remains unchanged. A non-empty valid [Wedding] Vows list replaces the original vows as a list. For the married daily route, use [Marriage] in Schedule.toml instead; [Marriage] is not the schedule table to use in Marriage.toml.

Translating heart events and festival lines#

Events.toml and Festivals.toml in a locale folder translate heart events and festival lines.

For events, each [EventName] table has Texts: the translated lines in the order they are said, counting spoken lines and message steps. Where the characters stand and what they do stay those of the base file.

toml
[RainyClinic]
Texts = [
  "Entraste huyendo de la lluvia.$h",
  "...No le digas a la doctora jefa que dije eso.$9",
]

For festivals, the tables work as in the base file and replace Dialogue and SpouseDialogue.

A wrong number of lines, an event or festival the base pack does not have, or a table without Texts is reported. Lines left out keep the original words.

Distributing a translation separately#

A translation can be released independently from the NPC pack. Include only locale TOML files; they override the matching NPC's text and do not add another NPC. Use the same NPCs/<NPC name>/locale/<language>/ path in either distribution:

text
Translation Pack/
├── manifest.json
├── mod.toml                 optional: NPCs = [], Locales, Translates
└── NPCs/
    └── Rei/
        └── locale/
            └── th/
                ├── Rei.toml
                ├── Dialogue.toml
                └── Marriage.toml

Mod-manager archive#

Keep manifest.json, mod.toml and NPCs/ together in the translation pack folder, as shown above, with ContentPackFor set to Framework.Mod.NpcHelper. Install it as a separate mod with your mod manager. NPC Helper asks SMAPI for its content packs and checks only NPCs/<NPC name>/locale/<code>/ inside each; it does not scan the Mods folder, and it never loads NPC definitions from a translation pack. A translation mod that is not a content pack for NPC Helper is not found.

mod.toml takes optional settings that help NPC Helper report mistakes:

  • NPCs = [] states that the pack intentionally defines no NPCs.
  • Locales = ["th", "pt-BR"] lists the languages the pack provides. A listed language with no files is reported.
  • Translates = ["Rei"] lists the NPCs the pack translates. If none of them is loaded, NPC Helper says so at startup and suggests the NPC you probably meant.
  • EmbedLocales is for an NPC pack that has its own language folders. See Embedded or legacy.

npchelper new translation writes both lists for you.

The older "manual" form#

Before 2.1 a translation could be shipped as locale files placed inside the NPC mod's own folder, with a separate manifest-only folder for update checks. That form is only accepted for one existing translation pack and will be removed. A manifest-only pack with NPCs = [] under any other ID is reported as a pack that does nothing. Always make new translations as content packs, as shown above. npchelper new translation creates the correct form.

Files placed this way end up in the NPC mod's own folder, so NPC Helper 2 sees them as a language folder of the NPC mod, not as a separate mod. See Embedded or legacy for how they are labelled.

Translations inside the NPC mod and from other mods#

A language folder for an NPC can be in two places:

  • Inside the NPC mod's own pack, for example Mods/MyNpcMod/NPCs/Rei/locale/es-ES/. The folder is part of the NPC mod. NPC Helper 2 calls it an embedded translation when the mod's author ships it.
  • Inside a separate content pack, for example Mods/Rei-Thai-Translation/NPCs/Rei/locale/th/. This is a translation with its own manifest.json, its own version, and its own author.

A separate pack replaces an embedded one#

When a separate content pack has a folder for the same NPC with the same language folder name, and the folder holds at least one .toml file, that folder replaces the embedded folder of that name. The two are not merged line by line.

Example: the NPC mod ships locale/th/ with 100 lines, and a Thai translation mod covers 90 of them.

text
NPC mod                          Thai translation mod
NPCs/Rei/locale/th/    (100)     NPCs/Rei/locale/th/    (90)

With both installed and the game in Thai, the 90 lines come from the translation mod and the other 10 show in the NPC's original language. They do not fall back to the embedded Thai lines.

This is deliberate. Two translators rarely agree on how to address the player, how to write a place name, or how formal to be. Mixing their lines inside one conversation reads as one character who changes voice from sentence to sentence. A replacement keeps one translator's voice, and the missing lines are visible (and reported, see Translation checks at startup) instead of being hidden by another translator's text.

Things to know:

  • Only that language is replaced. If the NPC mod also ships es-ES, it stays as it is.
  • The folder name must match. A separate pack with locale/es/ does not replace an embedded locale/es-ES/. For the game language es-ES, es is read first and the embedded es-ES is applied over it, as described in Choosing the language folder. Folder names are matched without regard to upper and lower case.
  • An empty folder replaces nothing. The separate pack's folder needs at least one .toml file.
  • The startup log tells you. For example: The embedded 'th' translation of NPC 'Rei' is replaced by mod 'Rei-Thai-Translation'.
  • Two separate packs for one language do not replace each other. Both are applied in path order and the later one wins for a key they share. Avoid installing two translations of the same language for the same NPC.

Embedded or legacy#

Translations were not always released as content packs. Some were, and still are, downloaded as loose files that you extract into the NPC mod's own folder (for example Mods/MyNpcMod/NPCs/Rei/locale/ru/Dialogue.toml). NPC Helper 2 loads those files exactly like an embedded folder, because they are in the same place. It cannot see where the files came from. But for the checks below it helps to tell a folder the NPC's author ships from a folder somebody copied in later, because the second kind is the one that falls behind when the NPC mod is updated.

So every language folder inside the NPC mod's own pack is labelled either embed or legacy:

  • EmbedLocales in mod.toml is the reliable way. The author lists the languages the mod ships (EmbedLocales = ["es-ES"]). Every other language folder inside the pack is legacy. A regional folder follows its language, so listing th covers th-TH. See EmbedLocales.
  • Without EmbedLocales, NPC Helper 2 guesses. A folder is taken for legacy when its keys do not match the NPC (some lines missing, or lines the NPC does not have) and its files are older than the NPC mod's files. The reasoning: an author writes the embedded translation together with the NPC, so it matches in full; a hand-copied file comes from an older download and lags behind.

The guess uses file dates, which are only a hint. Extracting an archive, a git checkout, or copying files can change every date to the same moment. And a translation that the author shipped but forgot to update looks exactly like a hand-copied one. If the label matters to you, set EmbedLocales.

The label changes three things, and nothing about how the text is loaded:

  • The startup log shows (legacy) instead of (embed) for the language.
  • A warning about a legacy folder says it was copied by hand, for example copied by hand into mod 'MyNpcMod' (legacy).
  • When a separate pack replaces a legacy folder, the log says legacy instead of embedded.

A translation from a separate content pack is never legacy, however old it is. It can only be reported as incomplete or outdated.

Translation checks at startup#

When the game starts, NPC Helper 2 compares each NPC's own texts with every translation folder it finds for that NPC. It checks every language that has files, not only the language the game is set to, so you see a problem before you switch language. It checks one NPC at a time, so a pack with several NPCs gets a separate verdict for each. The result goes into the NPC Helper 2 summary in the SMAPI console and log.

For each language it compares the folders the game would really read: a language folder and its regional folder together (es and es-ES), and a separate pack in place of an embedded folder of the same name.

What is compared#

Text Matched by
Dialogue (Dialogue.toml, [Dialogue] in Rei.toml) the dialogue key, such as Mon2
Engagement and spouse dialogue (Marriage.toml) the key in [Engagement] and [Dialogue]
Heart events (Events.toml) the event name, and the number of lines in Texts
Festival lines (Festivals.toml) the festival ID, such as spring13

The display name, gift tastes, schedules and wedding vows are not compared. A kind of text is only checked when the translation has at least one entry of that kind. A translation without a Marriage.toml is not told that marriage lines are missing, because a translator may leave them out on purpose.

What it reports#

There are three results, from mild to strong.

1. Extra lines only: a note. The translation has lines that the NPC does not have, and nothing is missing. This is a plain note, because a translator may add lines on purpose (a few extra greetings, for example). The lines are used as written.

text
Translation 'th' for NPC 'Rei' has 2 extra entries the NPC does not have (2 extra dialogue key(s) the NPC does not have: 'Extra1', 'Extra2'). They are used as written.

2. Missing lines: INCOMPLETE TRANSLATION, a warning. The NPC has lines the translation does not. The log counts what is missing and what is extra, and lists the first few keys. The missing lines stay in the NPC's original language.

text
INCOMPLETE TRANSLATION: 'th' for NPC 'Rei' from mod 'Rei-Thai-Translation' (3 missing, 1 extra): 3 dialogue entries of the NPC have no translation: 'Beach6', 'fall_Tue', 'Fri10'; 1 extra dialogue key(s) the NPC does not have: 'Old1'. The missing lines stay in the base language.

A warning is used because a missing line is a visible gap for the player (a sentence in the wrong language in the middle of a conversation), and the coloured warning is easy to find in the log. It does not stop the NPC from loading.

3. Too old or too different: OUTDATED TRANSLATION, a warning. The same missing lines, but one of two things is true, so the cause is probably not a few skipped lines but a version mismatch:

  • the translation's oldest file is more than 60 days older than the newest .toml file in the NPC's folder, or
  • many lines are missing: 10 or more, or 5 or more that make up at least a quarter of everything the NPC has to translate.
text
OUTDATED TRANSLATION: 'ru' for NPC 'Rei' copied by hand into mod '[HP] ReiMod' (legacy) does not match the NPC's texts (24 missing, 6 extra): 24 dialogue entries of the NPC have no translation: 'ArchaeologyHouse_Mon', 'ArchaeologyHouse_Tue', 'Beach6', 'fall_Tue', 'Fri10' and 19 more; 6 extra dialogue key(s) the NPC does not have: '10', '2', '4', '6', '8' and 1 more. Either the translation or the NPC mod is an old version. Update both to matching versions: until then some lines are missing or stay in the base language, and the story can read wrongly. The translation's oldest file is 279 day(s) older than the NPC mod's newest file (2026-11-01 against 2026-01-26), so the translation is the one behind.

The reasons for the thresholds:

  • 60 days, oldest against newest. A translation is only as current as its oldest file, and an NPC mod is only as old as its newest. If the NPC changed two months after the translation was written, the NPC very probably gained or renamed lines. Two months leaves room for translators who are a little slow without calling every update outdated.
  • Many missing lines. With that many gaps a few skipped lines are unlikely. The translation was written for another version of the NPC. A quarter of everything is used so a small NPC with 5 gaps counts as outdated, while a large NPC with 5 gaps is only incomplete.
  • Age alone never triggers a warning. A translation that matches the NPC completely is never reported, however old its files are. The age only decides between INCOMPLETE and OUTDATED when there is something missing.

The last sentence says which side is behind. If the translation's files are newer than the NPC mod's, it reads The NPC mod's newest file is N day(s) older than the translation's oldest file ... so the NPC mod is the one behind. That usually means you have an older version of the NPC mod than the translator had.

The dates are the files' last-modified dates. Mod managers that link files (such as Vortex) are followed to the real file. Extracting an archive may set every file to the extraction time, so treat the number of days as a clue and not as a measurement.

Where the results appear#

  • At startup, warnings are in the Problems part of the NPC Helper 2 summary and notes are in Notes. This includes the list of languages:
text
Notes:
  NPC 'Rei' has translations: es-ES (embed), ru (legacy), th (Rei-Thai-Translation).

The list shows only that a language has files and where it comes from: embed for the NPC mod's own pack, legacy for a hand-copied folder in it, or the name of the separate mod. It says nothing about whether the translation is complete. The checks above do that.

  • When you change the game's language, NPC Helper 2 rebuilds the NPCs and logs what it found as one entry for that language, grouped by NPC in name order. It logs nothing if there is nothing to say. The startup checks are not repeated.
text
Language 'th': 0 error(s), 0 warning(s).
  Rei:
    NOTE  [My NPC Mod] Rei.toml: NPC 'Rei': Translation 'th' for NPC 'Rei' found in mod 'Rei-Thai-Translation'.
  • An empty language folder in the NPC mod (for example a placeholder locale/th/) is reported as translating nothing, unless another mod provides that language for the same NPC.

How to fix a warning#

  • Players: update the translation and the NPC mod to versions made for each other. Check both mod pages for a new version. Nothing breaks while you wait: the missing lines show in the NPC's original language.
  • Translators: open the NPC mod's files, compare the keys the log lists, and add the missing lines. Delete lines the NPC no longer has, or keep them if they are on purpose (they only produce a note).
  • NPC mod authors: if the log calls one of your own folders legacy, add EmbedLocales. If it reports your embedded translation as outdated, update it or remove the folder.

Updating and removing#

To update a translation, replace its folder with the new version. To remove it, delete its folder from Mods. Players should install only one copy of a translation.

A simple workflow#

  1. Copy the NPC folder's structure under locale/<language-code>/.
  2. Start with Rei.toml and Dialogue.toml; add only the lines you actually translate.
  3. Keep each translated key exactly aligned with the original key, and leave internal names and game identifiers unchanged.
  4. Add Marriage.toml only if you are translating marriage text. Add Schedule.toml only if you intend to replace the schedule.
  5. Launch the game with that language selected and check the NPC in-game. If a line is still in the original language, check the locale code, file path, filename, and key.

Common questions#

Do I need to copy every original dialogue line? No. Untranslated keys use the original text.

Does locale/es-ES/ combine with locale/es/? Yes. es/ is read first and es-ES/ overrides it, so keep shared Spanish text in es/ and only the regional differences in es-ES/.

What if two translation packs define the same key? A separate pack that has the same language folder as the NPC mod replaces the NPC mod's embedded folder (see A separate pack replaces an embedded one). Between two separate packs, matching translation packs are merged in path order and the later pack wins.

What does npchelper validate check? Folder and file names the game would silently ignore (th_TH, Dialog.toml, a .txt file, translations placed directly in locale/) and translation folders whose NPC does not exist. npchelper pack refuses to zip a pack with these problems unless --force is given.

What happens when the language changes in game? NPC Helper rebuilds every file-based NPC from disk (base plus overlay) and refreshes the affected assets. If a rebuild fails, the previous version of that NPC is kept and a warning is logged. What the rebuild reports is logged as one entry for the language, grouped by NPC.

Why does my translation say "OUTDATED" when it works fine in game? It works, but some lines are missing and show in the original language, or the translation was written for another version of the NPC. See Translation checks at startup.

Why is a language marked "legacy"? The folder is inside the NPC mod's own pack and looks like a translation that was copied in by hand. See Embedded or legacy.

Will changing DisplayName rename the NPC internally? No. Keep Name and the NPC folder name unchanged; translate DisplayName.

Can I translate images here? No. Sprites and portraits are not localized.

Why did my original schedule disappear? A valid localized schedule replaces it. Copy every route you intend to retain, or remove the locale Schedule.toml file.

What if two translation packs change the same line? The NPC's own locale files are applied first, followed by separate matching locale packs. If several packs override the same key, one wins based on the packs' path order. Avoid installing multiple translations for the same NPC and language.