NodeBB i18n: Server-Side and Client-Side Implementation
Internationalization is a crucial aspect of NodeBB plugin development (NodeBB supports 49 languages as of this writing). This article explains how to implement internationalization in NodeBB, covering both server-side and client-side aspects. Basic Structure of Internationalization NodeBB's internationalization is implemented with the following structure: plugins/ nodebb-plugin-caiz/ languages/ en-US/ caiz.json global.json ja/ caiz.json global.json library.js plugin.json Translation files are placed in subdirectories named with language codes (e.g., en-US, ja) within the languages directory. Translation files are in JSON format, consisting of key-value pairs. The language codes available in NodeBB can be referenced from the folder names in the public/language directory of the NodeBB repository. For example: en-US: American English en-GB: British English ja: Japanese zh-CN: Chinese (Simplified) zh-TW: Chinese (Traditional) These language codes are based on ISO 639-1 (2-letter language codes) and ISO 639-2 (3-letter language codes). Additionally, country codes can be added with a hyphen to represent regional differences (e.g., en-US, pt-BR). Plugin Configuration Update The most important aspect of internationalization implementation is the plugin.json configuration: { "id": "nodebb-plugin-caiz", "name": "NodeBB Plugin for Caiz", "description": "NodeBB Plugin for Caiz", "version": "1.0.0", "library": "./library.js", "languages": "languages", "scripts": [ "static/client.js" ] } Key settings: languages: Specifies the directory for translation files (in this example, the languages directory) scripts: Specifies client-side JavaScript files When the languages property is set, NodeBB automatically loads translation files from the specified directory. Each language's translation file must be placed in a subdirectory named with its language code. Example translation file (languages/ja/caiz.json): { "reading": "読み込み中", "create_community": "コミュニティを作成", "create_community_message": "新しくコミュニティを作成する", "community_name": "コミュニティ名", "community_description": "コミュニティの説明", "submit_create": "作成", "follow": "フォロー中", "unfollow": "未フォロー", "following": "フォローする", "unfollowing": "フォロー解除する", "follow_success": "フォローしました", "unfollow_success": "フォロー解除しました", "error.generic": "エラーが発生しました" } With this file, you can retrieve text using [[caiz.reading]]. Client-Side Implementation Client-side internationalization is implemented using the translator module. Since it's not very user-friendly by default, it's better to retrieve it asynchronously: async function getTranslate() { return new Promise((resolve, reject) => { require(['translator'], resolve); }); } document.addEventListener('DOMContentLoaded', async () => { const translator = await getTranslate(); const messageKeys = [ 'caiz:reading', 'caiz:follow', 'caiz:unfollow', 'caiz:follow_success', 'caiz:unfollow_success', 'caiz:error.generic', 'caiz:unfollowing', 'caiz:following', ]; const messages = Object.fromEntries( await Promise.all( messageKeys.map(key => new Promise((resolve) => translator.translate(`[[${key}]]`, (t) => resolve([key, t])) ) ) ) ); const getText = (key) => messages[key] || key; }); This code performs the following operations: Asynchronously loads the translator module Defines a list of required translation keys Uses translator.translate to get translations for each key Stores translations as an object Retrieves translations using the getText function As a result, you can get the i18n result using getText: getText('caiz:reading'); // 読み込み中 Usage in Templates In template files (*.tpl), translations are used as follows: [[caiz:create_community]] × Important Considerations Translation Key Naming Convention Use the translation file name as a prefix (e.g., caiz: for caiz.json) Use hierarchical structure for organization (e.g., error.generic) Translation Loading Timing Server-side: During application startup Client-side: During page load Error Handling Fallback processing when translations are not found Debug support through log output Summary NodeBB's internationalization is primarily configured by specifying the translation file location in the languages property of plugin.json. By properly designing the translation file structure and maintaining consistent naming conventions, you can achieve maintainable internationalization. goofmint/caiz

