perf: 优化国际化语言

This commit is contained in:
kuaifan 2024-10-30 23:10:45 +08:00
parent 42f69124aa
commit d8a25e75d7
33 changed files with 2425 additions and 2638 deletions

View File

@ -992,12 +992,11 @@ class ApproveController extends AbstractController
'is_finished' => $process['is_finished'],
'data' => $data
];
$procDefName = Doo::translate($data['proc_def_name']);
$msgData['title'] = match ($type) {
'approve_reviewer' => $data['nickname'] . " 提交的「{$procDefName}」待你审批",
'approve_notifier' => "抄送 {$data['nickname']} 提交的「{$procDefName}」记录",
'approve_comment_notifier' => $data['comment_nickname'] . " 评论了 {$data['nickname']} 的「{$procDefName}」审批",
'approve_submitter' => $action == 'pass' ? "您发起的「{$procDefName}」已通过" : "您发起的「{$procDefName}」被 {$data['nickname']} 拒绝",
'approve_reviewer' => $data['nickname'] . " 提交的「{$data['proc_def_name']}」待你审批",
'approve_notifier' => "抄送 {$data['nickname']} 提交的「{$data['proc_def_name']}」记录",
'approve_comment_notifier' => $data['comment_nickname'] . " 评论了 {$data['nickname']} 的「{$data['proc_def_name']}」审批",
'approve_submitter' => $action == 'pass' ? "您发起的「{$data['proc_def_name']}」已通过" : "您发起的「{$data['proc_def_name']}」被 {$data['nickname']} 拒绝",
default => '不支持的指令',
};
if ($action == 'withdraw' || $action == 'pass' || $action == 'refuse') {

3
language/.gitignore vendored
View File

@ -1,7 +1,6 @@
config.php
undefined-*.txt
error.log
vendor
translate-gpt.md
*.log
test*
temp

View File

@ -599,11 +599,11 @@ webhook地址最长仅支持255个字符。
考勤机
手动签到
(*)评论了(*)的「(*)」审批
抄送(*)提交的「(*)」记录
(*)提交的「(*)」待你审批
您发起的「(*)」已通过
您发起的「(*)」被(*)拒绝
(*)评论了(*)的「(**)」审批
抄送(*)提交的「(**)」记录
(*)提交的「(**)」待你审批
您发起的「(**)」已通过
您发起的「(**)」被(*)拒绝
消息不存在或已被删除
此消息不支持翻译

View File

@ -1716,11 +1716,11 @@ WiFi签到延迟时长为±1分钟。
你确定要登出系统?
(*)评论了(*)的「(*)」审批
抄送(*)提交的「(*)」记录
(*)提交的「(*)」待你审批
您发起的「(*)」已通过
您发起的「(*)」被(*)拒绝
(*)评论了(*)的「(**)」审批
抄送(*)提交的「(**)」记录
(*)提交的「(**)」待你审批
您发起的「(**)」已通过
您发起的「(**)」被(*)拒绝
翻译
翻译消息

View File

@ -1,144 +0,0 @@
<?php
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
require __DIR__ . '/vendor/autoload.php';
use Orhanerday\OpenAi\OpenAi;
require_once("config.php");
// 读取所有要翻译的内容
$array = [];
foreach (['api', 'web'] as $type) {
$content = file_exists("original-{$type}.txt") ? file_get_contents("original-{$type}.txt") : "";
$array = array_merge($array, array_values(array_filter(array_unique(explode("\n", $content)))));
}
// 判定是否存在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 $tmp) {
if (!isset($tmp['key'])) {
continue;
}
$key = $tmp['key'];
$translations[$key] = $tmp;
if (in_array($key, $array)) {
$count = substr_count($key, '(*)');
if ($count > 0) {
foreach ($tmp as $k => $v) {
if ($k == 'zh' || $k == 'key') {
continue;
}
if ($count != substr_count($v, '(*)')) {
$regrror[$key] = $tmp;
continue 2;
}
}
}
} else {
$redundants[$key] = $tmp;
}
}
foreach ($array as $text) {
$key = trim($text);
if (!isset($translations[$key])) {
$needs[$key] = $key;
}
}
if (count($needs) > 0) {
$waits = array_chunk($needs, 100, true);
foreach ($waits as $index => $items) {
if ($index > 0) {
print_r("\n");
}
print_r("正在翻译:" . count($items) . "/" . count($needs) . "\n");
$content = implode("\n", $items);
$open_ai = new OpenAi(OPEN_AI_KEY);
$open_ai->setProxy(OPEN_AI_PROXY);
$chat = $open_ai->chat([
'model' => 'gpt-4o',
'messages' => [
[
"role" => "system",
"content" => <<<EOF
你是一个专业的翻译器,翻译的结果尽量符合“项目任务管理系统”的使用,将提供的文本按每行一个翻译成:
```json
[
{
"key": "",
"zh": "",
"zh-CHT": "",
"en": "",
"ko": "",
"ja": "",
"de": "",
"fr": "",
"id": "",
"ru": ""
}
]
```
key原文本zh留空(不用翻译)zh-CHT繁体中文en英语ko韩语ja日语de德语fr法语id印度尼西亚语ru俄语。
另外要注意的是其中的(*)为占位符翻译时不要删除也不要翻译这个占位符。请直接返回文本不需要使用markdown。
EOF,
],
[
"role" => "user",
"content" => $content,
],
],
'temperature' => 1.0,
'max_tokens' => 4000,
'frequency_penalty' => 0,
'presence_penalty' => 0,
]);
$obj = json_decode($chat);
$arr = json_decode($obj->choices[0]->message->content, true);
if (!$arr || !is_array($arr)) {
print_r("翻译失败:\n");
print_r($content . "\n");
continue;
}
foreach ($arr as $item) {
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;
}
}
if (empty($item['key'])) {
print_r("翻译结果不符合规范:{$item['key']}key为空\n");
continue;
}
$count = substr_count($item['key'], '(*)');
if ($count > 0) {
foreach ($item as $k => $v) {
if ($k == 'zh' || $k == 'key') {
continue;
}
if ($count != substr_count($v, '(*)')) {
print_r("翻译结果不符合规范:{$item['key']},正则匹配错误:{$k} => {$v}\n");
continue 2;
}
}
}
$item['zh'] = "";
$translations[$item['key']] = $item;
}
file_put_contents("translate.json", json_encode(array_values($translations), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
print_r("翻译完成:" . count($items) . "/" . count($needs) . "\n");
}
}

