Skip to content

国际化 ​

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 作为占位符,用于动态填充数据。

中文语言包 ​

json
{
  "server": {
    "running": "成功运行由 node.js 强力驱动的 omp 服务器",
    "connection": "连接信息:%s - %s : %s"
  }
}

英文语言包 ​

json
{
  "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 或空数组。

不传入第三个参数则从默认区域获取数据。

ts
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(区域)。默认值如下:

ts
class Player {
  charset = "ISO-8859-1";
  locale = "en_US";
}

通常 $t 会结合玩家的区域来获取对应的语言包数据。

ts
// 假设您有一个 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 字符)连接游戏。

ts
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 中。

powershell
$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding

cmd 用户可使用以下命令启动 OMP 服务器:

sh
cmd /c "chcp 65001 > nul & omp-server"

上述命令会将终端的字符集切换为 UTF-8。

其他环境请自行搜索解决方案。