Internationalization is a crucial aspect of NodeBB plugin development (NodeBB supports 49 languages as of this writing). This article explains how to implement internationalization in NodeBB, covering both server-side and client-side aspects.
Basic Structure of Internationalization
NodeBB's internationalization is implemented with the following structure:
plugins/
nodebb-plugin-caiz/
languages/
en-US/
caiz.json
global.json
ja/
caiz.json
global.json
library.js
plugin.json
Translation files are placed in subdirectories named with language codes (e.g., en-US
, ja
) within the languages
directory. Translation files are in JSON format, consisting of key-value pairs.
The language codes available in NodeBB can be referenced from the folder names in the public/language
directory of the NodeBB repository. For example:
-
en-US
: American English -
en-GB
: British English -
ja
: Japanese -
zh-CN
: Chinese (Simplified) -
zh-TW
: Chinese (Traditional)
These language codes are based on ISO 639-1 (2-letter language codes) and ISO 639-2 (3-letter language codes). Additionally, country codes can be added with a hyphen to represent regional differences (e.g., en-US
, pt-BR
).
Plugin Configuration Update
The most important aspect of internationalization implementation is the plugin.json
configuration:
{
"id": "nodebb-plugin-caiz",
"name": "NodeBB Plugin for Caiz",
"description": "NodeBB Plugin for Caiz",
"version": "1.0.0",
"library": "./library.js",
"languages": "languages",
"scripts": [
"static/client.js"
]
}
Key settings:
-
languages
: Specifies the directory for translation files (in this example, thelanguages
directory) -
scripts
: Specifies client-side JavaScript files
When the languages
property is set, NodeBB automatically loads translation files from the specified directory. Each language's translation file must be placed in a subdirectory named with its language code.
Example translation file (languages/ja/caiz.json
):
{
"reading": "読み込み中",
"create_community": "コミュニティを作成",
"create_community_message": "新しくコミュニティを作成する",
"community_name": "コミュニティ名",
"community_description": "コミュニティの説明",
"submit_create": "作成",
"follow": "フォロー中",
"unfollow": "未フォロー",
"following": "フォローする",
"unfollowing": "フォロー解除する",
"follow_success": "フォローしました",
"unfollow_success": "フォロー解除しました",
"error.generic": "エラーが発生しました"
}
With this file, you can retrieve text using [[caiz.reading]]
.
Client-Side Implementation
Client-side internationalization is implemented using the translator
module. Since it's not very user-friendly by default, it's better to retrieve it asynchronously:
async function getTranslate() {
return new Promise((resolve, reject) => {
require(['translator'], resolve);
});
}
document.addEventListener('DOMContentLoaded', async () => {
const translator = await getTranslate();
const messageKeys = [
'caiz:reading',
'caiz:follow',
'caiz:unfollow',
'caiz:follow_success',
'caiz:unfollow_success',
'caiz:error.generic',
'caiz:unfollowing',
'caiz:following',
];
const messages = Object.fromEntries(
await Promise.all(
messageKeys.map(key =>
new Promise((resolve) =>
translator.translate(`[[${key}]]`, (t) => resolve([key, t]))
)
)
)
);
const getText = (key) => messages[key] || key;
});
This code performs the following operations:
- Asynchronously loads the
translator
module - Defines a list of required translation keys
- Uses
translator.translate
to get translations for each key - Stores translations as an object
- Retrieves translations using the
getText
function
As a result, you can get the i18n result using getText
:
getText('caiz:reading'); // 読み込み中
Usage in Templates
In template files (*.tpl
), translations are used as follows:
class="modal-header">
class="modal-title">[[caiz:create_community]]
Important Considerations
-
Translation Key Naming Convention
- Use the translation file name as a prefix (e.g.,
caiz:
forcaiz.json
) - Use hierarchical structure for organization (e.g.,
error.generic
)
- Use the translation file name as a prefix (e.g.,
-
Translation Loading Timing
- Server-side: During application startup
- Client-side: During page load
-
Error Handling
- Fallback processing when translations are not found
- Debug support through log output
Summary
NodeBB's internationalization is primarily configured by specifying the translation file location in the languages
property of plugin.json
. By properly designing the translation file structure and maintaining consistent naming conventions, you can achieve maintainable internationalization.