refactor(i18n): 完成前端国际化改造

This commit is contained in:
kuaifan 2026-07-20 02:35:07 +00:00
parent c9b3515aef
commit df6c1fc04d
87 changed files with 3670 additions and 1001 deletions

View File

@ -51,6 +51,7 @@ php .claude/skills/dootask-release/scripts/language.php diff
```
输出 JSON
- `formatErrorCount > 0`translate.json **已有条目**含 raw `(*)`/`(**)` key、字段结构错误、非法/不连续的参数编号或规范化重复 → **停止**,报告 `formatErrors`,交用户修复
- `regexErrorCount > 0`translate.json **已有条目**的占位符与某语言值不一致 → **停止**,报告 `regexErrors`,交用户修复(这是历史数据问题,不要自行猜测修改)
- `redundantCount > 0`translate.json 里有、但原文已删除的条目 → 仅作提示apply 时会自动剔除,不致命)
- `needsCount == 0`:无新文案 → **跳到 1.4 直接生成**
@ -82,7 +83,7 @@ php .claude/skills/dootask-release/scripts/language.php diff
php .claude/skills/dootask-release/scripts/language.php apply /tmp/dootask-release-translated.json
```
脚本会校验字段完整性与占位符完整性、追加新条目、剔除冗余项,并按项目原生格式写回 `translate.json`。任一条不合格会报错停止,按提示修正翻译后重试。
脚本会校验字段完整性与占位符完整性、追加新条目、剔除冗余项,并按项目原生格式写回 `translate.json`参数化 key 必须使用 `(%T1)`/`(%M1)` 形式,禁止输入 raw `(*)`/`(**)`;编号须按出现顺序从 1 连续递增,各语言值可调整占位符顺序,但占位符的类型、编号和数量必须与 key 完全一致。输入 JSON 内相同或规范化后相同的 key 会被拒绝;已存在于 `translate.json` 的非待补项也会被拒绝,避免覆盖歧义。任一条不合格会明确报错停止,按提示修正翻译后重试。
**1.4 生成前端/后端语言文件**
@ -90,7 +91,7 @@ php .claude/skills/dootask-release/scripts/language.php apply /tmp/dootask-relea
php .claude/skills/dootask-release/scripts/language.php generate
```
`translate.json` 字节级重新生成 `public/language/web/*.js``public/language/api/*.json`(排序/转义与项目原生工具完全一致,正常情况下 diff 只包含本次新增条目)。
`translate.json` 字节级重新生成 `public/language/web/*.js``public/language/api/*.json`(排序/转义与项目原生工具完全一致,正常情况下 diff 只包含本次新增条目)。生成前会再次执行格式与占位符校验;存在 raw `(*)`/`(**)` key 或占位符错误时不会写入生成文件。
**1.5 报告**:用 `git status --short language public/language` 汇总本步改动,向用户报告新增了多少条翻译。

View File

@ -6,7 +6,8 @@
//
// 子命令:
// language.php diff
// —— 输出 JSONneeds(待翻译key 已转成 (%T1)/(%M1) 形式) / redundants(冗余,提示) / regexErrors(占位符错乱,致命)
// —— 输出 JSONneeds(待翻译key 已转成 (%T1)/(%M1) 形式) / redundants(冗余,提示)
// / formatErrors(raw 占位符、字段或编号错误,致命) / regexErrors(各语言占位符错乱,致命)
// language.php apply <translated.json>
// —— 把新翻译合并进 translate.json追加 + 剔除冗余),不生成 public 文件
// language.php generate
@ -41,43 +42,136 @@ function read_generateds(): array
return [$originals, $generateds];
}
// ---- 公共:构建 translations 映射normalizedKey -> obj并收集冗余/占位符错乱 ----
// ---- 公共:占位符与条目结构校验 ----
function parameter_tokens(string $value): array
{
preg_match_all('/\(%[TM]\d+\)/', $value, $matches);
return $matches[0];
}
function normalize_key(string $key): string
{
return preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $key);
}
function validate_entry(array $obj, string $location, bool $requireTranslations): array
{
$formatErrors = [];
$regexErrors = [];
foreach ($GLOBALS['LANG_FIELDS'] as $field) {
if (!array_key_exists($field, $obj)) {
$formatErrors[] = ['location' => "$location.$field", 'message' => "缺少字段 $field"];
} elseif (!is_string($obj[$field])) {
$formatErrors[] = ['location' => "$location.$field", 'message' => "字段 $field 必须是字符串"];
} elseif ($requireTranslations && $field !== 'key' && $field !== 'zh' && $obj[$field] === '') {
$formatErrors[] = ['location' => "$location.$field", 'message' => "字段 $field 不得为空"];
}
}
if (!isset($obj['key']) || !is_string($obj['key'])) {
return [$formatErrors, $regexErrors];
}
$key = $obj['key'];
if (preg_match('/\(\*{1,2}\)/', $key)) {
$formatErrors[] = [
'location' => "$location.key",
'message' => "key 不得包含 raw (*)/(**),必须使用 (%T1)/(%M1)$key",
];
}
$withoutValidParameters = preg_replace('/\(%[TM]\d+\)/', '', $key);
if (str_contains($withoutValidParameters, '(%')) {
$formatErrors[] = ['location' => "$location.key", 'message' => "存在非法参数占位符:$key"];
}
$keyTokens = parameter_tokens($key);
foreach ($keyTokens as $index => $token) {
preg_match('/\d+/', $token, $number);
if ((int)$number[0] !== $index + 1) {
$formatErrors[] = [
'location' => "$location.key",
'message' => "参数编号必须按出现顺序从 1 连续递增:$key",
];
break;
}
}
$expected = $keyTokens;
sort($expected);
foreach ($GLOBALS['LANG_FIELDS'] as $field) {
if ($field === 'key' || !isset($obj[$field]) || !is_string($obj[$field]) || $obj[$field] === '') {
continue;
}
$actual = parameter_tokens($obj[$field]);
sort($actual);
if ($actual !== $expected) {
$regexErrors[] = [
'location' => "$location.$field",
'key' => $key,
'field' => $field,
'value' => $obj[$field],
'expected' => $expected,
'actual' => $actual,
'message' => "参数占位符缺失、类型或编号不一致",
];
}
}
return [$formatErrors, $regexErrors];
}
function print_validation_errors(array $formatErrors, array $regexErrors): void
{
foreach ($formatErrors as $error) {
fwrite(STDERR, "格式错误 {$error['location']}{$error['message']}\n");
}
foreach ($regexErrors as $error) {
fwrite(STDERR, "占位符错误 {$error['location']}{$error['message']}key={$error['key']}\n");
}
}
// ---- 公共:构建 translations 映射normalizedKey -> obj并收集冗余/格式/占位符错乱 ----
function build_translations(array $originals): array
{
$translations = [];
$redundants = [];
$regrror = [];
$regexErrors = [];
$formatErrors = [];
if (!file_exists("translate.json")) {
fwrite(STDERR, "translate.json not exists\n");
exit(1);
}
$tmps = json_decode(file_get_contents("translate.json"), true);
foreach ($tmps as $obj) {
if (!isset($obj['key'])) {
if (!is_array($tmps)) {
$formatErrors[] = ['location' => 'translate.json', 'message' => '根数据必须是 JSON 数组'];
return [$translations, $redundants, $regexErrors, $formatErrors];
}
foreach ($tmps as $index => $obj) {
$location = "translate.json[index $index]";
if (!is_array($obj)) {
$formatErrors[] = ['location' => $location, 'message' => '条目必须是对象'];
continue;
}
[$entryFormatErrors, $entryRegexErrors] = validate_entry($obj, $location, false);
$formatErrors = array_merge($formatErrors, $entryFormatErrors);
$regexErrors = array_merge($regexErrors, $entryRegexErrors);
if (!isset($obj['key']) || !is_string($obj['key'])) {
continue;
}
$currentKey = $obj['key'];
$originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $currentKey);
$originalKey = normalize_key($currentKey);
if (!in_array($originalKey, $originals)) {
$redundants[$originalKey] = $obj;
continue;
}
$translations[$originalKey] = $obj;
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] = ['key' => $currentKey, 'field' => $k, 'value' => $v, 'match' => $match];
continue 2;
}
}
}
if (isset($translations[$originalKey])) {
$formatErrors[] = [
'location' => $location,
'message' => "规范化后 key 重复:$originalKey",
];
continue;
}
$translations[$originalKey] = $obj;
}
return [$translations, $redundants, $regrror];
return [$translations, $redundants, $regexErrors, $formatErrors];
}
// ---- 公共:由 translate.json + originals 重新生成 public 文件 ----
@ -127,7 +221,7 @@ function generate(array $generateds, array $translations): void
if ($cmd === 'diff') {
[$originals, $generateds] = read_generateds();
[$translations, $redundants, $regrror] = build_translations($originals);
[$translations, $redundants, $regexErrors, $formatErrors] = build_translations($originals);
// 需要翻译的数据(对齐 translate.php 150-169占位符按单一计数器编号
$needs = [];
@ -153,14 +247,16 @@ if ($cmd === 'diff') {
echo json_encode([
'needsCount' => count($needsOut),
'redundantCount' => count($redundants),
'regexErrorCount' => count($regrror),
'formatErrorCount' => count($formatErrors),
'regexErrorCount' => count($regexErrors),
'needs' => $needsOut,
'redundants' => array_keys($redundants),
'regexErrors' => array_values($regrror),
'formatErrors' => array_values($formatErrors),
'regexErrors' => array_values($regexErrors),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
if (count($regrror) > 0) {
exit(2); // 已有数据占位符错乱,需先修复
if (count($formatErrors) > 0 || count($regexErrors) > 0) {
exit(2); // 已有数据格式或占位符错乱,需先修复
}
exit(0);
}
@ -172,9 +268,10 @@ if ($cmd === 'apply') {
exit(1);
}
[$originals, $generateds] = read_generateds();
[$translations, $redundants, $regrror] = build_translations($originals);
if (count($regrror) > 0) {
fwrite(STDERR, "translate.json 已有条目占位符错乱,请先修复再发版。\n");
[$translations, $redundants, $regexErrors, $formatErrors] = build_translations($originals);
if (count($formatErrors) > 0 || count($regexErrors) > 0) {
print_validation_errors($formatErrors, $regexErrors);
fwrite(STDERR, "translate.json 已有条目格式或占位符错误,请先修复再发版。\n");
exit(2);
}
@ -183,45 +280,49 @@ if ($cmd === 'apply') {
fwrite(STDERR, "translated.json 必须是数组\n");
exit(1);
}
$added = 0;
foreach ($incoming as $raw) {
foreach ($GLOBALS['LANG_FIELDS'] as $f) {
if (!array_key_exists($f, $raw)) {
fwrite(STDERR, "新翻译缺字段 \"$f\"" . json_encode($raw, JSON_UNESCAPED_UNICODE) . "\n");
exit(1);
}
$originalSet = array_flip($originals);
$incomingSeen = [];
$itemsToAdd = [];
foreach ($incoming as $index => $raw) {
if (!is_array($raw)) {
fwrite(STDERR, "新翻译 translated.json[index $index] 必须是对象\n");
exit(1);
}
// 占位符完整性key 里每个 (%T1)/(%M1) 必须出现在每个非空语言值里
if (preg_match_all('/\(%[TM]\d+\)/', $raw['key'], $m)) {
foreach ($m[0] as $match) {
foreach ($GLOBALS['LANG_FIELDS'] as $f) {
if ($f === 'key' || $f === 'zh') {
continue;
}
if (empty($raw[$f])) {
continue;
}
if (!str_contains($raw[$f], $match)) {
fwrite(STDERR, "占位符 $match 在字段 \"$f\" 缺失:{$raw['key']}\n");
exit(1);
}
}
}
[$incomingFormatErrors, $incomingRegexErrors] = validate_entry($raw, "translated.json[index $index]", true);
if (count($incomingFormatErrors) > 0 || count($incomingRegexErrors) > 0) {
print_validation_errors($incomingFormatErrors, $incomingRegexErrors);
exit(1);
}
// 规范化:固定字段顺序 + zh 置空
$item = [];
foreach ($GLOBALS['LANG_FIELDS'] as $f) {
$item[$f] = $f === 'zh' ? '' : $raw[$f];
}
$originalKey = preg_replace(["/\(%T\d+\)/", "/\(%M\d+\)/"], ["(*)", "(**)"], $item['key']);
$originalKey = normalize_key($item['key']);
if (!isset($originalSet[$originalKey])) {
fwrite(STDERR, "新翻译 key 不在 original-web.txt/original-api.txt{$item['key']}\n");
exit(1);
}
if (isset($incomingSeen[$originalKey])) {
fwrite(STDERR, "新翻译输入内部重复translated.json[index {$incomingSeen[$originalKey]}] 与 translated.json[index $index] 规范化后均为「$originalKey」\n");
exit(1);
}
if (isset($translations[$originalKey])) {
fwrite(STDERR, "新翻译 key 已存在于 translate.json非待补项或存在覆盖歧义{$item['key']}(规范化:$originalKey\n");
exit(1);
}
$incomingSeen[$originalKey] = $index;
$itemsToAdd[$originalKey] = $item;
}
foreach ($itemsToAdd as $originalKey => $item) {
$translations[$originalKey] = $item;
$added++;
}
// array_values现有条目去冗余在前新条目追加在后
file_put_contents("translate.json", json_encode(array_values($translations), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
echo json_encode([
'added' => $added,
'added' => count($itemsToAdd),
'total' => count($translations),
'droppedRedundant' => count($redundants),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
@ -230,7 +331,12 @@ if ($cmd === 'apply') {
if ($cmd === 'generate') {
[$originals, $generateds] = read_generateds();
[$translations] = build_translations($originals);
[$translations, $redundants, $regexErrors, $formatErrors] = build_translations($originals);
if (count($formatErrors) > 0 || count($regexErrors) > 0) {
print_validation_errors($formatErrors, $regexErrors);
fwrite(STDERR, "translate.json 存在格式或占位符错误,已停止生成。\n");
exit(2);
}
generate($generateds, $translations);
exit(0);
}

View File

@ -36,10 +36,8 @@ jobs:
- name: ESLint
run: npm run lint
# 存量缺失文案 93 条(见 scripts/check-language.mjs 输出),清零后移除 continue-on-error 改为强制
- name: Language Check
run: npm run check:lang
continue-on-error: true
phpunit:
runs-on: ubuntu-latest

View File

@ -1,10 +1,5 @@
模版满足多种团队协作场景,同时支持自定义模版,满足团队个性化场景管理需求,可直观的查看项目的进展情况,团队协作更方便。
2、若你是团队的所有者请在删除您的帐号前转移所有权。例如该帐号所创建的项目可将项目移交他人或删除项目以及文件夹。
汇集文档、电子表格、思维笔记等多种在线工具,汇聚企业知识资源于一处,支持多人实时协同编辑,让团队协作更便捷。
多平台应用支持,打开客户端即可跟进项目任务进度, 同时让你在工作中每一个步骤都能拥有更高效愉悦的体验。
1、您将无法查看该帐号内的任何信息包括帐号信息、文件记录、聊天记录、项目信息、团队成员信息等。
通过灵活的任务日历,轻松安排每一天的日程,把任务拆解到每天,让工作目标更清晰,时间分配更合理。
针对项目和任务建立群组,工作问题可及时沟通,促进团队快速协作,提高团队工作效率。
帐号删除后,该帐号将无法正常登录且无法恢复,帐号下的所有数据也将被删除。
复杂大于或等于6个字符包含数字、字母大小写或者特殊字符。
不会向忽略的邮箱地址发送邮件,可使用换行分割多个地址。
@ -24,7 +19,6 @@
开启后可以直接使用 LDAP 帐号密码登录
必填:发送聊天内容前必须设置昵称。
移除成员负责的任务将变成无负责人,
首页底部:首页底部网站备案号等信息
为确保帐号安全,请确认是本人操作
修改邮箱和删除帐号需要邮箱验证码
关闭签到功能再开启需要重新安装。
@ -42,7 +36,6 @@
+ 输入子任务,回车添加子任务
4、请保证帐号未被暂停使用。
只能设置单个状态为验收/测试
以下是你当前的任务统计数据
可通过此链接直接加入项目。
项目不存在或不在成员列表内
简单大于或等于6个字符。
@ -54,14 +47,12 @@
任务列表不存在或已被删除
密码错误,请输入正确密码
开放:所有人都可以发言。
强大易用的协同创作云文档
注销前,请确认一下事项
签到前后时间收到消息通知
签到提醒对象3天内有签到的成员法定工作日
该状态下任务自动标记完成
输入您的信息以创建帐户。
仅支持Openwrt系统的路由器
便捷易用的项目管理模板
允许成员自己修改MAC地址
可设置多个状态为进行中
可通过此链接浏览文件。
@ -70,7 +61,6 @@
注意:离职操作不可逆!
请输入正确的邮箱地址!
项目负责人无法退出项目
高效便捷的团队沟通工具
以 http:// 或 https:// 开头
请开启您PHP环境的openssl
两次密码输入不一致!
@ -90,17 +80,13 @@
仅限项目负责人修改
仅限项目负责人删除
仅限项目负责人操作
任务描述,回车创建
你好,扫码确认登录
你确定要登出系统吗?
你确认领取任务吗?
列表名称,回车创建
同步修改子任务时间
子任务不支持此功能
最后在线于很久以前
注意此操作不可逆!
浏览图片空间的图片
清晰直观的任务日历
确认密码输入不一致
请先修改登录密码!
请填写正确的旧密码
@ -108,7 +94,6 @@
请输入正确的邀请码
请输入正确的验证码
请重新输入新密码!
轻量级任务管理工具
输入群名称(选填)
电话长度至少6位
不支持复制文件夹
@ -139,8 +124,6 @@
超出文件大小限制
输入您的电子邮件
选择任务协助人员
选择转发指定成员
选择转发最近聊天
邮箱、昵称、职位
请输入会议频道ID
第二次任务提醒
@ -158,9 +141,7 @@
帐号或密码错误
我的待完成任务
拖动到这里发送
支持多平台应用
文件格式不正确
最后在线于刚刚
最近打开的任务
未保存计划时间
正在上传文件...
@ -263,14 +244,9 @@ ID、名称、描述...
验证码已失效
使用 SSO 登录
PPT 演示文稿
同步滚动:关
同步滚动:开
Excel 工作表
html转markdown
上传文件夹
不是发送人
今日待完成
代码块主题
任务不存在
任务优先级
任务已完成
@ -296,7 +272,6 @@ html转markdown
工作流设置
已删除任务
已完成任务
已完成列表
已归档任务
开启工作流
归档的任务
@ -314,7 +289,6 @@ html转markdown
最晚可延后
服务器版本
未完成任务
未完成列表
未开放注册
未知的消息
标记未完成
@ -358,13 +332,13 @@ SMTP服务器
职位/职称
验收/测试
(*) 超期
未来 3 天内暂无到期任务
未来 (*) 天内暂无到期任务
暂无高优先级任务
所有任务均已分配负责人
当前筛选条件下暂无任务
可以切换上方条件查看其他任务
数据更新说明
页面统计数据在 60 秒内复用,重点关注任务列表除外。
页面统计数据在 (*) 秒内复用,重点关注任务列表除外。
上次更新:(*)
立即刷新
刷新成功
@ -407,14 +381,11 @@ SMTP服务器
允许修改
允许注册
全员群组
全屏查看
全屏编辑
全部文件
全部禁言
共享权限
共享设置
准备发布
分屏显示
创建时间
创建群组
创建项目
@ -575,7 +546,6 @@ SMTP服务器
紧凑经典
经典天盘
结束状态
缩小查看
群组设置
聊天昵称
聊天资料
@ -643,7 +613,6 @@ LDAP 地址
LDAP 端口
MD编辑器
Word 文档
导出XLSX
MAC地址
扫一扫
上个月
@ -663,9 +632,6 @@ MAC地址
到期后
天空蓝
子任务
导出CSV
导出TXT
导出XLS
已删除
已取消
已完成
@ -901,20 +867,17 @@ Pro版
(*)秒
(*)是一款轻量级的开源在线项目任务管理工具提供各类文档协作工具、在线思维导图、在线流程图、项目管理、任务分发、即时IM文件管理等工具。
(*)负责的部门、项目、任务和文件将移交给交接人;同时退出所有群(如果是群主则转让给交接人)
流转到【(*)】时,[任务负责人] 和 [项目管理员] 可以修改状态。
文件(*)格式不正确,请上传(*)格式的图片。
流转到【(*)】时添加状态负责人至任务负责人。
每个文件夹里最多只能创建(*)个文件或文件夹
文件(*)格式不正确,仅支持上传:(*)
正在进行帐号【(*)】MAC地址修改。
正在进行帐号【(*)】离职操作。
正在进行帐号【(*)】部门修改。
文件大小超限,最大限制:(*)KB
职位/职称最多只能设置(*)个字
任务描述最多只能设置(*)个字
你确定要归档项目【(*)】吗?
文件(*)太大,不能超过:(*)'
文件名称最多只能设置(*)个字
文件格式错误,限制类型:(*)
项目介绍最多只能设置(*)个字
@ -928,8 +891,6 @@ Pro版
消息内容最大不能超过(*)字
项目列表最多不能超过(*)个
项目名称不可以少于(*)个字
最多只能上传(*)张图片。
最多只能选择(*)张图片。
密码最多只能设置(*)位数
密码设置不能小于(*)位数
描述最多只能设置(*)个字
@ -1059,8 +1020,6 @@ Pro版
删除人员
删除的任务
你确定要还原删除吗?
转换成markdown
请输入html代码...
请输入License...
详细信息
域名
@ -1362,9 +1321,7 @@ APP 推送
你确定取消待办吗?
取消成功
请等待打包完成
选择一个项目查看更多任务
首页
无相关数据
权限设置
任务列权限
添加列
@ -1737,11 +1694,11 @@ WiFi签到延迟时长为±1分钟。
注意:此操作不可恢复,部门下的成员将移至默认部门。
维护中...
(*)评论了(*)的「(**)」审批
抄送(*)提交的「(**)」记录
(*)提交的「(**)」待你审批
您发起的「(**)」已通过
您发起的「(**)」被(*)拒绝
(*)评论了(*)的「(*)」审批
抄送(*)提交的「(*)」记录
(*)提交的「(*)」待你审批
您发起的「(*)」已通过
您发起的「(*)」被(*)拒绝
翻译
从不
@ -2351,7 +2308,6 @@ AI 消息助手
留空则不修改密码
职位
请输入电话号码
正在编辑帐号【ID:(*)】的信息。
编辑用户信息
AI任务分析
@ -2568,7 +2524,7 @@ AI任务分析
团队本周已完成 (*) 项
查看任务
涉及 (*) 人
3 天内到期
(*) 天内到期
本周完成
较上周
与上周持平
@ -2586,12 +2542,11 @@ AI任务分析
全部显示完毕
逾期 (*) 天
逾期 (*) 小时
夜深了,{username}
早上好,{username}
中午好,{username}
下午好,{username}
晚上好,{username}
演示数据,接口对接后可打开任务详情
夜深了,(*)
早上好,(*)
中午好,(*)
下午好,(*)
晚上好,(*)
服务器时间
暂无已超期任务
暂无今日到期任务
@ -2600,7 +2555,6 @@ AI任务分析
没有超期任务,保持住
今天只有这一项,处理完就轻松了
数量较多,仅显示最近 (*) 项,展开其余 (*) 项
太棒了,当前没有需要处理的任务
我的部门
待开始
暂无待开始任务
@ -2620,3 +2574,87 @@ AI任务分析
开启后在项目列表中显示额外的只读项目
选择团队范围
在项目列表中启用
+(*)位
(*)条回复
AgoraIO 声网
会话中断
确定要清空所有历史会话吗?
新窗口打开
组件加载失败!
没有更新描述。
已全选
已选部分
你的姓名
请输入你的姓名
会议中
未命名应用
应用 ID
应用名称
菜单标题
菜单位置
应用中心 - 常用
应用中心 - 管理
主导航
可见范围
仅管理员
图标地址
菜单 URL
背景颜色
保持激活状态
自动暗黑模式
沉浸式
透明背景
禁用作用域样式
可能要发的照片
其他任务
点击查看
处理
没有符合条件的数据
正常
暂无介绍
关联子任务
最多只能选择(*)个标签
来源
未知
分享时间
分享人
标题
任务状态
已还原
下个周期
LDAP 用户禁止修改邮箱
共(*)个
标签名称最多只能设置(*)个字
添加失败
未开启通知权限
使用端到端加密传输数据。
删除前,请确认以下事项:
已清楚风险,确定删除
只能输入字母或数字
授权
不支持单独查看此消息
报告详情
验证邮箱
您的邮箱已通过验证
今后您可以通过此邮箱重置您的帐号密码
链接已过期,已重新发送
请设置昵称
请设置联系电话
文件 (*) 上传失败 (*)
文件 (*) 格式不正确,请上传 jpg、jpeg、webp、gif、png 格式的图片。
文件 (*) 太大,不能超过:(*)
最多只能上传 (*) 张图片。
最多只能选择 (*) 张图片。
文件 (*) 上传失败,(*)
文件 (*) 格式不正确,仅支持上传:(*)
很久以前
@我的消息
确定要删除记录"(*)"吗?
添加(*)
共(*)个文件仅显示最新50个
正在进行帐号【ID:(*), (*)】离职操作。
(*) 负责的部门、项目、任务和文件将移交给交接人;同时退出所有群(如果是群主则转让给交接人)
正在编辑帐号【ID:(*), (*)】的信息。
打包下载(*)

File diff suppressed because it is too large Load Diff

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

@ -20,7 +20,7 @@ negative:
- 语言切换只影响当前账号,不会改其他成员
- 不影响其他用户给你发的消息(消息不会自动翻译,要单独用「消息翻译」功能)
- 某些插件 / 微应用可能未提供完整翻译,会回退到中文 / 英文
last_verified: v1.7.90
last_verified: v1.8.69
---
# 切换界面语言
@ -33,7 +33,7 @@ last_verified: v1.7.90
## 支持的语言
`resources/assets/js/language/` 下文件清单决定,常见:
`resources/assets/js/i18n/` 下文件清单决定,常见:
- 简体中文zh
- 繁体中文zh-CHT

View File

@ -104,7 +104,7 @@ import {mapState} from "vuex";
import emitter from "./store/events";
import AIAssistant from "./components/AIAssistant";
import UserDetail from "./pages/manage/components/UserDetail.vue";
import {languageName} from "./language";
import {languageName} from "./i18n";
export default {
mixins: [ctrlPressed],

View File

@ -4,7 +4,7 @@ const isSoftware = isElectron || isEEUIApp;
document.getElementById("app")?.setAttribute("data-preload", "false");
import {languageName, switchLanguage as $L} from "./language";
import {languageName, switchLanguage as $L} from "./i18n";
import {isLocalHost} from "./components/Replace/utils";
import './functions/common'

View File

@ -280,7 +280,7 @@ import FloatButton from "./float-button.vue";
import AssistantModal from "./modal.vue";
import PromptImage from "./prompt-image.vue";
import {buildWeakPrompt, renderWeakPromptText} from "./page-context";
import {getLanguage} from "../../language";
import {getLanguage} from "../../i18n";
import {getWelcomePrompts} from "./welcome-prompts";
export default {

View File

@ -5,7 +5,7 @@
* 提示内容基于 DooTask MCP 工具的实际能力设计
*/
import {languageName} from "../../language";
import {languageName} from "../../i18n";
// SVG 图标定义
const SVG_ICONS = {

View File

@ -37,7 +37,7 @@
<script>
import {mapState} from "vuex";
import IFrame from "../pages/manage/components/IFrame";
import {languageName} from "../language";
import {languageName} from "../i18n";
export default {
name: "Drawio",

View File

@ -212,7 +212,7 @@ export default {
if (type == 'day') {
return date.date();
} else if (type == 'week') {
return this.$L(`星期${'日一二三四五六'.charAt(date.day())}`);
return this.$L(['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][date.day()]);
} else {
return date;
}

View File

@ -293,7 +293,7 @@ export default {
} else {
$A.noticeWarning({
title: this.$L('上传失败'),
desc: this.$L('文件 ' + file.name + ' 上传失败 ' + res.msg),
desc: this.$L('文件 (*) 上传失败 (*)', file.name, res.msg),
});
this.$refs.upload.fileList.pop();
}
@ -307,14 +307,14 @@ export default {
//
$A.noticeWarning({
title: this.$L('文件格式不正确'),
desc: this.$L('文件 ' + file.name + ' 格式不正确,请上传 jpg、jpeg、webp、gif、png 格式的图片。')
desc: this.$L('文件 (*) 格式不正确,请上传 jpg、jpeg、webp、gif、png 格式的图片。', file.name)
});
},
handleMaxSize(file) {
//
$A.noticeWarning({
title: this.$L('超出文件大小限制'),
desc: this.$L('文件 ' + file.name + ' 太大,不能超过:' + $A.bytesToSize(this.maxImageSize * 1024))
desc: this.$L('文件 (*) 太大,不能超过:(*)', file.name, $A.bytesToSize(this.maxImageSize * 1024))
});
},
handleBeforeUpload(file) {
@ -325,7 +325,7 @@ export default {
check = this.uploadList.length < this.maxNum;
}
if (!check) {
$A.noticeWarning(this.$L('最多只能上传 ' + this.maxNum + ' 张图片。'));
$A.noticeWarning(this.$L('最多只能上传 (*) 张图片。', this.maxNum));
return false;
}
// 10MB iview max-size maxSize
@ -370,7 +370,7 @@ export default {
} catch (err) {
$A.noticeWarning({
title: this.$L('上传失败'),
desc: this.$L('文件 ' + rawFile.name + ' 上传失败 ' + ((err && err.message) || '')),
desc: this.$L('文件 (*) 上传失败 (*)', rawFile.name, (err && err.message) || ''),
});
const idx = this.$refs.upload.fileList.indexOf(item);
if (idx > -1) this.$refs.upload.fileList.splice(idx, 1);
@ -452,7 +452,7 @@ export default {
}
let check = this.uploadList.length < this.maxNum;
if (!check) {
$A.noticeWarning(this.$L('最多只能选择 ' + this.maxNum + ' 张图片。'));
$A.noticeWarning(this.$L('最多只能选择 (*) 张图片。', this.maxNum));
return;
}
item.active = true;

View File

@ -153,7 +153,7 @@ import {DatePicker} from 'view-design-hi';
import microApp from '@micro-zoe/micro-app'
import DialogWrapper from '../../pages/manage/components/DialogWrapper.vue'
import UserSelect from "../UserSelect.vue";
import {languageList, languageName} from "../../language";
import {languageList, languageName} from "../../i18n";
import emitter from "../../store/events";
import TransferDom from "../../directives/transfer-dom";
import store from "../../store";

View File

@ -58,7 +58,7 @@
</style>
<script>
import {mapState} from "vuex";
import {languageName} from "../language";
import {languageName} from "../i18n";
export default {
name: "OnlyOffice",

View File

@ -5,7 +5,7 @@
<script>
import {mapState} from "vuex";
import PreviewImage from "./index";
import {languageName} from "../../language";
import {languageName} from "../../i18n";
export default {
name: 'PreviewImageState',

View File

@ -68,7 +68,7 @@
import tinymce from 'tinymce/tinymce';
import ImgUpload from "./ImgUpload";
import {mapState} from "vuex";
import {languageName} from "../language";
import {languageName} from "../i18n";
import {chunkedUpload, CHUNK_THRESHOLD} from "../store/chunkedUpload";
const windowTouch = "ontouchend" in document
@ -661,7 +661,7 @@ export default {
} else {
$A.noticeWarning({
title: this.$L('上传失败'),
desc: this.$L('文件 ' + file.name + ' 上传失败,' + res.msg)
desc: this.$L('文件 (*) 上传失败,(*)', file.name, res.msg)
});
}
},
@ -675,7 +675,7 @@ export default {
//
$A.noticeWarning({
title: this.$L('文件格式不正确'),
desc: this.$L('文件 ' + file.name + ' 格式不正确,仅支持上传:' + this.uploadFormat.join(','))
desc: this.$L('文件 (*) 格式不正确,仅支持上传:(*)', file.name, this.uploadFormat.join(','))
});
},
@ -683,7 +683,7 @@ export default {
//
$A.noticeWarning({
title: this.$L('超出文件大小限制'),
desc: this.$L('文件 ' + file.name + ' 太大,不能超过:' + $A.bytesToSize(this.maxSize * 1024))
desc: this.$L('文件 (*) 太大,不能超过:(*)', file.name, $A.bytesToSize(this.maxSize * 1024))
});
},
@ -708,7 +708,7 @@ export default {
} catch (err) {
$A.noticeWarning({
title: this.$L('上传失败'),
desc: this.$L('文件 ' + rawFile.name + ' 上传失败,' + ((err && err.message) || '')),
desc: this.$L('文件 (*) 上传失败,(*)', rawFile.name, (err && err.message) || ''),
});
} finally {
this.uploadIng--;

View File

@ -92,26 +92,26 @@ export default {
const now = $A.daytz()
const line = $A.dayjs(this.user.line_at)
const seconds = now.unix() - line.unix()
let stats = '最后在线于很久以前';
let stats = this.$L('最后在线于(*)', this.$L('很久以前'));
if (seconds < 60) {
stats = `最后在线于刚刚`
stats = this.$L('最后在线于(*)', this.$L('刚刚'))
} else if (seconds < 3600) {
stats = `最后在线于 ${Math.floor(seconds / 60)} 分钟前`
stats = this.$L('最后在线于(*)分钟前', Math.floor(seconds / 60))
} else if (seconds < 3600 * 6) {
stats = `最后在线于 ${Math.floor(seconds / 3600)} 小时前`
stats = this.$L('最后在线于(*)小时前', Math.floor(seconds / 3600))
} else {
const nowYmd = now.format('YYYY-MM-DD')
const lineYmd = line.format('YYYY-MM-DD')
const lineHi = line.format('HH:mm')
if (nowYmd === lineYmd) {
stats = `最后在线于今天 ${lineHi}`
stats = this.$L('最后在线于今天(*)', lineHi)
} else if (now.clone().subtract(1, 'day').format('YYYY-MM-DD') === lineYmd) {
stats = `最后在线于昨天 ${lineHi}`
stats = this.$L('最后在线于昨天(*)', lineHi)
} else if (seconds < 3600 * 24 * 365) {
stats = `最后在线于 ${lineYmd}`
stats = this.$L('最后在线于(*)', lineYmd)
}
}
this.$emit('update:online', this.$L(stats))
this.$emit('update:online', stats)
}
}
}

View File

@ -77,7 +77,7 @@ import '@kangc/v-md-editor/lib/theme/style/vuepress.css';
import Prism from 'prismjs';
// Language
import {languageName} from "../../../language";
import {languageName} from "../../../i18n";
import zhCN from '@kangc/v-md-editor/lib/lang/zh-CN';
import enUS from '@kangc/v-md-editor/lib/lang/en-US';

View File

@ -20,7 +20,7 @@ import '@kangc/v-md-editor/lib/theme/style/vuepress.css';
import Prism from 'prismjs';
// Language
import {languageName} from "../../../language";
import {languageName} from "../../../i18n";
import zhCN from '@kangc/v-md-editor/lib/lang/zh-CN';
import enUS from '@kangc/v-md-editor/lib/lang/en-US';

View File

@ -1,7 +1,7 @@
/**
* EEUI App 专用
*/
import {languageName} from "../language";
import {languageName} from "../i18n";
(function (window) {
const $ = window.$A;

View File

@ -165,17 +165,17 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
return [startSecond, $A.daytz().endOf('month').toDate()];
}
}, {
text: $A.L('3天'),
text: $A.L('(*)天', 3),
value() {
return [startSecond, $A.daytz().add(2, 'day').endOf('day').toDate()];
}
}, {
text: $A.L('5天'),
text: $A.L('(*)天', 5),
value() {
return [startSecond, $A.daytz().add(4, 'day').endOf('day').toDate()];
}
}, {
text: $A.L('7天'),
text: $A.L('(*)天', 7),
value() {
return [startSecond, $A.daytz().add(6, 'day').endOf('day').toDate()];
}

View File

@ -3,7 +3,7 @@ const utils = require('./utils')
const languageList = utils.languageList
const languageName = utils.getLanguage()
const languageCache = new Map();
const languageRegex = [];
const languageTemplateCache = new Map();
if (typeof window.LANGUAGE_DATA === "undefined") {
window.LANGUAGE_DATA = {}
@ -21,25 +21,21 @@ function initLanguage() {
//
keys.forEach((key, index) => {
if (/\(%[TM]\d+\)/.test(key)) {
// 处理复杂的键值
const _m = {};
const translation = {
key: new RegExp("^" + utils.replaceEscape(key) + "$"),
}
// 缓存参数化键值
const template = utils.normalizeArgumentsLanguage(key)
const translateArguments = new Set();
key.replace(/\(%M(\d+)\)/g, (_, index) => {
translateArguments.add(index)
})
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;
});
languageTemplateCache.set(`${template}-${language}`, {
text: window.LANGUAGE_DATA[language][index],
translateArguments,
});
}
translation._m = Object.keys(_m);
languageRegex.push(translation)
} else {
// 缓存简单的键值
for (let language in window.LANGUAGE_DATA) {
@ -122,39 +118,31 @@ function getLanguage() {
* @returns {string|*}
*/
function switchLanguage(inputString) {
if (typeof arguments[1] !== "undefined") {
inputString = utils.replaceArgumentsLanguage(inputString, arguments)
}
if (typeof inputString !== "string" || !inputString) {
return inputString
}
if (arguments.length > 1) {
const templateKey = `${inputString}-${languageName}`;
if (languageTemplateCache.has(templateKey)) {
const {text, translateArguments} = languageTemplateCache.get(templateKey);
if (!text) {
return utils.replaceArgumentsLanguage(inputString, arguments);
}
return text.replace(/\(%[TM](\d+)\)/g, (_, index) => {
const value = utils.getArgumentLanguage(arguments[index]);
return translateArguments.has(index) ? switchLanguage(String(value)) : value;
});
}
inputString = utils.replaceArgumentsLanguage(inputString, arguments)
}
// 读取缓存
const cacheKey = `${inputString}-${languageName}`;
if (languageCache.has(cacheKey)) {
return languageCache.get(cacheKey);
}
// 正则匹配
for (const translation of languageRegex) {
const { key, _m } = translation;
const match = key.exec(inputString);
if (match) {
if (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;
}
languageCache.set(cacheKey, inputString);
return inputString;
}
}
// 开发模式下,未翻译的文本自动添加到语言文件
if (window.systemInfo.debug === "yes") {
setTimeout(_ => {

View File

@ -22,30 +22,28 @@ export default {
*/
replaceArgumentsLanguage(text, objects) {
let j = 1;
while (text.indexOf("(*)") !== -1) {
if (typeof objects[j] === "object") {
text = text.replace("(*)", "");
} else {
text = text.replace("(*)", objects[j]);
}
j++;
}
return text;
return text.replace(/\(\*\)/g, () => this.getArgumentLanguage(objects[j++]));
},
/**
* 译文转义
* @param val
* 获取语言参数
* @param value
* @returns {string|*}
*/
replaceEscape(val) {
if (!val || val == '') {
getArgumentLanguage(value) {
if (value === null || typeof value === "undefined" || typeof value === "object") {
return '';
}
return val
.replace(/\(%[TM]\d+\)/g, "~:%%:~")
.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
.replace(/~:%%:~/g, '(.*?)');
return value;
},
/**
* 规范化参数化语言键
* @param text
* @returns {string}
*/
normalizeArgumentsLanguage(text) {
return text.replace(/\(%[TM]\d+\)/g, "(*)");
},
/**

View File

@ -166,7 +166,7 @@
<script>
import {mapState} from "vuex";
import {languageList, languageName, setLanguage} from "../language";
import {languageList, languageName, setLanguage} from "../i18n";
import VueQrcode from "@chenfengyuan/vue-qrcode";
import emitter from "../store/events";
@ -261,12 +261,12 @@ export default {
subTitle() {
const title = window.systemInfo.title || "DooTask";
if (this.loginMode == 'qrcode') {
return this.$L(`请使用${title}移动端扫描二维码。`)
return this.$L('请使用(*)移动端扫描二维码。', title)
}
if (this.loginType=='reg') {
return this.$L(`输入您的信息以创建帐户。`)
return this.$L('输入您的信息以创建帐户。')
}
return this.$L(`输入您的凭证以访问您的帐户。`)
return this.$L('输入您的凭证以访问您的帐户。')
},
loginText() {

View File

@ -262,7 +262,7 @@
<Icon v-else type="ios-search" />
</div>
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.eeuiAppKeyboardHide">
<Input type="search" v-model="projectKeyValue" :placeholder="$L(`共${projectTotal || cacheProjects.length}个项目,搜索...`)" clearable/>
<Input type="search" v-model="projectKeyValue" :placeholder="$L('共(*)个项目,搜索...', projectTotal || cacheProjects.length)" clearable/>
</Form>
</div>
<ButtonGroup class="manage-box-new-group">
@ -489,7 +489,7 @@ import notificationKoro from "notification-koro1";
import emitter from "../store/events";
import SearchBox from "../components/SearchBox.vue";
import transformEmojiToHtml from "../utils/emoji";
import {languageName} from "../language";
import {languageName} from "../i18n";
import {AINormalizeJsonContent, PROJECT_AI_SYSTEM_PROMPT, withLanguagePreferencePrompt} from "../utils/ai";
import Draggable from 'vuedraggable'
import DepartmentOwnerView from "./manage/components/DepartmentOwnerView.vue";
@ -535,7 +535,7 @@ export default {
addRule: {
name: [
{ required: true, message: this.$L('请填写项目名称!'), trigger: 'change' },
{ type: 'string', min: 2, message: this.$L('项目名称至少2个字'), trigger: 'change' }
{ type: 'string', min: 2, message: this.$L('项目名称至少(*)个字!', 2), trigger: 'change' }
]
},

View File

@ -271,7 +271,7 @@
:title="$L(mybotModifyData.id > 0 ? '修改机器人' : '添加机器人')"
:mask-closable="false">
<Form :model="mybotModifyData" v-bind="formOptions" @submit.native.prevent>
<Alert v-if="mybotModifyData.system_name" type="error" style="margin-bottom:18px">{{ $L(`正在修改系统机器人${mybotModifyData.system_name}`) }}</Alert>
<Alert v-if="mybotModifyData.system_name" type="error" style="margin-bottom:18px">{{ $L('正在修改系统机器人(*)', mybotModifyData.system_name) }}</Alert>
<FormItem prop="avatar" :label="$L('头像')">
<ImgUpload v-model="mybotModifyData.avatar" :num="1" :width="512" :height="512" whcut="cover"/>
</FormItem>
@ -396,7 +396,7 @@
:title="$L('扫码登录')"
:mask-closable="false">
<div class="mobile-scan-login-box">
<div class="mobile-scan-login-title">{{ $L(`你好,扫码确认登录`) }}</div>
<div class="mobile-scan-login-title">{{ $L('你好,扫码确认登录') }}</div>
<div class="mobile-scan-login-subtitle">{{ $L('为确保帐号安全,请确认是本人操作') }}</div>
</div>
<div slot="footer" class="adaption">

View File

@ -50,7 +50,7 @@ import 'tui-calendar-hi/toastui-calendar.css';
import Calendar from "./components/Calendar";
import {theme} from './components/Calendar/theme';
import emitter from "../../store/events";
import {addLanguage} from "../../language";
import {addLanguage} from "../../i18n";
import {mapGetters, mapState} from "vuex";
export default {
@ -95,15 +95,7 @@ export default {
{"key": "{五}", "zh": "五", "general": "Fri"},
{"key": "{六}", "zh": "六", "general": "Sat"},
]);
const dayNames = [
this.$L('{日}'),
this.$L('{一}'),
this.$L('{二}'),
this.$L('{三}'),
this.$L('{四}'),
this.$L('{五}'),
this.$L('{六}')
];
const dayNames = ['{日}', '{一}', '{二}', '{三}', '{四}', '{五}', '{六}'].map(day => this.$L(day));
this.options.week.dayNames = dayNames;
this.options.month.dayNames = dayNames;
this.options.view = this.$store.state.cacheCalendarView || this.options.view;

View File

@ -340,7 +340,7 @@ import TransferDom from "../../../../directives/transfer-dom";
import clickoutside from "../../../../directives/clickoutside";
import longpress from "../../../../directives/longpress";
import {inputLoadAdd, inputLoadIsLast, inputLoadRemove} from "./one";
import {languageList, languageName} from "../../../../language";
import {languageList, languageName} from "../../../../i18n";
import {isMarkdownFormat, MarkdownConver} from "../../../../utils/markdown";
import {cutText, extractPlainText} from "../../../../utils/text";
import {MESSAGE_AI_SYSTEM_PROMPT, withLanguagePreferencePrompt} from "../../../../utils/ai";

View File

@ -6,7 +6,7 @@
<Form ref="export" :model="formData" v-bind="formOptions" @submit.native.prevent>
<FormItem :label="$L('导出成员')">
<UserSelect v-model="formData.userid" :multiple-max="100" avatar-name show-disable :title="$L('请选择成员')"/>
<div class="form-tip">{{$L('每次最多选择导出100个成员')}}</div>
<div class="form-tip">{{$L('每次最多选择导出(*)个成员', 100)}}</div>
</FormItem>
<FormItem :label="$L('签到日期')">
<DatePicker

View File

@ -27,7 +27,7 @@
<div class="dialog-info">
<div class="dialog-name" v-html="transformEmojiToHtml(dialog.name)"></div>
<div class="dialog-meta">
<span class="member-count">{{$L('(*)人', dialog.people || 0)}}</span>
<span class="member-count">{{$L('(*) 人', dialog.people || 0)}}</span>
<span v-if="dialog.last_at" class="last-time">{{$A.timeFormat(dialog.last_at)}}</span>
</div>
</div>

View File

@ -19,7 +19,7 @@
<div class="card-link link-red">{{$L('查看任务')}} </div>
</li>
<li @click="onBlock('soon')">
<div class="card-label">{{$L('3 天内到期')}}</div>
<div class="card-label">{{$L('(*) 天内到期', 3)}}</div>
<div class="card-data">
<span class="card-num num-orange">{{blocks.due_soon || 0}}</span>
<span class="card-sub">{{$L('项')}}</span>
@ -302,7 +302,7 @@ export default {
chips({blocks, highPriorityChip}) {
return [
{type: 'overdue', label: this.$L('已超期'), num: blocks.overdue || 0},
{type: 'soon', label: this.$L('3 天内到期'), num: blocks.due_soon || 0},
{type: 'soon', label: this.$L('(*) 天内到期', 3), num: blocks.due_soon || 0},
{type: 'hi', label: highPriorityChip.label, num: highPriorityChip.num},
{type: 'noowner', label: this.$L('未分配负责人'), num: blocks.no_owner || 0},
]
@ -341,7 +341,7 @@ export default {
case 'overdue':
return this.$L('暂无已超期任务')
case 'soon':
return this.$L('未来 3 天内暂无到期任务')
return this.$L('未来 (*) 天内暂无到期任务', 3)
case 'hi':
return this.$L('暂无高优先级任务')
case 'noowner':

View File

@ -50,7 +50,7 @@
<div v-if="canKickMember(item)" class="user-exit" @click.stop="onExit(item)"><Icon type="md-exit"/></div>
</li>
<li class="label">
<span>{{$L(`群成员 (${userList.length}人)`)}}</span>
<span>{{$L('群成员 ((*)人)', userList.length)}}</span>
</li>
</template>
<li v-for="item in userList" @click="openUser(item.userid)">

View File

@ -33,7 +33,7 @@
<div class="initiate">
<span>{{ $L('由') }}</span>
<UserAvatar :userid="createId" :size="22" :showName="true"/>
<span> {{ $L('发起,参与接龙目前共'+num+'人') }}</span>
<span> {{ $L('发起,参与接龙目前共(*)人', num) }}</span>
</div>
<div class="textarea">
<Input ref="wordChainTextareaRef"

View File

@ -1,6 +1,6 @@
<template>
<div class="dialog-session-history">
<div class="session-history-title">{{$L('与 (*) 会话历史', sessionData.name)}}</div>
<div class="session-history-title">{{$L('与(*)会话历史', sessionData.name)}}</div>
<Scrollbar
ref="list"
class="session-history-list"

View File

@ -63,7 +63,7 @@
<li v-if="uindex < emojiUsersNum" :key="`emoji-user-li-${uindex}-${uitem}`">
<UserAvatar :userid="uitem" show-name :show-icon="false"/>
</li>
<li v-else-if="uindex == emojiUsersNum" :key="`emoji-user-more-${uindex}`">+{{item.userids.length - emojiUsersNum}}</li>
<li v-else-if="uindex == emojiUsersNum" :key="`emoji-user-more-${uindex}`">{{$L('+(*)位', item.userids.length - emojiUsersNum)}}</li>
</template>
</ul>
</div>
@ -75,7 +75,7 @@
<!--回复数-->
<div v-if="!hideReply && msgData.reply_num > 0" class="reply" @click="replyList">
<i class="taskfont">&#xe6eb;</i>
{{msgData.reply_num}}条回复
{{$L('(*)条回复', msgData.reply_num)}}
</div>
<!--标注-->
<div v-if="msgData.tag" class="tag" @click="openTag">

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} 的「${msg.data.proc_def_name}」审批`) }}</b>
<b>{{ $L('(*)评论了(*)的「(*)」审批', msg.data.comment_nickname, msg.data.nickname, $L(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} 提交的「${msg.data.proc_def_name}」记录`) }}</b>
<b>{{ $L('抄送(*)提交的「(*)」记录', msg.data.nickname, $L(msg.data.proc_def_name)) }}</b>
<div class="cause">
<p>{{$L("状态")}}<b>{{ msg.is_finished ? $L("已完成") : $L("审批中") }}</b></p>
<p>{{$L("申请人")}}<span class="mark-color">@{{ msg.data.nickname }}</span> {{ msg.data.department }}</p>

View File

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

View File

@ -1,6 +1,6 @@
<template>
<div class="open-approve-details" :data-id="msg.data.id">
<b>{{ $L(title) }}</b>
<b>{{ title }}</b>
<div class="cause">
<p>{{$L("状态")}}<b>{{ $L(statusText) }}</b></p>
<p>{{$L("申请人")}}<span class="mark-color">@{{ msg.data.start_nickname }}</span> {{ msg.data.department }}</p>
@ -23,7 +23,10 @@ export default {
},
computed: {
title({msg}) {
return msg.action === 'pass' ? `您发起的「${msg.data.proc_def_name}」已通过` : `您发起的「${msg.data.proc_def_name}」被 ${msg.data.nickname} 拒绝`
const name = this.$L(msg.data.proc_def_name)
return msg.action === 'pass'
? this.$L('您发起的「(*)」已通过', name)
: this.$L('您发起的「(*)」被(*)拒绝', name, msg.data.nickname)
},
statusText({msg}) {
switch (msg.action) {

View File

@ -4,7 +4,7 @@
<script>
import DialogMarkdown from "../../DialogMarkdown.vue";
import {languageName} from "../../../../../language";
import {languageName} from "../../../../../i18n";
export default {
components: {DialogMarkdown},

View File

@ -237,7 +237,7 @@
<div v-if="!isStaticMode && multiSelectMode" class="dialog-multi-select-bar">
<div class="multi-select-info">
<span>{{ $L('已选(*)条', selectedMsgIds.length) }}</span>
<span v-if="selectedMsgIds.length >= 100" class="multi-select-max">{{ $L('(最多100条)') }}</span>
<span v-if="selectedMsgIds.length >= 100" class="multi-select-max">{{ $L('(最多(*))', 100) }}</span>
</div>
<div class="multi-select-actions">
<Button type="primary" size="small" :disabled="selectedMsgIds.length === 0" @click="onMultiForward">{{ $L('转发') }}</Button>
@ -461,7 +461,7 @@
:title="$L('修改资料')"
:mask-closable="false">
<Form :model="modifyData" v-bind="formOptions" @submit.native.prevent>
<Alert v-if="modifyData.system_name" type="error" style="margin-bottom:18px">{{$L(`正在修改系统机器人${modifyData.system_name}`)}}</Alert>
<Alert v-if="modifyData.system_name" type="error" style="margin-bottom:18px">{{$L('正在修改系统机器人(*)', modifyData.system_name)}}</Alert>
<FormItem prop="avatar" :label="$L('头像')">
<ImgUpload v-model="modifyData.avatar" :num="1" :width="512" :height="512" whcut="cover"/>
</FormItem>
@ -743,7 +743,7 @@ import touchclick from "../../../directives/touchclick";
import longpress from "../../../directives/longpress";
import TransferDom from "../../../directives/transfer-dom";
import resizeObserver from "../../../directives/resize-observer";
import {languageList} from "../../../language";
import {languageList} from "../../../i18n";
import {isLocalHost} from "../../../components/Replace/utils";
import emitter from "../../../store/events";
import Forwarder from "./Forwarder/index.vue";
@ -1323,7 +1323,7 @@ export default {
if (unread_one && unread_one < startMsgId) {
array.push({
type: 'unread',
label: this.$L(`未读消息${not}`),
label: this.$L('未读消息(*)条', not),
msg_id: unread_one
})
}
@ -1331,7 +1331,7 @@ export default {
array.push(...mention_ids.map(msg_id => {
return {
type: 'mention',
label: this.$L(`@我的消息`),
label: this.$L('@我的消息'),
msg_id
}
}))
@ -3165,7 +3165,7 @@ export default {
} else if (this.selectedMsgIds.length < 100) {
this.selectedMsgIds.push(msgId);
} else {
$A.messageWarning(this.$L('最多选择100条消息'));
$A.messageWarning(this.$L('最多选择(*)条消息', 100));
}
},

View File

@ -217,7 +217,7 @@ export default {
const vNode = [
h('Poptip', {
props: {
title: this.$L(`确定要取消收藏"${params.row.name}"吗?`),
title: this.$L('确定要取消收藏"(*)"吗?', params.row.name),
confirm: true,
transfer: true,
placement: 'left',

View File

@ -309,7 +309,7 @@
<script>
import { mapState } from 'vuex';
import { languageName } from "../../../language";
import { languageName } from "../../../i18n";
export default {
name: "MCPHelper",

View File

@ -128,7 +128,7 @@ import DragBallComponent from "../../../../components/DragBallComponent";
import UserSelect from "../../../../components/UserSelect.vue";
import emitter from "../../../../store/events";
import {getErrorMessage} from "./utils";
import {languageName} from "../../../../language";
import {languageName} from "../../../../i18n";
export default {
name: "MeetingManager",

View File

@ -338,7 +338,7 @@ export default {
list.push({
id,
button: this.$L('重置'),
content: this.$L(`确定重置为【${name}】吗?`),
content: this.$L('确定重置为【(*)】吗?', name),
})
}
}

View File

@ -20,7 +20,7 @@
</li>
<template v-if="!(windowWidth <= 980 || projectData.cacheParameter.chat) && projectUser.length > 0" v-for="item in projectUser">
<li v-if="item.userid === -1" class="more">
<ETooltip :disabled="$isEEUIApp || windowTouch" :content="$L('共' + (projectData.project_user.length) + '个成员')">
<ETooltip :disabled="$isEEUIApp || windowTouch" :content="$L('共(*)个成员', projectData.project_user.length)">
<Icon type="ios-more"/>
</ETooltip>
</li>
@ -426,7 +426,7 @@
</RadioGroup>
<div v-if="settingData.archive_method==='system'" class="form-tip">{{$L('根据系统设置的自动归档规则执行')}}</div>
<template v-else-if="settingData.archive_method=='custom'">
<div class="form-tip">{{$L('任务完成 (*) 天后自动归档。', settingData.archive_days || 'n')}}</div>
<div class="form-tip">{{$L('任务完成(*)天后自动归档。', settingData.archive_days || 'n')}}</div>
<div class="setting-auto-day">
<Input v-model="settingData.archive_days" type="number">
<span slot="append">{{$L('天')}}</span>

View File

@ -51,7 +51,7 @@
<script>
import { mapState } from 'vuex'
import {languageName} from "../../../../language";
import {languageName} from "../../../../i18n";
import {systemTags} from "./utils";
import Tags from "./tags.vue";

View File

@ -109,7 +109,7 @@ export default {
newValue = this.value.filter(item => item.name !== tag.name);
} else {
if (this.max > 0 && this.value.length >= this.max) {
$A.messageWarning(this.$L('最多只能选择 (*) 个标签', this.max));
$A.messageWarning(this.$L('最多只能选择(*)个标签', this.max));
return;
}
newValue = [...this.value, { name: tag.name, color: tag.color }];

View File

@ -136,7 +136,7 @@ import {mapState} from 'vuex'
import Draggable from 'vuedraggable';
import VMPreviewNostyle from "../../../../components/VMEditor/nostyle.vue";
import AllTaskTemplates from "./templates";
import {languageName} from "../../../../language";
import {languageName} from "../../../../i18n";
export default {
name: 'ProjectTaskTemplate',

View File

@ -218,14 +218,14 @@
<Radio label="replace">{{$L('流转模式')}}</Radio>
<Radio label="merge">{{$L('剔除模式')}}</Radio>
</RadioGroup>
<div v-if="settingData.usertype=='replace'" class="form-tip">{{$L(`流转到${settingData.name}时改变任务负责人为状态负责人原本的任务负责人移至协助人员`)}}</div>
<div v-else-if="settingData.usertype=='merge'" class="form-tip">{{$L(`流转到【${settingData.name}】时改变任务负责人为状态负责人(并保留操作状态的人员),原本的任务负责人移至协助人员。`)}}</div>
<div v-else class="form-tip">{{$L(`流转到【${settingData.name}】时添加状态负责人至任务负责人。`)}}</div>
<div v-if="settingData.usertype=='replace'" class="form-tip">{{$L('流转到(*)时改变任务负责人为状态负责人原本的任务负责人移至协助人员', settingData.name)}}</div>
<div v-else-if="settingData.usertype=='merge'" class="form-tip">{{$L('流转到【(*)】时改变任务负责人为状态负责人(并保留操作状态的人员),原本的任务负责人移至协助人员。', settingData.name)}}</div>
<div v-else class="form-tip">{{$L('流转到【(*)】时添加状态负责人至任务负责人。', settingData.name)}}</div>
</FormItem>
<FormItem prop="userlimit" :label="$L('限制负责人')">
<iSwitch v-model="settingData.userlimit" :true-value="1" :false-value="0"/>
<div v-if="settingData.userlimit===1" class="form-tip">{{$L(`流转到${settingData.name}[任务负责人] [项目管理员] 可以修改状态`)}}</div>
<div v-else class="form-tip">{{$L(`流转到【${settingData.name}】时,[任务负责人] 和 [项目管理员] 可以修改状态。`)}}</div>
<div v-if="settingData.userlimit===1" class="form-tip">{{$L('流转到(*)[任务负责人] [项目管理员] 可以修改状态', settingData.name)}}</div>
<div v-else class="form-tip">{{$L('流转到【(*)】时,[任务负责人] 和 [项目管理员] 可以修改状态。', settingData.name)}}</div>
</FormItem>
</div>
</div>
@ -237,7 +237,7 @@
<Option v-for="(item, index) in columnList" :value="item.id" :key="index">{{ item.name }}</Option>
</Select>
<div class="form-tip">
{{$L(`流转到【${settingData.name}】时自动将任务移动至关联列表。`)}}
{{$L('流转到【(*)】时自动将任务移动至关联列表。', settingData.name)}}
<a v-if="settingData.columnid" href="javascript:void(0)" @click="settingData.columnid=0">{{$L('取消关联')}}</a>
</div>
</FormItem>

View File

@ -134,7 +134,7 @@ export default {
const actions = [
h('Poptip', {
props: {
title: this.$L(`确定要删除记录"${params.row.name || this.$L('未命名')}"吗?`),
title: this.$L('确定要删除记录"(*)"吗?', params.row.name || this.$L('未命名')),
confirm: true,
transfer: true,
placement: 'left',

View File

@ -9,7 +9,7 @@
:rows="1"
:autosize="{ minRows: 1, maxRows: 3 }"
:maxlength="255"
:placeholder="$L(typeName + '描述,回车创建')"
:placeholder="$L('(*)描述,回车创建', $L(typeName))"
enterkeyhint="done"
@on-focus="onFocus=true"
@on-blur="onFocus=false"
@ -30,7 +30,7 @@
</div>
</div>
<div class="add-btn" @click="openAdd">
<Icon class="add-icon" type="md-add" />{{$L('添加' + typeName)}}
<Icon class="add-icon" type="md-add" />{{$L('添加(*)', $L(typeName))}}
</div>
</Col>
<Col span="3"></Col>
@ -46,13 +46,13 @@
:rows="2"
:autosize="{ minRows: 2, maxRows: 3 }"
:maxlength="255"
:placeholder="$L(typeName + '描述,回车创建')"
:placeholder="$L('(*)描述,回车创建', $L(typeName))"
enterkeyhint="done"
@on-focus="onFocus=true"
@on-blur="onFocus=false"
@on-keydown="onKeydown"/>
<div class="add-placeholder" @click="openAdd">
<Icon type="md-add" />{{$L('添加' + typeName)}}
<Icon type="md-add" />{{$L('添加(*)', $L(typeName))}}
</div>
<div class="priority">
<ul>

View File

@ -304,7 +304,7 @@
<i class="taskfont">&#xe6e6;</i>{{$L('附件')}}
</div>
<ul class="item-content file">
<li v-if="taskDetail.file_num > 50" class="tip">{{$L(`${taskDetail.file_num}个文件仅显示最新50个`)}}</li>
<li v-if="taskDetail.file_num > 50" class="tip">{{$L('(*)个文件仅显示最新50个', taskDetail.file_num)}}</li>
<li v-for="(file, index) in fileList" :key="index" @click="showFileDropdown(file, $event)">
<img v-if="file.id" class="file-ext" :src="file.thumb"/>
<Loading v-else class="file-load"/>

View File

@ -6,7 +6,7 @@
<Form ref="exportTask" :model="formData" v-bind="formOptions" @submit.native.prevent>
<FormItem :label="$L('导出成员')">
<UserSelect v-model="formData.userid" :multiple-max="100" avatar-name show-disable :title="$L('请选择成员')"/>
<div class="form-tip">{{$L('每次最多选择导出100个成员')}}</div>
<div class="form-tip">{{$L('每次最多选择导出(*)个成员', 100)}}</div>
</FormItem>
<FormItem :label="$L('时间范围')">
<DatePicker

View File

@ -281,7 +281,7 @@
v-model="disableShow"
:title="$L('操作离职')">
<Form :model="disableData" v-bind="formOptions" @submit.native.prevent>
<Alert type="error" style="margin-bottom:18px">{{$L(`正在进行帐号【ID:${disableData.userid}, ${disableData.nickname}】离职操作。`)}}</Alert>
<Alert type="error" style="margin-bottom:18px">{{$L('正在进行帐号【ID:(*), (*)】离职操作。', disableData.userid, disableData.nickname)}}</Alert>
<FormItem :label="$L('离职时间')">
<DatePicker
ref="disableTime"
@ -296,7 +296,7 @@
<FormItem :label="$L('交接人')">
<UserSelect v-model="disableData.transfer_userid" :disabled-choice="[disableData.userid]" :multiple-max="1" :title="$L('选择交接人')"/>
<div class="form-tip">{{ $L('可选,留空则不执行迁移') }}</div>
<div class="form-tip">{{ $L(`${disableData.nickname} 负责的部门、项目、任务和文件将移交给交接人;同时退出所有群(如果是群主则转让给交接人)`) }}</div>
<div class="form-tip">{{ $L('(*) 负责的部门、项目、任务和文件将移交给交接人;同时退出所有群(如果是群主则转让给交接人)', disableData.nickname) }}</div>
</FormItem>
</Form>
<div slot="footer" class="adaption">
@ -1245,7 +1245,7 @@ export default {
$A.modalConfirm({
title: this.$L('同步部门成员'),
content: `<div>${this.$L(`你确定要同步部门成员吗?`)}</div><div style="color:#f00;font-weight:600">${this.$L(`注:此操作会同步子部门成员到当前部门`)}</div>`,
content: `<div>${this.$L('你确定要同步部门成员吗?')}</div><div style="color:#f00;font-weight:600">${this.$L('注:此操作会同步子部门成员到当前部门')}</div>`,
language: false,
loading: true,
onOk: () => {
@ -1272,7 +1272,7 @@ export default {
if (delItem) {
$A.modalConfirm({
title: this.$L('删除部门'),
content: `<div>${this.$L(`你确定要删除【${delItem.name}】部门吗?`)}</div><div style="color:#f00;font-weight:600">${this.$L(`注意:此操作不可恢复,部门下的成员将移至默认部门。`)}</div>`,
content: `<div>${this.$L('你确定要删除【(*)】部门吗?', delItem.name)}</div><div style="color:#f00;font-weight:600">${this.$L('注意:此操作不可恢复,部门下的成员将移至默认部门。')}</div>`,
language: false,
loading: true,
onOk: () => {

View File

@ -90,7 +90,7 @@
class="manage-tags-btn icon"
@click="onOpenTagsModal"
>
<Icon type="ios-settings-outline" /> 管理
<Icon type="ios-settings-outline" /> {{$L('管理')}}
</Button>
</div>
<div v-else class="tags-empty">

View File

@ -6,7 +6,7 @@
width="560">
<Form :model="formData" v-bind="formOptions" @submit.native.prevent>
<Alert type="warning" style="margin-bottom:18px">
{{ $L(`正在编辑帐号【ID:${userData.userid}, ${userData.nickname}】的信息。`) }}
{{ $L('正在编辑帐号【ID:(*), (*)】的信息。', userData.userid, userData.nickname) }}
</Alert>
<FormItem :label="$L('昵称')">
@ -98,7 +98,7 @@
<template v-if="checkinMode">
<FormItem :label="$L('人脸图片')" class="checkin-field">
<ImgUpload v-model="formData.faceimg" :num="1" :width="512" :height="512" whcut="cover"/>
<div class="form-tip">{{ $L('建议尺寸:500x500') }}</div>
<div class="form-tip">{{ $L('建议尺寸:(*)', '500x500') }}</div>
</FormItem>
<FormItem :label="$L('MAC地址')" class="checkin-field">

View File

@ -87,13 +87,13 @@
</div>
</div>
<div class="tag-meta-info" v-if="tag.created_by_name">
<span>{{$L('由 (*) 创建', tag.created_by_name)}}</span>
<span>{{$L('由(*)创建', tag.created_by_name)}}</span>
</div>
</li>
</ul>
</div>
<div v-if="total > 0" class="tag-modal-footer">
<span>{{$L('当前共 (*) 个标签', total)}}</span>
<span>{{$L('当前共(*)个标签', total)}}</span>
</div>
</div>
</ModalAlive>
@ -242,7 +242,7 @@ export default {
return;
}
if (name.length > 20) {
$A.messageError(this.$L('标签名称最多只能设置20个字'));
$A.messageError(this.$L('标签名称最多只能设置(*)个字', 20));
return;
}
if (this.pending.add) {
@ -288,7 +288,7 @@ export default {
return;
}
if (name.length > 20) {
$A.messageError(this.$L('标签名称最多只能设置20个字'));
$A.messageError(this.$L('标签名称最多只能设置(*)个字', 20));
return;
}
if (name === tag.name) {

View File

@ -30,7 +30,7 @@
trigger="click">
<div class="dashboard-cache-popover">
<strong>{{$L('数据更新说明')}}</strong>
<p>{{$L('页面统计数据在 60 秒内复用,重点关注任务列表除外。')}}</p>
<p>{{$L('页面统计数据在 (*) 秒内复用,重点关注任务列表除外。', 60)}}</p>
<span>{{$L('上次更新:(*)', teamStatsUpdatedTime)}}</span>
<Button
type="primary"
@ -585,24 +585,20 @@ export default {
},
dashboardHello({systemConfig, userInfo, nowTime}) {
let hello;
if (systemConfig.system_welcome) {
hello = systemConfig.system_welcome;
} else {
const hour = $A.daytz(nowTime).hour();
if (hour < 5) {
hello = '夜深了,{username}';
} else if (hour < 11) {
hello = '早上好,{username}';
} else if (hour < 14) {
hello = '中午好,{username}';
} else if (hour < 18) {
hello = '下午好,{username}';
} else {
hello = '晚上好,{username}';
}
return this.$L(systemConfig.system_welcome).replace(/\{username}/g, userInfo.nickname);
}
return this.$L(hello).replace(/\{username}/g, userInfo.nickname);
const hour = $A.daytz(nowTime).hour();
if (hour < 5) {
return this.$L('夜深了,(*)', userInfo.nickname);
} else if (hour < 11) {
return this.$L('早上好,(*)', userInfo.nickname);
} else if (hour < 14) {
return this.$L('中午好,(*)', userInfo.nickname);
} else if (hour < 18) {
return this.$L('下午好,(*)', userInfo.nickname);
}
return this.$L('晚上好,(*)', userInfo.nickname);
}
},

View File

@ -72,7 +72,7 @@
<div class="file-shear">
<span>{{$L('粘贴')}}</span>
<template v-show="showBtnText">"<em>{{shearFirst.name}}</em>"</template>
<span v-if="shearIds.length > 1">{{ $L(`${shearIds.length}个文件`) }}</span>
<span v-if="shearIds.length > 1">{{ $L('(*)个文件', shearIds.length) }}</span>
</div>
</Button>
<Button type="primary" size="small" @click="clearShear">{{ $L('取消剪切') }}</Button>
@ -2000,7 +2000,7 @@ export default {
$A.messageWarning("请等待打包完成");
return;
}
const name = this.$L(`打包下载${fileName}`)
const name = this.$L('打包下载(*)', fileName)
this.$store.dispatch("call", {
url: 'file/download/pack',
data: {ids, name},

View File

@ -155,8 +155,8 @@
</ul>
</li>
<li class="loaded">
<template v-if="contactsKey">{{$L('搜索到' + contactsFilter.length + '位联系人')}}</template>
<template v-else>{{$L('' + contactsTotal + '位联系人')}}</template>
<template v-if="contactsKey">{{$L('搜索到(*)位联系人', contactsFilter.length)}}</template>
<template v-else>{{$L('(*)位联系人', contactsTotal)}}</template>
</li>
</template>
<li v-else-if="contactsLoad == 0" class="nothing">

View File

@ -31,7 +31,7 @@
<Row class="setting-template">
<Col span="24">
<ImgUpload v-model="faceimgs" :num="1" :width="512" :height="512" whcut="cover"/>
<div class="form-tip">{{ $L('建议尺寸:500x500') }}</div>
<div class="form-tip">{{ $L('建议尺寸:(*)', '500x500') }}</div>
</Col>
</Row>
</TabPane>

View File

@ -146,7 +146,7 @@
<template v-if="formData.locat_bd_lbs_point.lng">
<div class="form-tip">
<a href="javascript:void(0)" @click="openMapSelect">
{{ $L(`经度:${formData.locat_bd_lbs_point.lng},纬度:${formData.locat_bd_lbs_point.lat},半径:${formData.locat_bd_lbs_point.radius}`) }}
{{ $L('经度:(*),纬度:(*),半径:(*)米', formData.locat_bd_lbs_point.lng, formData.locat_bd_lbs_point.lat, formData.locat_bd_lbs_point.radius) }}
</a>
</div>
<div class="form-tip" @click="openMapSelect">{{$L('点击修改允许签到位置')}}</div>
@ -165,7 +165,7 @@
<template v-if="formData.locat_amap_point.lng">
<div class="form-tip">
<a href="javascript:void(0)" @click="openMapSelect">
{{ $L(`经度:${formData.locat_amap_point.lng},纬度:${formData.locat_amap_point.lat},半径:${formData.locat_amap_point.radius}`) }}
{{ $L('经度:(*),纬度:(*),半径:(*)米', formData.locat_amap_point.lng, formData.locat_amap_point.lat, formData.locat_amap_point.radius) }}
</a>
</div>
<div class="form-tip" @click="openMapSelect">{{$L('点击修改允许签到位置')}}</div>
@ -184,7 +184,7 @@
<template v-if="formData.locat_tencent_point.lng">
<div class="form-tip">
<a href="javascript:void(0)" @click="openMapSelect">
{{ $L(`经度:${formData.locat_tencent_point.lng},纬度:${formData.locat_tencent_point.lat},半径:${formData.locat_tencent_point.radius}`) }}
{{ $L('经度:(*),纬度:(*),半径:(*)米', formData.locat_tencent_point.lng, formData.locat_tencent_point.lat, formData.locat_tencent_point.radius) }}
</a>
</div>
<div class="form-tip" @click="openMapSelect">{{$L('点击修改允许签到位置')}}</div>

View File

@ -7,7 +7,7 @@
v-bind="formOptions"
@submit.native.prevent>
<div class="block-setting-box">
<h3>AgoraIO 声网</h3>
<h3>{{$L('AgoraIO 声网')}}</h3>
<div class="form-box">
<FormItem :label="$L('会议功能')" prop="open">
<RadioGroup v-model="formData.open">

View File

@ -119,7 +119,7 @@
<span slot="append">{{$L('天')}}</span>
</Input>
</div>
<div slot="content">{{$L('任务完成 (*) 天后自动归档。', formDatum.archived_day || 'n')}}</div>
<div slot="content">{{$L('任务完成(*)天后自动归档。', formDatum.archived_day || 'n')}}</div>
</ETooltip>
</FormItem>
<FormItem :label="$L('可见性选项')" prop="taskVisible">
@ -292,9 +292,9 @@
</FormItem>
<FormItem :label="$L('欢迎词')" prop="system_welcome">
<div style="width: 220px;">
<Input v-model="formDatum.system_welcome" :placeholder="$L('欢迎您,{username}')"/>
<Input v-model="formDatum.system_welcome" :placeholder="$L('欢迎您,(*)', '{username}')"/>
</div>
<div class="form-tip">{{$L('仪表盘欢迎词,{username} 代表用户昵称')}}</div>
<div class="form-tip">{{$L('仪表盘欢迎词,(*)代表用户昵称', '{username}')}}</div>
</FormItem>
<FormItem :label="$L('图片优化')" prop="image_compress">
<RadioGroup v-model="formDatum.image_compress">

View File

@ -25,7 +25,7 @@
</div>
<Modal
v-model="warningShow"
:title="$L(`删除${appTitle}帐号`)"
:title="$L('删除(*)帐号', appTitle)"
class="page-setting-delete-box">
<div class="big-text">{{ $L('帐号删除后,该帐号将无法正常登录且无法恢复,帐号下的所有数据也将被删除。') }}</div>
<div class="small-text">
@ -63,9 +63,9 @@ export default {
{
validator: (rule, value, callback) => {
if (value.trim() === '') {
callback(new Error(this.$L('请输入邮箱帐号')));
callback(new Error(this.$L('请输入邮箱帐号')));
} else if (!$A.isEmail(value.trim())) {
callback(new Error(this.$L('请输入正确邮箱帐号')));
callback(new Error(this.$L('请输入正确的邮箱地址')));
} else {
callback();
}

View File

@ -2,7 +2,7 @@
<div class="setting-item submit">
<Loading v-if="configLoad > 0"/>
<Form v-else ref="formDatum" :model="formDatum" :rules="ruleDatum" v-bind="formOptions" @submit.native.prevent>
<Alert v-if="isLdap" type="warning">{{$L('LDAP 用户禁止修改邮箱地址')}}</Alert>
<Alert v-if="isLdap" type="warning">{{$L('LDAP 用户禁止修改邮箱')}}</Alert>
<FormItem :label="$L('新邮箱地址')" prop="newEmail">
<Input v-if="isRegVerify == 1" v-model="formDatum.newEmail"
:class="count > 0 ? 'setting-send-input':'setting-input'" search @on-search="sendEmailCode"

View File

@ -15,7 +15,7 @@
</template>
<script>
import {languageList, languageName, setLanguage} from "../../../language";
import {languageList, languageName, setLanguage} from "../../../i18n";
import {mapState} from "vuex";
export default {

View File

@ -36,7 +36,7 @@ export default {
ruleDatum: {
oldpass: [
{ required: true, message: this.$L('请输入旧密码!'), trigger: 'change' },
{ type: 'string', min: 6, message: this.$L('密码长度至少6位'), trigger: 'change' }
{ type: 'string', min: 6, message: this.$L('密码长度至少(*)位!', 6), trigger: 'change' }
],
newpass: [
{
@ -53,7 +53,7 @@ export default {
required: true,
trigger: 'change'
},
{ type: 'string', min: 6, message: this.$L('密码长度至少6位'), trigger: 'change' }
{ type: 'string', min: 6, message: this.$L('密码长度至少(*)位!', 6), trigger: 'change' }
],
checkpass: [
{

View File

@ -8,7 +8,7 @@
@submit.native.prevent>
<FormItem :label="$L('头像')" prop="userimg">
<ImgUpload v-model="formData.userimg" :num="1" :width="512" :height="512" whcut="cover"/>
<span class="form-tip">{{$L('建议尺寸:200x200')}}</span>
<span class="form-tip">{{$L('建议尺寸:(*)', '200x200')}}</span>
</FormItem>
<FormItem :label="$L('邮箱')" prop="email">
<Input v-model="userInfo.email" disabled></Input>
@ -113,7 +113,7 @@ export default {
],
nickname: [
{required: true, message: this.$L('请输入昵称!'), trigger: 'change'},
{type: 'string', min: 2, message: this.$L('昵称长度至少2位'), trigger: 'change'}
{type: 'string', min: 2, message: this.$L('昵称长度至少(*)位!', 2), trigger: 'change'}
]
},

View File

@ -2,7 +2,7 @@
</template>
<script>
import {languageName} from "../language";
import {languageName} from "../i18n";
export default {
mounted() {

View File

@ -1,5 +1,5 @@
import * as openpgp from 'openpgp_hi/lightweight';
import {initLanguage, languageList, languageName} from "../language";
import {initLanguage, languageList, languageName} from "../i18n";
import {$callData, $urlSafe, SSEClient} from '../utils'
import {isLocalHost} from "../components/Replace/utils";
import emitter from "./events";
@ -344,7 +344,7 @@ export default {
dispatch("userEditInput", 'nickname').then(() => {
dispatch("call", cloneParams).then(resolve).catch(reject)
}).catch(err => {
reject({ret: -1, data, msg: err || $A.L('请设置昵称')})
reject({ret: -1, data, msg: err || $A.L('请设置昵称')})
})
return
}
@ -354,7 +354,7 @@ export default {
dispatch("userEditInput", 'tel').then(() => {
dispatch("call", cloneParams).then(resolve).catch(reject)
}).catch(err => {
reject({ret: -1, data, msg: err || $A.L('请设置联系电话')})
reject({ret: -1, data, msg: err || $A.L('请设置联系电话')})
})
return
}

View File

@ -4,7 +4,7 @@
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import store from './index'
import { languageName } from '../language'
import { languageName } from '../i18n'
export const CHUNK_THRESHOLD = 10 * 1024 * 1024
export const CHUNK_SIZE = 5 * 1024 * 1024 // 必须与后端 ChunkUpload::CHUNK_SIZE 一致

View File

@ -1,4 +1,4 @@
import {languageList, languageName} from "../language";
import {languageList, languageName} from "../i18n";
/**
* AI 服务商标识与显示名映射

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
* - resources/assets/js/functions/common.js / localForage / Storage / ihttp / ajaxc / time / sort
* - resources/assets/js/functions/web.js / iviewui / dark
* - resources/assets/js/functions/eeui.js EEUI App
* $L resources/assets/js/language/index.js switchLanguage
* $L resources/assets/js/i18n/index.js switchLanguage
* window.$LVue.prototype.$L $A.L resources/assets/js/app.js
*
* / $.extend $A