国际化
Infernus 的国际化核心在于字符集编码转换,而非多语言翻译。虽然 $t 可以用于翻译需求,但核心功能是确保服务端(UTF-8)与玩家客户端(旧式字符集如 ISO-8859-1 或 GBK)之间的数据交换正确。大多数服务器仅服务于单一区域,因此您可能完全不需要多语言支持——但至少应了解玩家国际化部分,以避免乱码问题。
VSCode 插件
infernus-starter 中的 .vscode/extensions.json 提供了插件安装建议。强烈建议您在 VSCode 右下角弹出提示时点击安装,以便在开发时直观地查看国际化数据。
i18n-ally-custom-framework.yml 和 settings.json 已为您配置好相关插件设置。
示例
定义语言包
语言包由 .json 文件组成,以树形结构作为语言包数据的 key。
字符串中可以包含 %s 作为占位符,用于动态填充数据。
中文语言包
{
"server": {
"running": "成功运行由 node.js 强力驱动的 omp 服务器",
"connection": "连接信息:%s - %s : %s"
}
}英文语言包
{
"server": {
"running": "Successfully running an omp server powered by node.js",
"connection": "connection information: %s - %s : %s"
}
}实例化
通过 new I18n("默认区域 key", 语言包对象) 创建国际化实例。
通常我们只需使用内部的 $t 函数。
$t("语言包数据 key", [占位数组], 区域 key) 可获取对应区域的语言包文本。
当字符串没有占位符时,可传入 null、undefined 或空数组。
不传入第三个参数则从默认区域获取数据。
import { I18n } from "@infernus/core";
// 国际化的 JSON 语言包
import zh_CN from "./locales/zh_CN.json";
import en_US from "./locales/en_US.json";
const locales = {
zh_CN,
en_US,
};
export const { $t } = new I18n("en_US", locales);
console.log($t("server.running"));
console.log($t("server.connection", ["127.0.0.1", "8080", "123456"]));
console.log($t("server.connection", ["127.0.0.1", "8080", "123456"], "zh_CN"));实用函数
I18n 类还提供了几个实用的静态方法,用于字符集之间的转换:
encodeToBuf- 将字符串转换为指定字符集的字节数组。
decodeFromBuf- 将字节数组转换为指定字符集的字符串。
getValidStr- 截取字节数组中的有效字符串,以第一个
0字节作为字符串结尾标记。
- 截取字节数组中的有效字符串,以第一个
玩家国际化
每个玩家实例上有两个属性控制国际化:charset(字符集)和 locale(区域)。默认值如下:
class Player {
charset = "ISO-8859-1";
locale = "en_US";
}通常 $t 会结合玩家的区域来获取对应的语言包数据。
// 假设您有一个 player 实例
console.log($t("server.incoming", ["127.0.0.1", "8080", "123456"], player.locale));如需修改玩家的语言或字符集,请修改 charset 和 locale 属性,并始终确保 charset 正确。您可以根据 IP 地址推测玩家区域,或让玩家输入特定字符串后比对字节来推断字符集。
允许国际化用户名
提示
默认情况下,游戏服务器只允许英文字母、数字和下划线组成的用户名连接。
OMP 提供了新的 API,可启用或禁用用户名中的特定字节值(0–255)。
GameMode.supportAllNickname() 方法利用这些 API,允许所有国际化的用户名(包括中文等非 ASCII 字符)连接游戏。
import { GameMode } from "@infernus/core";
GameMode.onInit(({ next }) => {
GameMode.supportAllNickname();
return next();
});原理
警告
双向字符集数据交换正常的前提是玩家的字符集设置正确。
如果设置了错误的字符集,服务端和客户端双方都将得到乱码。
Infernus 底层使用 iconv-lite 进行字符集转换,并通过 polyfill.amx 拦截 gamemode 中的部分回调,以十进制字节数组形式返回。
服务端向客户端的函数(如 SendClientMessage、SendClientMessageToAll、ShowPlayerDialog、CreateDynamic3DTextLabel)进行了字符集转换,将 UTF-8 数据转为玩家字符集数据,确保玩家能正常显示。
客户端到服务端的函数(如 OnText、OnPlayerCommandText、OnDialogResponse)同样经过了字符集转换,确保数据交换正确。
提示
虽然字符集已转换,但玩家字符集中不包含的 UTF-8 字符仍会以乱码(如 ?)形式显示,这是早期 SA 客户端的底层限制。例如,使用 ISO-8859-1 字符集的客户端收到 emoji 或中文字符时就会显示为乱码。
对于 GameText/TextDraw,Infernus 不会套用玩家的 charset 转换,而是使用调用这两个 API 时传入的 charset(GameText 默认 win1252,TextDraw 默认 ISO-8859-1)。客户端是按字节值从字体贴图中取字形(类似于前端中的精灵图),而不是把字节当作文本解码。部分客户端模组(如汉化补丁)会替换字体并期望 UTF-8 字节,这种情况请传入 charset: "utf8"。
俄文是同一套机制的另一面:russifier 是玩家装在客户端上的俄语化 mod,它重绘游戏的字形,把西里尔字母画进原字体留给重音拉丁字符的槽位。@infernus/rustext 负责服务端这一半——改写出站的 GameText、TextDraw 与 Menu RPC 文本,让 cp1251 字节落到该 mod 对应的字形槽位上。因此必须显式传入 charset: "cp1251"(上面的默认字符集会在包拿到文本之前就把西里尔字母变成 ?),并把 RussifierType 设为与玩家实际安装的那套一致——各套方案的槽位布局互不相同(SanLtd 是默认值,另有 OneC、Rush、MG 等)。包本身不附带任何 russifier 资源。广播给所有玩家的文本(GameText.forAll)不带玩家 id,不会被转换。
SA 重制版内部使用 UTF-8 实现,如果未来 OMP 推出重制版的联机功能,应无需额外处理。
终端乱码
如果使用 pino.js 等日志库时终端出现乱码,可尝试以下方法解决。
PowerShell 用户请执行以下命令(仅对当前会话有效)。
如需永久生效,请将该命令添加到 $PROFILE 中。
$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encodingcmd 用户可使用以下命令启动 OMP 服务器:
cmd /c "chcp 65001 > nul & omp-server"上述命令会将终端的字符集切换为 UTF-8。
其他环境请自行搜索解决方案。