= 2) { $first = $value[0]; $last = $value[$length - 1]; if (($first === '"' && $last === '"') || ($first === "'" && $last === "'")) { $value = substr($value, 1, $length - 2); } } $env[$name] = $value; } return $env; } // 获取环境变量值的简单工具函数 function language_env_value(string $key, array $env): ?string { if (array_key_exists($key, $env)) { return $env[$key]; } $value = getenv($key); if ($value !== false) { return $value; } return null; } // 读取语言环境配置 $languageEnvFile = dirname(__DIR__) . '/.env'; $languageEnv = is_readable($languageEnvFile) ? language_parse_env_file($languageEnvFile) : []; // 优先从 .env 读取 OPENAI 配置,未找到时再次尝试 getenv 覆盖 $openAiKey = trim(language_env_value('OPENAI_API_KEY', $languageEnv) ?? ''); if ($openAiKey === '') { fwrite(STDERR, "OPENAI_API_KEY 未设置,请在项目根目录的 .env 中配置。\n"); exit(1); } $openAiProxy = trim(language_env_value('OPENAI_PROXY_URL', $languageEnv) ?? ''); // 读取所有要翻译的内容 $originals = []; $generateds = []; foreach (['web', 'api'] as $type) { $content = file_exists("original-{$type}.txt") ? file_get_contents("original-{$type}.txt") : ""; $array = array_values(array_filter(array_unique(explode("\n", $content)))); $generateds[$type] = $array; $originals = array_merge($originals, $array); } // 判定是否存在translate.json文件 if (!file_exists("translate.json")) { print_r("translate.json not exists"); exit; } $translations = []; // 翻译数据 $regrror = []; // 正则匹配错误的数据 $redundants = []; // 多余的数据 $needs = []; // 需要翻译的数据 // 读取翻译数据 $tmps = json_decode(file_get_contents("translate.json"), true); foreach ($tmps as $obj) { if (!isset($obj['key'])) { continue; } $currentKey = $obj['key']; $originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $currentKey); $translations[$originalKey] = $obj; if (!in_array($originalKey, $originals)) { unset($translations[$originalKey]); $redundants[$originalKey] = $obj; continue; } if (preg_match_all('/\(%[TM]\d+\)/', $currentKey, $matches)) { foreach ($matches[0] as $match) { foreach ($obj as $k => $v) { if (empty($v)) { continue; } if (!str_contains($v, $match)) { // 正则匹配错误 $regrror[$originalKey] = [ $k => $v, 'match' => $match, 'key' => $currentKey, ]; continue 2; } } } } } if (count($regrror) > 0) { print_r("正则匹配错误的数据:\n"); print_r($regrror); exit(); } if (count($redundants) > 0) { print_r("多余的数据:\n"); print_r(implode(", ", array_keys($redundants)) . "\n\n"); } // 需要翻译的数据 foreach ($originals as $text) { $key = trim($text); if (!isset($translations[$key])) { $needs[$key] = $key; } } if (count($needs) > 0) { $array = array_chunk($needs, 10, true); $success = []; $error = []; $done = 0; foreach ($array as $index => $keys) { // 生成翻译内容 foreach ($keys as &$key) { $c = 1; $key = preg_replace_callback('/\((\*+)\)/', function ($m) use (&$c) { $label = strlen($m[1]) > 1 ? "M" : "T"; return "(%" . $label . $c++ . ")"; }, $key); } $content = implode("\n", $keys); // 开始翻译 print_r("正在翻译:" . (count($keys) + $done) . "/" . count($needs) . "...\n"); $openAi = new OpenAi($openAiKey); if ($openAiProxy !== '') { $openAi->setProxy($openAiProxy); } $result = $openAi->chat([ "model" => "gpt-5.1", "reasoning_effort" => "low", 'messages' => [ [ "role" => "system", "content" => << "user", "content" => $content, ], ] ]); // 处理结果 $obj = json_decode($result); $txt = preg_replace('/(^\s*```json\s*|\s*```\s*$)/', "", $obj->choices[0]->message->content); $txt = preg_replace('/\(%([TM]\d+)\)/', '(%$1)', $txt); $arr = json_decode($txt, true); if (!$arr || !is_array($arr)) { $error = array_merge($error, array_flip($keys)); print_r("翻译失败:\n" . $content . "\n\n"); file_put_contents("translate-gpt.log", json_encode($obj, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n", FILE_APPEND); continue; } // 验证结果 foreach ($arr as $item) { if (empty($item['key'])) { print_r("翻译结果不符合规范:key为空。\n"); print_r($item); continue; } foreach (['key', 'zh', 'zh-CHT', 'en', 'ko', 'ja', 'de', 'fr', 'id', 'ru'] as $lang) { if (!isset($item[$lang])) { print_r("翻译结果不符合规范:{$item['key']},缺少:{$lang} 的值。\n"); continue 2; } } $currentKey = $item['key']; $originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $currentKey); if (preg_match_all('/\(%[TM]\d+\)/', $currentKey, $matches)) { foreach ($matches[0] as $match) { foreach ($item as $k => $v) { if (empty($v)) { continue; } if (!str_contains($v, $match)) { // 正则匹配错误 $error[$originalKey] = [ 'key' => $currentKey, $k => $v, 'match' => $match, ]; continue 3; } } } } $item['zh'] = ""; $translations[$originalKey] = $item; $success[$originalKey] = $item; } print_r("翻译完成:" . (count($keys) + $done) . "/" . count($needs) . "\n\n"); $done += count($keys); } if (count($error) > 0) { print_r("正则匹配错误的数据:\n"); print_r(json_encode(array_values($error), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n\n"); } // 保存翻译结果 file_put_contents("translate.json", json_encode(array_values($translations), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); print_r("----------------\n\n"); print_r("总翻译:" . count($needs) . " 条\n"); print_r("成功:" . count($success) . " 条\n"); print_r("错误:" . count($error) . " 条\n\n"); print_r("----------------\n\n"); } // 生成前端使用的文件 foreach ($generateds as $type => $array) { $datas = []; foreach ($array as $text) { $text = trim($text); if (isset($translations[$text])) { $datas[] = $translations[$text]; } } // 按长度排序 $inOrder = []; foreach ($datas as $index => $item) { if (preg_match('/\(%[TM]\d+\)/', $item['key'])) { $inOrder[$index] = strlen($item['key']); } else { $inOrder[$index] = strlen($item['key']) + 10000000000; } } array_multisort($inOrder, SORT_DESC, $datas); // 合成数组 $results = []; $index = 0; foreach ($datas as $items) { foreach ($items as $kk => $item) { if (!isset($results)) { $results[$kk] = []; } $results[$kk][] = $item; } } // 生成文件 if ($type === 'api') { if (!is_dir("../public/language/api")) { mkdir("../public/language/api", 0777, true); } foreach ($results as $kk => $item) { $file = "../public/language/api/$kk.json"; file_put_contents($file, json_encode($item, JSON_UNESCAPED_UNICODE)); } } elseif ($type === 'web') { if (!is_dir("../public/language/web")) { mkdir("../public/language/web", 0777, true); } foreach ($results as $kk => $item) { $file = "../public/language/web/$kk.js"; file_put_contents($file, "if(typeof window.LANGUAGE_DATA===\"undefined\")window.LANGUAGE_DATA={};window.LANGUAGE_DATA[\"{$kk}\"]=" . json_encode($item, JSON_UNESCAPED_UNICODE)); } } print_r("[$type] total: " . count($results['key']) . "\n"); } print_r("\n任务结束\n");