> lang => [ key => message ] */ private static array $messages = []; /** * 从请求中获取语言:优先读 header lang,否则按 Webman config('translation.locale') 推断 */ public static function getLang(?Request $request = null): string { $request = $request ?? (function_exists('request') ? request() : null); if ($request !== null) { $lang = $request->header(self::LANG_HEADER); if ($lang !== null && $lang !== '') { $lang = strtolower(trim((string) $lang)); if ($lang === self::LANG_EN) { return self::LANG_EN; } if ($lang === self::LANG_ZH || $lang === 'chs') { return self::LANG_ZH; } } } $locale = (string) (function_exists('config') ? config('translation.locale', 'zh_CN') : 'zh_CN'); return stripos($locale, 'en') !== false ? self::LANG_EN : self::LANG_ZH; } /** * 翻译文案(对外接口 message): * - 推荐:抛英文 key(如 USER_NOT_FOUND),根据 lang 返回对应语言 * - 兼容:仍抛中文原文时,lang=en 按旧映射翻译,否则原样返回 * * 语言文件优先从 Webman config('translation.path')/api/{lang}.php 加载 */ public static function translate(string $message, ?Request $request = null): string { $lang = self::getLang($request); $map = self::loadMessages($lang); if (isset($map[$message])) { return (string) $map[$message]; } // 若传入的是中文/原文,则按固定规则生成英文 key(MSG_XXXXXXXX)再翻译 $key = self::toMsgKey($message); if ($key !== null && isset($map[$key])) { return (string) $map[$key]; } return $message; } /** * 加载某语言的 API 文案(推荐:key=英文,value=对应语言文案) */ private static function loadMessages(string $locale): array { if (isset(self::$messages[$locale])) { return self::$messages[$locale]; } $path = null; if (function_exists('config')) { $base = rtrim((string) config('translation.path', ''), DIRECTORY_SEPARATOR); if ($base !== '') { $path = $base . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . $locale . '.php'; } } if ($path !== null && is_file($path)) { self::$messages[$locale] = require $path; return self::$messages[$locale]; } // 回退到 app/api/lang/{lang}.php(同样使用英文 key) $fallback = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $locale . '.php'; self::$messages[$locale] = is_file($fallback) ? (require $fallback) : []; return self::$messages[$locale]; } /** * 将原文转换为英文 key(只包含英文字符/数字/下划线):MSG_XXXXXXXX(crc32) * 若入参已经是英文 key,则返回 null(表示无需转换) */ private static function toMsgKey(string $message): ?string { $trim = trim($message); if ($trim === '') { return null; } // 已经是英文错误码 key(只允许 A-Z/0-9/_,且至少 3 位) if (preg_match('/^[A-Z0-9_]{3,}$/', $trim) === 1) { return null; } return 'MSG_' . strtoupper(sprintf('%08X', crc32($trim))); } /** * 带占位符的翻译,如 translateParams('当前玩家余额%s小于%s无法继续游戏', [$coin, $minCoin]) * 先翻译再替换(en 文案使用 %s 占位) */ public static function translateParams(string $message, array $params = [], ?Request $request = null): string { $translated = self::translate($message, $request); if ($params !== []) { $translated = sprintf($translated, ...$params); } return $translated; } }