View File

@ -1,76 +0,0 @@
<?php
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
// 读取所有要翻译的内容
$array = [];
foreach (['api', 'web'] as $type) {
$content = file_exists("original-{$type}.txt") ? file_get_contents("original-{$type}.txt") : "";
$array = array_merge($array, array_values(array_filter(array_unique(explode("\n", $content)))));
}
// 判定是否存在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 $tmp) {
if (!isset($tmp['key'])) {
continue;
}
$key = $tmp['key'];
$translations[$key] = $tmp;
if (in_array($key, $array)) {
$count = substr_count($key, '(*)');
if ($count > 0) {
foreach ($tmp as $k => $v) {
if ($k == 'zh' || $k == 'key') {
continue;
}
if ($count != substr_count($v, '(*)')) {
$regrror[$key] = $tmp;
continue 2;
}
}
}
} else {
$redundants[$key] = $tmp;
}
}
foreach ($array as $text) {
$key = trim($text);
if (!isset($translations[$key])) {
$needs[$key] = $key;
}
}
if (count($regrror) > 0) {
print_r("regrror:\n");
print_r(implode("\n", array_keys($regrror)));
print_r("\n\n");
}
if (count($redundants) > 0) {
print_r("redundants:\n");
print_r(implode("\n", array_keys($redundants)));
print_r("\n\n");
}
if (count($needs) > 0) {
print_r("needs:\n");
print_r(implode("\n", array_keys($needs)));
print_r("\n\n");
}
print_r([
'translate_count' => count($translations),
'regerror_count' => count($regrror),
'redundant_count' => count($redundants),
'need_count' => count($needs),
]);

File diff suppressed because it is too large Load Diff

View File

@ -1,316 +1,281 @@
<?php
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
require_once ("config.php");
require __DIR__ . '/vendor/autoload.php';
/**
* 有道翻译类
*/
class Youdao
{
const URL = "https://openapi.youdao.com/api";
const CURL_TIMEOUT = 10000;
use Orhanerday\OpenAi\OpenAi;
private string $APP_KEY = '';
private string $SEC_KEY = '';
require_once("config.php");
/**
* @param $APP_KEY
* @param $SEC_KEY
*/
public function __construct($APP_KEY, $SEC_KEY)
{
$this->APP_KEY = $APP_KEY;
$this->SEC_KEY = $SEC_KEY;
}
/**
* 翻译
* @param $q
* @param $from
* @param $to
* @param $successSleep
* @return mixed|null
* @throws Exception
*/
public function translate($q, $from = null, $to = null, $successSleep = 1)
{
if ($from === null) {
$from = 'auto';
}
if ($to === null) {
$to = $from;
$from = 'auto';
}
$salt = $this->create_guid();
$args = array(
'q' => $q,
'appKey' => $this->APP_KEY,
'salt' => $salt,
);
$args['from'] = $from;
$args['to'] = $to;
$args['signType'] = 'v3';
$curtime = strtotime("now");
$args['curtime'] = $curtime;
$signStr = $this->APP_KEY . $this->truncate($q) . $salt . $curtime . $this->SEC_KEY;
$args['sign'] = hash("sha256", $signStr);
// $args['vocabId'] = '您的用户词表ID';
$data = $this->call(self::URL, $args);
$res = json_decode($data, true);
if ($res['errorCode'] == 0 && $res['translation']) {
sleep($successSleep);
return is_array($res['translation']) ? $res['translation'][0] : $res['translation'];
}
file_put_contents('error.log', $data . "\n", FILE_APPEND);
throw new Exception("翻译失败详细查看error.log\n\n");
return null;
}
private function call($url, $args = null, $method = "post", $testflag = 0, $timeout = self::CURL_TIMEOUT, $headers = array())
{
$ret = false;
$i = 0;
while ($ret === false) {
if ($i > 1)
break;
if ($i > 0) {
sleep(1);
}
$ret = $this->callOnce($url, $args, $method, false, $timeout, $headers);
$i++;
}
return $ret;
}
private function callOnce($url, $args = null, $method = "post", $withCookie = false, $timeout = self::CURL_TIMEOUT, $headers = array())
{
$ch = curl_init();
$data = $this->convert($args);
if ($method == "post") {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_POST, 1);
} else {
if ($data) {
if (stripos($url, "?") > 0) {
$url .= "&$data";
} else {
$url .= "?$data";
}
}
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
if ($withCookie) {
curl_setopt($ch, CURLOPT_COOKIEJAR, $_COOKIE);
}
$r = curl_exec($ch);
curl_close($ch);
return $r;
}
private function convert($args)
{
$data = '';
if (is_array($args)) {
foreach ($args as $key => $val) {
if (is_array($val)) {
foreach ($val as $k => $v) {
$data .= $key . '[' . $k . ']=' . rawurlencode($v) . '&';
}
} else {
$data .= "$key=" . rawurlencode($val) . "&";
}
}
return trim($data, "&");
}
return $args;
}
private function create_guid()
{
$microTime = microtime();
list($a_dec, $a_sec) = explode(" ", $microTime);
$dec_hex = dechex($a_dec * 1000000);
$sec_hex = dechex($a_sec);
$this->ensure_length($dec_hex, 5);
$this->ensure_length($sec_hex, 6);
$guid = $dec_hex;
$guid .= $this->create_guid_section(3);
$guid .= '-';
$guid .= $this->create_guid_section(4);
$guid .= '-';
$guid .= $this->create_guid_section(4);
$guid .= '-';
$guid .= $this->create_guid_section(4);
$guid .= '-';
$guid .= $sec_hex;
$guid .= $this->create_guid_section(6);
return $guid;
}
private function create_guid_section($characters)
{
$return = "";
for ($i = 0; $i < $characters; $i++) {
$return .= dechex(mt_rand(0, 15));
}
return $return;
}
private function truncate($q)
{
$len = $this->abslength($q);
return $len <= 20 ? $q : (mb_substr($q, 0, 10) . $len . mb_substr($q, $len - 10, $len));
}
private function abslength($str)
{
if (empty($str)) {
return 0;
}
if (function_exists('mb_strlen')) {
return mb_strlen($str, 'utf-8');
} else {
preg_match_all("/./u", $str, $ar);
return count($ar[0]);
}
}
private function ensure_length(&$string, $length)
{
$strlen = strlen($string);
if ($strlen < $length) {
$string = str_pad($string, $length, "0");
} else if ($strlen > $length) {
$string = substr($string, 0, $length);
}
}
// 读取所有要翻译的内容
$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);
}
try {
// 译文
$translations = [];
if (file_exists( "translate.json")) {
$tmps = json_decode(file_get_contents("translate.json"), true);
foreach ($tmps as $tmp) {
if (!isset($tmp['key'])) {
continue;
}
$translations[$tmp['key']] = $tmp;
}
// 判定是否存在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;
}
foreach (['api', 'web'] as $type) {
// 读取文件
$content = file_exists("original-{$type}.txt") ? file_get_contents("original-{$type}.txt") : "";
$array = array_values(array_filter(array_unique(explode("\n", $content))));
// 提取要翻译的
$datas = [];
$needs = [];
foreach ($array as $text) {
$text = trim($text);
if ($tmp = json_decode($text, true)) {
$key = key($tmp);
$value = current($tmp);
} else {
$key = $value = $text;
}
if (isset($translations[$key])) {
$datas[] = $translations[$key];
} else {
$needs[$key] = $value;
}
}
$waits = array_chunk($needs, 200, true);
// 分组翻译
$YD = new Youdao(YOUDAO_APP_KEY, YOUDAO_SEC_KEY);
$func = function($text) {
if (!$text) {
return null;
}
return preg_replace_callback("/^\(\*\)\s*[A-Z]/", function ($match) {
return strtolower($match[0]);
}, ucfirst($text));
};
foreach ($waits as $items) {
$text = implode("\n", $items);
$TCS = explode("\n", $YD->translate($text, "zh-CHS", "zh-CHT")); // 繁体
$ENS = explode("\n", $YD->translate($text, "zh-CHS", "en")); // 英语
$KOS = explode("\n", $YD->translate($text, "zh-CHS", "ko")); // 韩语
$JAS = explode("\n", $YD->translate($text, "zh-CHS", "ja")); // 日语
$DES = explode("\n", $YD->translate($text, "zh-CHS", "de")); // 德语
$FRS = explode("\n", $YD->translate($text, "zh-CHS", "fr")); // 法语
$IDS = explode("\n", $YD->translate($text, "zh-CHS", "id")); // 印度尼西亚
$RUS = explode("\n", $YD->translate($text, "zh-CHS", "ru")); // 俄语
$index = 0;
foreach ($items as $key => $item) {
$tmp = [];
$tmp['key'] = $key;
$tmp['zh'] = $key != $item ? $item : "";
$tmp["zh-CHT"] = $func($TCS[$index]);
$tmp["en"] = $func($ENS[$index]);
$tmp["ko"] = $func($KOS[$index]);
$tmp["ja"] = $func($JAS[$index]);
$tmp["de"] = $func($DES[$index]);
$tmp["fr"] = $func($FRS[$index]);
$tmp["id"] = $func($IDS[$index]);
$tmp["ru"] = $func($RUS[$index]);
$datas[] = $translations[$key] = $tmp;
$index++;
}
}
// 按长度排序
$inOrder = [];
foreach ($datas as $index => $item) {
$key = $item['key'];
if (str_contains($key, '(*)')) {
$inOrder[$index] = strlen($key);
} else {
$inOrder[$index] = strlen($key) + 10000000000;
}
}
array_multisort($inOrder, SORT_DESC, $datas);
// 合成数组
$results = ['key' => []];
$index = 0;
foreach ($datas as $items) {
$results['key'][$items['key']] = $index++;
foreach ($items as $key => $item) {
if ($key === 'key') {
$currentKey = $obj['key'];
$originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $currentKey);
$translations[$originalKey] = $obj;
if (!in_array($originalKey, $originals)) {
// 多余的数据
$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 (!isset($results)) {
$results[$key] = [];
if (!str_contains($v, $match)) {
// 正则匹配错误
$regrror[$originalKey] = [
$k => $v,
'match' => $match,
'key' => $currentKey,
];
continue 2;
}
$results[$key][] = $item;
}
}
// 生成文件
if ($type === 'api') {
if (!is_dir("../public/language/api")) {
mkdir("../public/language/api", 0777, true);
}
foreach ($results as $key => $item) {
$file = "../public/language/api/$key.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 $key => $item) {
$file = "../public/language/web/$key.js";
file_put_contents($file, "if(typeof window.LANGUAGE_DATA===\"undefined\")window.LANGUAGE_DATA={};window.LANGUAGE_DATA[\"{$key}\"]=" . json_encode($item, JSON_UNESCAPED_UNICODE));
}
}
print_r("[$type] translate success\ntotal: " . count($results['key']) . "\nadd: " . count($needs) . "\n\n");
}
file_put_contents("translate.json", json_encode(array_values($translations), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");
} catch (Exception $e) {
print_r("[$type] error, " . $e->getMessage());
}
if (count($regrror) > 0) {
print_r("正则匹配错误的数据:\n");
print_r($regrror);
exit();
}
if (count($redundants) > 0) {
print_r("多余的数据:\n");
print_r($redundants);
exit();
}
// 需要翻译的数据
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(OPEN_AI_KEY);
$openAi->setProxy(OPEN_AI_PROXY);
$result = $openAi->chat([
'model' => 'gpt-4o',
'messages' => [
[
"role" => "system",
"content" => <<<EOF
你是一个专业的翻译器,翻译的结果尽量符合 “项目任务管理系统” 的使用,请将提供的内容按每行一个翻译成:
```json
[
{
"key": "", // 原文本
"zh": "", // 留空(不用翻译)
"zh-CHT": "", // 繁体中文
"en": "", // 英语
"ko": "", // 韩语
"ja": "", // 日语
"de": "", // 德语
"fr": "", // 法语
"id": "", // 印度尼西亚语
"ru": "" // 俄语
}
]
```
请注意:(%T1)(%T2)(%T3)(%M1)(%M2) ...... 这类以 `小括号(%+内容)` 的字符组合是一个变量,翻译时请保留。
例子1
原文:此(%T1)已经处于【(%T2)】共享文件夹中,无法重复共享。
翻译成英语This (%T1) is already in the (%T2) shared folder and cannot be shared again。
例子2
原文:(%T1)的周报[(%T2)][(%T3)月第(%T4)]
翻译成英语Weekly report of (%T1) [(%T2)] [(Week (%T4) of (%T3) month)]
例子3
原文:(%T1)提交的「(%M2)」待你审批
翻译成英语:'(%M2)' submitted by (%T1) is waiting for your approval
例子4
原文:您发起的「(%M1)」已通过
翻译成英语The '(%M1)' you initiated has been approved
EOF,
],
[
"role" => "user",
"content" => $content,
],
],
'temperature' => 1.0,
'max_tokens' => 4000,
'frequency_penalty' => 0,
'presence_penalty' => 0,
]);
// 处理结果
$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");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,12 +2,57 @@ const utils = require('./utils')
const languageList = utils.languageList
const languageName = utils.getLanguage()
const languageRege = {}
const languageCache = new Map();
const languageRegex = [];
if (typeof window.LANGUAGE_DATA === "undefined") {
window.LANGUAGE_DATA = {}
}
/**
* 初始化语言需在加载完语言文件后调用
*/
function initLanguage() {
if (typeof window.LANGUAGE_DATA === "undefined" || typeof window.LANGUAGE_DATA["key"] === "undefined") {
return
}
const keys = window.LANGUAGE_DATA['key'] || []
delete window.LANGUAGE_DATA['key'];
//
keys.forEach((key, index) => {
if (/\(%[TM]\d+\)/.test(key)) {
// 处理复杂的键值
const _m = {};
const translation = {
key: new RegExp("^" + utils.replaceEscape(key) + "$"),
}
for (let language in window.LANGUAGE_DATA) {
if (typeof languageList[language] === "undefined") {
continue
}
translation[language] = window.LANGUAGE_DATA[language][index]
?.replace(/\(%([TM])(\d+)\)/g, function (_, type, index) {
if (type === 'M') {
_m[index] = index;
}
return "$" + index;
});
}
translation._m = Object.keys(_m);
languageRegex.push(translation)
} else {
// 缓存简单的键值
for (let language in window.LANGUAGE_DATA) {
if (typeof languageList[language] === "undefined") {
continue
}
const result = window.LANGUAGE_DATA[language][index] || key
languageCache.set(`${key}-${language}`, result);
}
}
})
}
/**
* 添加语言数据
* @param data
@ -16,17 +61,24 @@ function addLanguage(data) {
if (!$A.isArray(data)) {
return
}
const keys = Object.assign(Object.keys(languageList))
data.some(item => {
let index = -1;
item.key && keys.some(key => {
const value = item[key] || item['general'] || null
if (value && typeof window.LANGUAGE_DATA[key] !== "undefined") {
index = window.LANGUAGE_DATA[key].push(value) - 1
data.forEach(item => {
const {key, general} = item
if (!key) {
return
}
if (general) {
for (let language in window.LANGUAGE_DATA) {
if (typeof languageList[language] === "undefined") {
continue
}
languageCache.set(`${key}-${language}`, general);
}
})
if (index > -1) {
window.LANGUAGE_DATA['key'][item.key] = index
}
for (let language in item) {
if (language === 'key' || language === 'general') {
continue
}
languageCache.set(`${key}-${language}`, item[language]);
}
})
}
@ -65,68 +117,56 @@ function getLanguage() {
/**
* 转换语言
* @param text
* @param inputString
* @returns {string|*}
*/
function switchLanguage(text) {
if (typeof arguments[1] !== "undefined") {
return switchLanguage(utils.replaceArgumentsLanguage(text, arguments))
function switchLanguage(inputString) {
if (typeof inputString !== "string" || !inputString) {
return inputString
}
if (typeof text !== "string" || !text) {
return text
// 读取缓存
const cacheKey = `${inputString}-${languageName}`;
if (languageCache.has(cacheKey)) {
return languageCache.get(cacheKey);
}
//
if (typeof window.LANGUAGE_DATA === "undefined"
|| typeof window.LANGUAGE_DATA["key"] === "undefined"
|| typeof window.LANGUAGE_DATA[languageName] === "undefined") {
return text
}
const index = window.LANGUAGE_DATA["key"][text] || -1
if (index > -1) {
return window.LANGUAGE_DATA[languageName][index] || text
}
if (typeof languageRege[text] === "undefined") {
languageRege[text] = false
for (let key in window.LANGUAGE_DATA["key"]) {
if (key.indexOf("(*)") > -1) {
const rege = new RegExp("^" + utils.replaceEscape(key) + "$", "g")
if (rege.test(text)) {
let j = 0
const index = window.LANGUAGE_DATA["key"][key]
const value = (window.LANGUAGE_DATA[languageName][index] || key)?.replace(/\(\*\)/g, function () {
return "$" + (++j)
})
languageRege[text] = {rege, value}
break
// 正则匹配
for (const translation of languageRegex) {
const { key, _m } = translation;
const match = key.exec(inputString);
if (match && translation[languageName]) {
const result = translation[languageName].replace(/\$(\d+)/g, (_, index) => {
if (_m.includes(index)) {
return switchLanguage(match[index]);
}
}
return match[index] || '';
});
languageCache.set(cacheKey, result);
return result;
}
}
if (languageRege[text]) {
return text.replace(languageRege[text].rege, languageRege[text].value)
}
// 开发模式下,未翻译的文本自动添加到语言文件
if (window.systemInfo.debug === "yes") {
setTimeout(_ => {
try {
let key = '__language:Undefined__'
let languageTmp = JSON.parse(window.localStorage.getItem(key) || '[]')
let cacheKey = '__language:Undefined__'
let languageTmp = JSON.parse(window.localStorage.getItem(cacheKey) || '[]')
if (!$A.isArray(languageTmp)) {
languageTmp = []
}
let tmpRege = null
let tmpData = languageTmp.find((val) => {
tmpRege = new RegExp("^" + val.replace(/\(\*\)/g, "(.*?)") + "$", "g")
return !!text.match(tmpRege)
})
if (!tmpData) {
languageTmp.push(text)
window.localStorage.setItem(key, JSON.stringify(languageTmp))
if (languageTmp.findIndex(item => item == inputString) === -1) {
languageTmp.push(inputString)
window.localStorage.setItem(cacheKey, JSON.stringify(languageTmp))
}
} catch (e) {
}
} catch (e) { }
}, 10)
}
return text
// 未匹配返回原始字符串
languageCache.set(cacheKey, inputString);
return inputString;
}
export {languageName, languageList, addLanguage, setLanguage, getLanguage, switchLanguage}
export {languageName, languageList, addLanguage, setLanguage, initLanguage, getLanguage, switchLanguage}

View File

@ -42,7 +42,10 @@ export default {
if (!val || val == '') {
return '';
}
return val.replace(/\(\*\)/g, "~%~").replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&').replace(/~%~/g, '(.*?)');
return val
.replace(/\(%[TM]\d+\)/g, "~:%%:~")
.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
.replace(/~:%%:~/g, '(.*?)');
},
/**

View File

@ -1,6 +1,6 @@
<template>
<div class="open-approve-details" :data-id="msg.data.id">
<b>{{ $L(`${msg.data.comment_nickname} 评论了 ${msg.data.nickname} 的「${$L(msg.data.proc_def_name)}」审批`) }}</b>
<b>{{ $L(`${msg.data.comment_nickname} 评论了 ${msg.data.nickname} 的「${msg.data.proc_def_name}」审批`) }}</b>
<div class="cause">
<p>{{$L('申请人')}}<span class="mark-color">@{{ msg.data.nickname }}</span> {{ msg.data.department }}</p>
<b>{{$L('评论内容')}}</b>

View File

@ -1,6 +1,6 @@
<template>
<div class="open-approve-details" :data-id="msg.data.id">
<b>{{ $L(`抄送 ${msg.data.nickname} 提交的「${$L(msg.data.proc_def_name)}」记录`) }}</b>
<b>{{ $L(`抄送 ${msg.data.nickname} 提交的「${msg.data.proc_def_name}」记录`) }}</b>
<div class="cause">
<p>{{$L("申请人")}}<span class="mark-color">@{{ msg.data.nickname }}</span> {{ msg.data.department }}</p>
<b>{{$L("详情")}}</b>

View File

@ -1,6 +1,6 @@
<template>
<div class="open-approve-details" :data-id="msg.data.id">
<b>{{ $L(`${msg.data.nickname} 提交的「${$L(msg.data.proc_def_name)}」待你审批`) }}</b>
<b>{{ $L(`${msg.data.nickname} 提交的「${msg.data.proc_def_name}」待你审批`) }}</b>
<div class="cause">
<p>{{$L("申请人")}}<span class="mark-color">@{{ msg.data.nickname }}</span> {{ msg.data.department }}</p>
<b>{{$L("详情")}}</b>

View File

@ -27,7 +27,7 @@ export default {
},
computed: {
title({msg}) {
return msg.action === 'pass' ? `您发起的「${this.$L(msg.data.proc_def_name)}」已通过` : `您发起的「${this.$L(msg.data.proc_def_name)}」被 ${msg.data.nickname} 拒绝`
return msg.action === 'pass' ? `您发起的「${msg.data.proc_def_name}」已通过` : `您发起的「${msg.data.proc_def_name}」被 ${msg.data.nickname} 拒绝`
}
},
methods: {},

View File

@ -1,6 +1,6 @@
import {Store} from 'le5le-store';
import * as openpgp from 'openpgp_hi/lightweight';
import {languageList, languageName} from "../language";
import {initLanguage, languageList, languageName} from "../language";
import {$callData, $urlSafe, SSEClient} from './utils'
export default {
@ -60,6 +60,7 @@ export default {
`language/web/${languageName}.js`,
`language/iview/${languageName}.js`,
])
initLanguage()
resolve(action)
})