From cad8b67680cf5963f2d41e0b238b30a0f2df41b2 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Tue, 5 Nov 2024 02:50:29 +0900 Subject: [PATCH] add en locale (#22) * upgrade flask-babel * move loclae dir to fit pybabel default * adapt python_babel * move locale selector * update doc * update .mo file in docker build * update po files * type annotations * make ruff happy * migrate to docker-compose action * fix action * try oidc * fix test * pin codecov action version * cleanup * type hints & docs * upgrade deps * server /storage/ from flask * type annotations * wip: script to translate .po with openai * wip * wip * translation * revise * revise translations * update * tune log --- .github/workflows/check-pr.yml | 6 +- CONTRIBUTION.md | 6 +- Dockerfile | 2 + Makefile | 11 + app/__init__.py | 37 +- app/apis/__init__.py | 2 +- app/apis/member.py | 7 +- app/apis/type.py | 2 +- app/config.py | 5 +- app/constants/base.py | 4 +- app/decorators/file.py | 14 +- app/decorators/url.py | 5 +- app/factory.py | 14 +- app/locales/en_US/LC_MESSAGES/messages.po | 115 -- .../zh_Hant_TW/LC_MESSAGES/messages.po | 112 -- app/models/file.py | 68 +- app/models/invitation.py | 2 +- app/models/message.py | 14 +- app/models/project.py | 15 +- app/models/user.py | 4 +- app/scripts/fill_en_translations.py | 66 + app/scripts/fill_zh_translations.py | 31 + app/translations/__init__.py | 34 + app/translations/en/LC_MESSAGES/messages.po | 1449 +++++++++++++++++ app/translations/zh/LC_MESSAGES/messages.po | 1375 ++++++++++++++++ app/utils/mongo.py | 9 +- deps-top.txt | 16 +- docs/models.md | 112 +- manage.py | 57 +- requirements.txt | 89 +- tests/api/test_auth_api.py | 2 +- 31 files changed, 3195 insertions(+), 490 deletions(-) delete mode 100644 app/locales/en_US/LC_MESSAGES/messages.po delete mode 100644 app/locales/zh_Hant_TW/LC_MESSAGES/messages.po create mode 100644 app/scripts/fill_en_translations.py create mode 100644 app/scripts/fill_zh_translations.py create mode 100644 app/translations/__init__.py create mode 100644 app/translations/en/LC_MESSAGES/messages.po create mode 100644 app/translations/zh/LC_MESSAGES/messages.po diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 93e7c00..ae3b209 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -37,11 +37,11 @@ jobs: verbose: true custom-arguments: '-n=2' emoji: false - - uses: codecov/codecov-action@v4.0.1 + - uses: codecov/codecov-action@v4.5.0 if: always() with: - token: ${{ secrets.CODECOV_TOKEN }} - # XXX: can't if (SECRET_DEFINED) for this step + fail_ci_if_error: true + use_oidc: true - name: save test report uses: actions/upload-artifact@v4 if: always() diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index ec49232..73a93b9 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -8,7 +8,7 @@ ### init venv for development ``` -$ python3.10 -mvenv venv +$ make create-venv deps $ venv/bin/pip install -r requirements.txt ``` @@ -19,3 +19,7 @@ $ venv/bin/ruff . ``` +### run within docker + + + diff --git a/Dockerfile b/Dockerfile index a068e31..97c955d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,3 +12,5 @@ COPY . /app WORKDIR /app EXPOSE 5000 + +RUN make babel-update-mo diff --git a/Makefile b/Makefile index 1d5dfd6..bf8faa8 100644 --- a/Makefile +++ b/Makefile @@ -42,3 +42,14 @@ test_single: test_logging: #--capture=no venv/bin/pytest --capture=sys --log-cli-level=DEBUG tests/base/test_logging.py + +babel-update-po: + venv/bin/pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot app + venv/bin/pybabel update -i messages.pot -d app/translations + +babel-update-mo: + venv/bin/pybabel compile -d app/translations + +babel-translate-po: + venv/bin/python app/scripts/fill_zh_translations.py + venv/bin/python app/scripts/fill_en_translations.py \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index 0b9116e..9cc4eeb 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,19 +1,17 @@ import os import logging -from flask import Flask, g, request +from flask import Flask from .factory import ( app_config, create_celery, create_flask_app, init_flask_app, - babel, oss, gs_vision, ) -from app.constants.locale import Locale from app.utils.logging import configure_root_logger, configure_extra_logs configure_root_logger() @@ -27,7 +25,17 @@ STORAGE_PATH = os.path.abspath(os.path.join(APP_PATH, "..", "storage")) # 储存地址 # Singletons -flask_app = create_flask_app(Flask(__name__)) +flask_app = create_flask_app( + Flask( + __name__, + **{ + "static_url_path": "/storage", + "static_folder": STORAGE_PATH, + } + if app_config["STORAGE_TYPE"] == "LOCAL_STORAGE" + else {}, + ) +) configure_extra_logs(flask_app) celery = create_celery(flask_app) init_flask_app(flask_app) @@ -37,27 +45,6 @@ def create_app(): return flask_app -@babel.localeselector -def get_locale(): - current_user = g.get("current_user") - if ( - current_user - and current_user.locale - and current_user.locale != "auto" - and current_user.locale in Locale.ids() - ): - return current_user.locale - return request.accept_languages.best_match(["zh_CN", "zh_TW", "zh", "en_US", "en"]) - - -# @babel.timezoneselector -# def get_timezone(): -# # TODO 弄清 timezone 是什么东西 -# current_user = g.get('current_user') -# if current_user: -# if current_user.timezone: -# return current_user.timezone - __all__ = [ "oss", "gs_vision", diff --git a/app/apis/__init__.py b/app/apis/__init__.py index 3d4c77d..da11165 100644 --- a/app/apis/__init__.py +++ b/app/apis/__init__.py @@ -7,7 +7,7 @@ from flask import Blueprint, Flask logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +# logger.setLevel(logging.DEBUG) """ @apiDefine TokenHeader diff --git a/app/apis/member.py b/app/apis/member.py index 3218600..b3075ad 100644 --- a/app/apis/member.py +++ b/app/apis/member.py @@ -1,3 +1,4 @@ +from typing import Union from flask import request from app.core.responses import MoePagination @@ -5,6 +6,8 @@ from app.decorators.auth import token_required from app.decorators.url import fetch_model, fetch_group from app.exceptions import NoPermissionError +from app.models.project import Project +from app.models.team import Team from app.models.user import User from app.validators.member import ChangeMemberSchema @@ -61,7 +64,7 @@ class MemberAPI(MoeAPIView): @token_required @fetch_group @fetch_model(User) - def put(self, group, user: User): + def put(self, group: Union[Project, Team], user: User): """ @api {put} /v1///users/ 修改团体的成员 @apiVersion 1.0.0 @@ -91,7 +94,7 @@ def put(self, group, user: User): @token_required @fetch_group @fetch_model(User) - def delete(self, group, user: User): + def delete(self, group: Union[Project, Team], user: User): """ @api {delete} /v1///users/ 删除团体的成员 @apiVersion 1.0.0 diff --git a/app/apis/type.py b/app/apis/type.py index 4ba35e2..bc1561b 100644 --- a/app/apis/type.py +++ b/app/apis/type.py @@ -1,4 +1,4 @@ -from app import Locale +from app.constants.locale import Locale from app.core.views import MoeAPIView from app.models.team import Team from app.exceptions.base import RequestDataWrongError diff --git a/app/config.py b/app/config.py index 1c055a8..5e90466 100644 --- a/app/config.py +++ b/app/config.py @@ -2,9 +2,12 @@ # 脱敏的生产环境配置(严禁记录密钥) # 开发测试配置可放在 configs 文件夹下(已 gitignore)或项目外 # =========== +import dotenv from os import environ as env import urllib.parse as urlparse +dotenv.load_dotenv() + # ----------- # 基础设置 # ----------- @@ -33,7 +36,7 @@ # ----------- # i18n # ----------- -BABEL_DEFAULT_LOCALE = "zh_Hans_CN" +BABEL_DEFAULT_LOCALE = "zh" BABEL_DEFAULT_TIMEZONE = "UTC" # ----------- # 其他设置 diff --git a/app/constants/base.py b/app/constants/base.py index 49e22b6..8f60199 100644 --- a/app/constants/base.py +++ b/app/constants/base.py @@ -38,7 +38,9 @@ def get_detail(cls, attr: str, detail_name: str, default_value: str = "") -> str @classmethod def to_api( - cls, ids: Union[List[int], List[str]] = None, id: Union[int, str] = None + cls, + ids: Union[List[int], List[str], None] = None, + id: Union[int, str, None] = None, ) -> Union[List[Dict], Dict]: """转化为前端使用数组,并加上介绍""" # 如果指定了id,则返回相应id的类型 diff --git a/app/decorators/file.py b/app/decorators/file.py index 64cbac7..a45a6e1 100644 --- a/app/decorators/file.py +++ b/app/decorators/file.py @@ -1,4 +1,5 @@ from functools import wraps +from typing import TYPE_CHECKING from app.exceptions import ( FileNotActivatedError, @@ -7,12 +8,15 @@ ) from app.constants.file import FileType +if TYPE_CHECKING: + from app.models.file import File + def need_activated(func): """必须激活的File才能进行操作""" @wraps(func) - def wrapper(self, *args, **kwargs): + def wrapper(self: "File", *args, **kwargs): if not self.activated: raise FileNotActivatedError return func(self, *args, **kwargs) @@ -20,12 +24,12 @@ def wrapper(self, *args, **kwargs): return wrapper -def only(file_type): +def only(file_type: int): def decorator(func): """只允许某类型使用的函数,供File模型使用""" @wraps(func) - def wrapper(self, *args, **kwargs): + def wrapper(self: "File", *args, **kwargs): if self.type != file_type: raise FileTypeNotSupportError(str(func)) return func(self, *args, **kwargs) @@ -39,7 +43,7 @@ def only_file(func): """只允许非FOLDER使用的函数,供File模型使用""" @wraps(func) - def wrapper(self, *args, **kwargs): + def wrapper(self: "File", *args, **kwargs): if self.type == FileType.FOLDER: raise FileTypeNotSupportError(gettext("不能对文件夹执行 ") + func.__name__) return func(self, *args, **kwargs) @@ -51,7 +55,7 @@ def only_folder(func): """只允许FOLDER使用的函数,供File模型使用""" @wraps(func) - def wrapper(self, *args, **kwargs): + def wrapper(self: "File", *args, **kwargs): if self.type != FileType.FOLDER: raise FileTypeNotSupportError(gettext("不能对文件执行 ") + str(func)) return func(self, *args, **kwargs) diff --git a/app/decorators/url.py b/app/decorators/url.py index 2708c02..4a0da62 100644 --- a/app/decorators/url.py +++ b/app/decorators/url.py @@ -1,4 +1,5 @@ from functools import wraps +from typing import Optional from mongoengine.errors import ValidationError @@ -8,7 +9,9 @@ from app.utils.str import to_underscore -def fetch_model(document, from_name=None, to_name=None): +def fetch_model( + document: type, from_name: Optional[str] = None, to_name: Optional[str] = None +): """ 从url的id中获取相对应的模型对象 diff --git a/app/factory.py b/app/factory.py index 0d599d6..996a02a 100644 --- a/app/factory.py +++ b/app/factory.py @@ -8,6 +8,7 @@ import app.config as _app_config from app.services.oss import OSS from .apis import register_apis +from app.translations import get_locale from app.models import connect_db @@ -23,8 +24,13 @@ k: getattr(_app_config, k) for k in dir(_app_config) if not k.startswith("_") } +_create_flask_app_called = False + def create_flask_app(app: Flask) -> Flask: + global _create_flask_app_called + assert not _create_flask_app_called, "create_flask_app should only be called once" + _create_flask_app_called = True app.config.from_mapping(app_config) connect_db(app.config) # print("WTF", app.logger.level) @@ -37,10 +43,13 @@ def create_flask_app(app: Flask) -> Flask: def init_flask_app(app: Flask): register_apis(app) - babel.init_app(app) + babel.init_app(app, locale_selector=get_locale) apikit.init_app(app) logger.info(f"----- build id: {app_config['BUILD_ID']}") - logger.info("站点支持语言: " + str([str(i) for i in babel.list_translations()])) + with app.app_context(): + logger.debug( + "站点支持语言: " + str([str(i) for i in babel.list_translations()]) + ) oss.init(app.config) # 文件储存 @@ -126,6 +135,7 @@ def create_default_team(admin_user): def init_db(app: Flask): + """init db models""" # 初始化角色,语言 from app.models.language import Language from app.models.project import ProjectRole diff --git a/app/locales/en_US/LC_MESSAGES/messages.po b/app/locales/en_US/LC_MESSAGES/messages.po deleted file mode 100644 index f664262..0000000 --- a/app/locales/en_US/LC_MESSAGES/messages.po +++ /dev/null @@ -1,115 +0,0 @@ -# English (United States) translations for PROJECT. -# Copyright (C) 2017 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2017. -# -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-05-09 18:11+0800\n" -"PO-Revision-Date: 2017-04-14 15:38+0800\n" -"Last-Translator: FULL NAME \n" -"Language: en_US\n" -"Language-Team: en_US \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.4.0\n" - -#: app/apis/auth.py:58 -msgid "注册成功" -msgstr "" - -#: app/exceptions/auth.py:16 -msgid "验证异常" -msgstr "" - -#: app/exceptions/auth.py:25 -msgid "需要登录" -msgstr "" - -#: app/exceptions/auth.py:34 -msgid "此邮箱未注册" -msgstr "" - -#: app/exceptions/auth.py:43 -msgid "密码错误" -msgstr "" - -#: app/exceptions/auth.py:52 -msgid "需要令牌" -msgstr "" - -#: app/exceptions/auth.py:63 -msgid "无效的令牌 ({message})" -msgstr "" - -#: app/exceptions/base.py:20 -msgid "未定义的错误" -msgstr "" - -#: app/exceptions/v_code.py:16 -msgid "验证码异常" -msgstr "" - -#: app/exceptions/v_code.py:25 -msgid "验证码已过期,请重新申请" -msgstr "" - -#: app/exceptions/v_code.py:34 -msgid "验证码错误" -msgstr "" - -#: app/exceptions/v_code.py:43 -msgid "验证码失效,请重新获取" -msgstr "" - -#: app/exceptions/v_code.py:55 -msgid "请等候{second}秒后再试" -msgstr "" - -#: app/exceptions/v_code.py:57 -msgid "冷却中,请稍后再试" -msgstr "" - -#: app/models/user.py:114 -msgid "这个用户还没有签名" -msgstr "This user has not signature" - -#: app/validators/auth.py:20 -msgid "此昵称已存在,请尝试其他昵称" -msgstr "" - -#: app/validators/auth.py:27 -msgid "此邮箱已存在,您可以直接登录或尝试其他邮箱" -msgstr "" - -#: app/validators/auth.py:33 app/validators/auth.py:36 -#: app/validators/auth.py:39 app/validators/auth.py:42 -#: app/validators/auth.py:44 app/validators/auth.py:47 -#: app/validators/auth.py:60 app/validators/auth.py:63 -#: app/validators/auth.py:69 -msgid "必填" -msgstr "Required" - -#: app/validators/auth.py:33 app/validators/auth.py:60 -#: app/validators/auth.py:69 -msgid "邮箱格式不正确" -msgstr "Not a valid email address" - -#: app/validators/auth.py:35 app/validators/auth.py:38 -#: app/validators/auth.py:62 -msgid "长度为{min}到{max}个字符" -msgstr "Length must be between {min} and {max}" - -#~ msgid "此邮箱已存在,您可以直接登录" -#~ msgstr "" - -#~ msgid "此昵称已存在" -#~ msgstr "" - -#~ msgid "验证码异常基类" -#~ msgstr "" - diff --git a/app/locales/zh_Hant_TW/LC_MESSAGES/messages.po b/app/locales/zh_Hant_TW/LC_MESSAGES/messages.po deleted file mode 100644 index 8250dac..0000000 --- a/app/locales/zh_Hant_TW/LC_MESSAGES/messages.po +++ /dev/null @@ -1,112 +0,0 @@ -# Chinese (Traditional, Taiwan) translations for PROJECT. -# Copyright (C) 2017 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2017. -# -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-05-09 18:11+0800\n" -"PO-Revision-Date: 2017-04-14 15:23+0800\n" -"Last-Translator: FULL NAME \n" -"Language: zh_Hant_TW\n" -"Language-Team: zh_Hant_TW \n" -"Plural-Forms: nplurals=1; plural=0\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.4.0\n" - -#: app/apis/auth.py:58 -msgid "注册成功" -msgstr "" - -#: app/exceptions/auth.py:16 -msgid "验证异常" -msgstr "" - -#: app/exceptions/auth.py:25 -msgid "需要登录" -msgstr "" - -#: app/exceptions/auth.py:34 -msgid "此邮箱未注册" -msgstr "" - -#: app/exceptions/auth.py:43 -msgid "密码错误" -msgstr "" - -#: app/exceptions/auth.py:52 -msgid "需要令牌" -msgstr "需要令牌TW" - -#: app/exceptions/auth.py:63 -msgid "无效的令牌 ({message})" -msgstr "無效的令牌 ({message})" - -#: app/exceptions/base.py:20 -msgid "未定义的错误" -msgstr "" - -#: app/exceptions/v_code.py:16 -msgid "验证码异常" -msgstr "" - -#: app/exceptions/v_code.py:25 -msgid "验证码已过期,请重新申请" -msgstr "" - -#: app/exceptions/v_code.py:34 -msgid "验证码错误" -msgstr "" - -#: app/exceptions/v_code.py:43 -msgid "验证码失效,请重新获取" -msgstr "验证码失效,请重新获取" - -#: app/exceptions/v_code.py:55 -msgid "请等候{second}秒后再试" -msgstr "" - -#: app/exceptions/v_code.py:57 -msgid "冷却中,请稍后再试" -msgstr "" - -#: app/models/user.py:114 -msgid "这个用户还没有签名" -msgstr "這個用戶還沒有簽名" - -#: app/validators/auth.py:20 -msgid "此昵称已存在,请尝试其他昵称" -msgstr "" - -#: app/validators/auth.py:27 -msgid "此邮箱已存在,您可以直接登录或尝试其他邮箱" -msgstr "此郵箱已存在,您可以直接登錄或嘗試其他郵箱" - -#: app/validators/auth.py:33 app/validators/auth.py:36 -#: app/validators/auth.py:39 app/validators/auth.py:42 -#: app/validators/auth.py:44 app/validators/auth.py:47 -#: app/validators/auth.py:60 app/validators/auth.py:63 -#: app/validators/auth.py:69 -msgid "必填" -msgstr "必填" - -#: app/validators/auth.py:33 app/validators/auth.py:60 -#: app/validators/auth.py:69 -msgid "邮箱格式不正确" -msgstr "郵箱格式不正確" - -#: app/validators/auth.py:35 app/validators/auth.py:38 -#: app/validators/auth.py:62 -msgid "长度为{min}到{max}个字符" -msgstr "長度為{min}到{max}個字符" - -#~ msgid "此昵称已存在" -#~ msgstr "此昵稱已存在" - -#~ msgid "验证码异常基类" -#~ msgstr "" - diff --git a/app/models/file.py b/app/models/file.py index 31f3c22..1f84ec7 100644 --- a/app/models/file.py +++ b/app/models/file.py @@ -1,5 +1,5 @@ from io import BufferedReader -from typing import NoReturn, Union, BinaryIO +from typing import TYPE_CHECKING, List, NoReturn, Optional, Union, BinaryIO import datetime import math import re @@ -48,6 +48,9 @@ ) from app.models.target import Target from app.models.term import Term + +if TYPE_CHECKING: + from app.models.project import Project from app.tasks.file_parse import parse_text, safe from app.tasks.ocr import ocr from app.constants.source import SourcePositionType @@ -77,7 +80,7 @@ class Filename: """文件名类,用于检查文件名合法性,及生成用于排序的文件名""" - def __init__(self, name, folder=False): + def __init__(self, name: str, folder=False): self.name = name # 检查文件名有效性 self._check_valid() @@ -160,8 +163,10 @@ class File(Document): type = IntField(db_field="t", required=True) # 文件类型 # == 归属 == - project = ReferenceField("Project", db_field="p", required=True) # 所属项目 - parent = ReferenceField("File", db_field="f") # 父级文件夹 + project: "Project" = ReferenceField( + "Project", db_field="p", required=True + ) # 所属项目 + parent: Optional["File"] = ReferenceField("File", db_field="f") # 父级文件夹 ancestors = ListField( ReferenceField("File"), db_field="a", default=list ) # 祖先文件夹 @@ -257,52 +262,52 @@ def clean(self): self.dir_sort_name = self.parent.dir_sort_name + self.parent.sort_name + "/" @classmethod - def by_id(cls, id): + def by_id(cls, id) -> "File": file = cls.objects(id=id).first() if file is None: raise FileNotExistError return file @property - def revisions(self): + def revisions(self) -> List["File"]: """获得同名的所有修订版,包括本修订版""" return self.project.get_files( name=self.name, parent=self.parent, activated="all" ) @property - def activated_revision(self): + def activated_revision(self) -> Optional["File"]: """获得同名的激活的修订版""" return self.project.get_files( name=self.name, parent=self.parent, activated=True ).first() @property - def deactivated_revisions(self): + def deactivated_revisions(self) -> List["File"]: """获得同名的未激活的修订版""" return self.project.get_files( name=self.name, parent=self.parent, activated=False ) @property - def ancestor_ids(self): + def ancestor_ids(self) -> List[ObjectId]: """返回祖先的ids,是一个包含ObjectId的列表""" son = self.to_mongo(use_db_field=False, fields=["ancestors"]) ids = son.get("ancestors", []) return ids - def ancestor_caches(self, target): + def ancestor_caches(self, target: "Target") -> list["FileTargetCache"]: """返回祖先的FileTargetCache""" caches = FileTargetCache.objects(file__in=self.ancestor_ids, target=target) return caches - def cache(self, target): + def cache(self, target: "Target") -> list["FileTargetCache"]: """返回自己的FileTargetCache""" cache = FileTargetCache.objects(file=self, target=target).first() return cache @need_activated - def create_revision(self): + def create_revision(self) -> "File": """创建一个修订版""" file = File( name=self.name, @@ -371,7 +376,7 @@ def activate_revision(self): def rename(self, name): """重命名""" # 检查是否有同名文件 - old_file = self.project.get_files(name=name, parent=self.parent).first() + old_file: "File" = self.project.get_files(name=name, parent=self.parent).first() # 如果有同名文件 if old_file: # 不是自身,文件名重复,抛出异常 @@ -406,7 +411,7 @@ def rename(self, name): file.save() @need_activated - def move_to(self, parent): + def move_to(self, parent: Union[str, ObjectId, "File"]): """将某个文件或文件夹移动到另一个地方""" # 强制从数据库更新自身,以免之前有删除、建立文件夹操作,影响最后的计数缓存 self.reload() @@ -790,7 +795,7 @@ def clear(self): # 如果是文件夹,还需要物理删除所有下级文件 if self.type == FileType.FOLDER: # 包含所有下级的文件夹、文件、修订版 - files = File.objects(ancestors=self) + files: List[File] = File.objects(ancestors=self) # 物理删除源文件 for file in files: if file.type != FileType.FOLDER: @@ -841,6 +846,7 @@ def to_json_file(self): # 所有图片文件的翻译会合并到一个文件 # 文本的翻译会单独列出 + raise NotImplementedError @only_file def safe_scan(self): @@ -995,7 +1001,7 @@ def clear_all_sources(self): ) self.inc_cache("source_count", -self.source_count) - def sources(self, skip=None, limit=None, order_by: list = None): + def sources(self, skip=None, limit=None, order_by: list = None) -> list["Source"]: sources = Source.objects(file=self) # 排序处理 sources = mongo_order(sources, order_by, ["rank"]) @@ -1162,10 +1168,10 @@ def to_api(self): class FileTargetCache(Document): """文件对于不同目标语言的计数缓存""" - file = ReferenceField( + file: "File" = ReferenceField( "File", db_field="f", required=True, reverse_delete_rule=CASCADE ) # 所属文件 - target = ReferenceField( + target: "Target" = ReferenceField( Target, db_field="t", required=True, reverse_delete_rule=CASCADE ) translated_source_count = IntField(db_field="ts", default=0) # 已翻译的原文数量 @@ -1188,7 +1194,7 @@ def to_api(self): class Source(Document): - file = ReferenceField("File", db_field="f", reverse_delete_rule=CASCADE) + file: "File" = ReferenceField("File", db_field="f", reverse_delete_rule=CASCADE) rank = IntField(db_field="r", required=True) # 排序 content = StringField(db_field="c", default="") # 内容 # === 图片独有的参数 === @@ -1215,7 +1221,7 @@ class Source(Document): meta = {"indexes": ["file", "blank", "rank"]} @classmethod - def by_id(cls, id): + def by_id(cls, id) -> "Source": source = cls.objects(id=id).first() if source is None: raise SourceNotExistError @@ -1231,7 +1237,7 @@ def find_terms(self): self.possible_terms.append(term) self.save() - def copy(self, source): + def copy(self, source: "Source"): """将其他Source的Translation和Tip复制到此Source,必须是空Source才能使用""" if self.translations().count() > 0: return # 如果已有翻译,则不复制 @@ -1291,7 +1297,7 @@ def copy(self, source): edit_time=tip.edit_time, ).save() - def move_ahead(self, next_source): + def move_ahead(self, next_source: Optional["Source"]): """将Source移动到某个Source之前,None则移动到最后""" self.reload() # 刷新缓存 if self.file.type != FileType.IMAGE: @@ -1367,7 +1373,13 @@ def create_tip(self, content: str, target, user=None): """Tip.create(source=self)的快捷方式""" return Tip.create(content=content, source=self, target=target, user=user) - def translations(self, target=None, skip=None, limit=None, order_by: list = None): + def translations( + self, + target: Optional["Target"] = None, + skip=None, + limit=None, + order_by: list = None, + ) -> list["Translation"]: translations = Translation.objects(source=self) if target: translations = translations.filter(target=target) @@ -1377,7 +1389,13 @@ def translations(self, target=None, skip=None, limit=None, order_by: list = None translations = mongo_slice(translations, skip, limit) return translations - def tips(self, target=None, skip=None, limit=None, order_by: list = None): + def tips( + self, + target: Optional["Target"] = None, + skip=None, + limit=None, + order_by: list = None, + ) -> List["Tip"]: tips = Tip.objects(source=self) if target: tips = tips.filter(target=target) @@ -1553,7 +1571,7 @@ def to_api(self): class Tip(Document): source = ReferenceField( - "Source", db_field="o", reverse_delete_rule=CASCADE + Source, db_field="o", reverse_delete_rule=CASCADE ) # 所属原文 target = ReferenceField( Target, db_field="t", reverse_delete_rule=CASCADE diff --git a/app/models/invitation.py b/app/models/invitation.py index be297ee..0bcde72 100644 --- a/app/models/invitation.py +++ b/app/models/invitation.py @@ -37,7 +37,7 @@ class Invitation(Document): @classmethod def get(cls, user=None, group=None, status=None, skip=None, limit=None): - invitations = cls.objects() + invitations: list[Invitation] = cls.objects() if user: invitations = invitations.filter(user=user) if group: diff --git a/app/models/message.py b/app/models/message.py index fbf9bcf..1467c80 100644 --- a/app/models/message.py +++ b/app/models/message.py @@ -12,11 +12,15 @@ from app.exceptions.message import MessageTypeError from app.constants.message import MessageType +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from app.models.user import User class Message(Document): - sender = ReferenceField("User", db_field="s", required=True) # 发送者 - receiver = ReferenceField("User", db_field="r", required=True) # 接收人 + sender: "User" = ReferenceField("User", db_field="s", required=True) # 发送者 + receiver: "User" = ReferenceField("User", db_field="r", required=True) # 接收人 content = StringField(db_field="c", default="") # 内容 type = IntField( db_field="t", required=True @@ -25,7 +29,9 @@ class Message(Document): create_time = DateTimeField(db_field="ct", default=datetime.datetime.utcnow) @classmethod - def send_system_message(cls, sender, receiver, content, message_type): + def send_system_message( + cls, sender: "User", receiver: "User", content, message_type + ): """发送系统消息""" if message_type not in [ MessageType.SYSTEM, @@ -42,7 +48,7 @@ def send_system_message(cls, sender, receiver, content, message_type): return message @classmethod - def send_user_message(cls, sender, receiver, content): + def send_user_message(cls, sender: "User", receiver: "User", content: str): """发送用户消息""" message = cls( sender=sender, diff --git a/app/models/project.py b/app/models/project.py index f57276e..e4bc802 100644 --- a/app/models/project.py +++ b/app/models/project.py @@ -54,6 +54,7 @@ from app.models.term import TermBank if TYPE_CHECKING: + from app.models.user import User from app.models.team import Team from app.models.output import Output from app.tasks.file_parse import find_terms @@ -281,7 +282,7 @@ class ProjectSet(Document): name = StringField(db_field="n", required=True) # 集合名 intro = StringField(db_field="i", default="") # 项目集介绍 - team = ReferenceField("Team", db_field="t", required=True) + team: "Team" = ReferenceField("Team", db_field="t", required=True) create_time = DateTimeField(db_field="ct", default=datetime.datetime.utcnow) edit_time = DateTimeField( db_field="et", required=True, default=datetime.datetime.utcnow @@ -674,14 +675,8 @@ def upload( file.upload_real_file(real_file) return file - def upload_from_zip(self): - """从zip导入项目""" - - def upload_from_github(self): - """从github导入项目""" - @classmethod - def by_id(cls, id_: str): + def by_id(cls, id_: str) -> "Project": project = cls.objects(id=id_).first() if project is None: raise ProjectNotExistError() @@ -886,8 +881,8 @@ def outputs(self): @staticmethod def batch_to_api( - projects, - user, + projects: list["Project"], + user: "User", /, *, inherit_admin_team=None, diff --git a/app/models/user.py b/app/models/user.py index 41c47cb..c658b38 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -49,7 +49,7 @@ class User(Document): email = StringField(required=True, unique=True, db_field="e") # 邮箱 name = StringField(required=True, unique=True, db_field="n") # 姓名 signature = StringField(default="", db_field="s") # 个性签名 - locale = StringField(default=Locale.AUTO, db_field="l") # 语言 + locale = StringField(default=Locale.AUTO, db_field="l") # 语言 # NOT USED timezone = StringField(default="", db_field="t") # 时区 _avatar = StringField(default="", db_field="a") # 头像 banned = BooleanField(defult=False, db_field="b") # 是否被封禁 @@ -183,7 +183,7 @@ def generate_token(self, expires_in=2592000): return token @classmethod - def verify_token(cls, token): + def verify_token(cls, token: str): # 检查是否时Bearer token if not token.startswith("Bearer "): raise BadTokenError(gettext("令牌格式错误,应形如 Bearer x.x.x")) diff --git a/app/scripts/fill_en_translations.py b/app/scripts/fill_en_translations.py new file mode 100644 index 0000000..9798674 --- /dev/null +++ b/app/scripts/fill_en_translations.py @@ -0,0 +1,66 @@ +import dotenv +from babel.messages.pofile import read_po, write_po +import openai +import logging + +dotenv.load_dotenv() +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +client = openai.Client() + + +def translate_text(text: str, target_language: str): + response = client.chat.completions.create( + model="gpt-4o", + messages=[ + { + "role": "user", + "content": f"""You are a skillful multilanguage translatior specialized in UI i18n. Please translate the following text to {target_language}: {text}. Please only return the translated text and nothing else.""", + } + ], + max_tokens=1000, + temperature=0, + ) + return response.choices[0].message.content + + +def parse_and_translate_po(file_path: str, target_language: str, limit=0): + # Read the .po file + with open(file_path, "rb") as po_file: + catalog = read_po(po_file) + + print(f"Translating {len(catalog)} messages to {target_language}") + + translated_count = 0 + + # Translate each message + for message in catalog: + # print("message:", message.id, message.string) + if message.id and not message.string: + print(f"Translating '{message.id}'") + try: + translated_text = translate_text(str(message.id), target_language) + message.string = translated_text + logger.info(f"Translated '{message.id}' to '{message.string}'") + translated_count += 1 + except Exception: + break + else: + print(f"Skipping '{message.id}'") + if limit and translated_count >= limit: + break + + # Write the updated catalog back to the .po file + with open(file_path, "wb") as po_file: + write_po(po_file, catalog) + + +def po_path_for_language(language_code): + return f"app/translations/{language_code}/LC_MESSAGES/messages.po" + + +if __name__ == "__main__": + for lang in ["en"]: + po_file_path = po_path_for_language(lang) + parse_and_translate_po(po_file_path, lang, limit=500) diff --git a/app/scripts/fill_zh_translations.py b/app/scripts/fill_zh_translations.py new file mode 100644 index 0000000..e540232 --- /dev/null +++ b/app/scripts/fill_zh_translations.py @@ -0,0 +1,31 @@ +from babel.messages.pofile import read_po, write_po +import logging + +logger = logging.getLogger(__name__) + + +def fill_msg_with_msg_id(file_path: str): + # Read the .po file + with open(file_path, "rb") as po_file: + catalog = read_po(po_file) + + for message in catalog: + if message.id and not message.string: + message.string = message.id + elif message.id != message.string: + logger.warning( + "%s L%s: MISMATCH message id %s / message string %s", + file_path, + message.lineno, + message.id, + message.string, + ) + + # Write the updated catalog back to the .po file + with open(file_path, "wb") as po_file: + write_po(po_file, catalog) + + +if __name__ == "__main__": + po_file_path = "app/translations/zh/LC_MESSAGES/messages.po" + fill_msg_with_msg_id(po_file_path) diff --git a/app/translations/__init__.py b/app/translations/__init__.py new file mode 100644 index 0000000..ed6d801 --- /dev/null +++ b/app/translations/__init__.py @@ -0,0 +1,34 @@ +import logging +from typing import Optional +from flask import g, request +from app.constants.locale import Locale + +logger = logging.getLogger(__name__) +# logger.setLevel(logging.DEBUG) + + +def get_locale() -> Optional[str]: + current_user = g.get("current_user") + if ( + current_user + and current_user.locale + and current_user.locale != "auto" + and current_user.locale in Locale.ids() + ): + # NOTE User.locale is not used + logging.debug("locale from user %s", current_user.locale) + return current_user.locale + # "zh" locale asssets is created from hardcoded strings + # "en" locale are machine translated + best_match = request.accept_languages.best_match(["zh", "en"], default="en") + logging.debug("locale from req %s", best_match) + return best_match + + +# @babel.timezoneselector +# def get_timezone(): +# # TODO 弄清 timezone 是什么东西 +# current_user = g.get('current_user') +# if current_user: +# if current_user.timezone: +# return current_user.timezone diff --git a/app/translations/en/LC_MESSAGES/messages.po b/app/translations/en/LC_MESSAGES/messages.po new file mode 100644 index 0000000..4e02285 --- /dev/null +++ b/app/translations/en/LC_MESSAGES/messages.po @@ -0,0 +1,1449 @@ +# English (United States) translations for PROJECT. +# Copyright (C) 2017 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-11-04 02:29+0900\n" +"PO-Revision-Date: 2017-04-14 15:38+0800\n" +"Last-Translator: FULL NAME \n" +"Language: en_US\n" +"Language-Team: en_US \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: app/apis/application.py:147 app/apis/invitation.py:200 +msgid "已接受" +msgstr "Approved" + +#: app/apis/application.py:154 app/apis/invitation.py:205 +msgid "已拒绝" +msgstr "Rejected" + +#: app/apis/application.py:181 +msgid "只有申请人可以删除申请" +msgstr "Only the applicant can remove the application" + +#: app/apis/application.py:183 app/apis/file.py:254 app/apis/invitation.py:236 +#: app/apis/project_set.py:102 app/apis/target.py:31 app/apis/term.py:151 +#: app/apis/term.py:311 app/core/rbac.py:483 +msgid "删除成功" +msgstr "Removed" + +#: app/apis/avatar.py:56 app/validators/avatar.py:23 +msgid "不支持的头像类型" +msgstr "Unsupported avatar type" + +#: app/apis/avatar.py:58 app/validators/avatar.py:25 +msgid "缺少id" +msgstr "Missing id" + +#: app/apis/avatar.py:75 app/apis/invitation.py:166 app/apis/me.py:116 +#: app/apis/me.py:149 app/apis/project.py:93 app/apis/project_set.py:71 +#: app/apis/source.py:204 app/apis/team.py:187 app/apis/term.py:122 +#: app/apis/term.py:278 app/apis/user.py:188 app/apis/user.py:220 +msgid "修改成功" +msgstr "Updated" + +#: app/apis/file.py:57 app/apis/project.py:53 +msgid "您没有此项目的访问权限" +msgstr "Access permission not granted" + +#: app/apis/file.py:121 +msgid "您没有此项目的上传文件权限" +msgstr "Upload file permission not granted" + +#: app/apis/file.py:159 app/apis/file.py:220 +msgid "您没有权限移动文件" +msgstr "Move file permission not granted" + +#: app/apis/file.py:225 +msgid "移动成功" +msgstr "Moved" + +#: app/apis/file.py:228 +msgid "您没有权限修改文件名" +msgstr "You do not have permission to modify the file name." + +#: app/apis/file.py:230 +msgid "改名成功" +msgstr "Renamed" + +#: app/apis/file.py:232 +msgid "需要提供parent_id或name参数" +msgstr "Please specify parent_id or name" + +#: app/apis/file.py:252 +msgid "您没有权限删除文件" +msgstr "Removal permission not granted" + +#: app/apis/file.py:279 app/apis/project.py:366 +msgid "您没有此项目所在团队使用自动标记限额的权限" +msgstr "Permission not granted" + +#: app/apis/file.py:282 app/apis/project.py:369 +msgid "源语言不支持自动标记" +msgstr "The source language does not support oCR" + +#: app/apis/file.py:284 +msgid "文件不是图像文件" +msgstr "Not an image file" + +#: app/apis/file.py:286 app/apis/project.py:378 +msgid "团队限额不足" +msgstr "Team credit insufficient" + +#: app/apis/file.py:288 app/constants/file.py:117 +msgid "已加入队列" +msgstr "Queued" + +#: app/apis/file.py:316 +msgid "处理成功" +msgstr "Complete" + +#: app/apis/invitation.py:156 +msgid "只能修改邀请角色等级比您低的邀请" +msgstr "You can only modify invitations for roles lower than your own." + +#: app/apis/invitation.py:163 app/models/user.py:383 +msgid "邀请的角色等级需要比您低" +msgstr "The role level of the invitee needs to be lower than yours." + +#: app/apis/invitation.py:234 +msgid "只能删除邀请角色等级比您低的邀请" +msgstr "You can only delete invitations for roles that are lower than your level." + +#: app/apis/me.py:180 +msgid "修改成功,请重新登陆" +msgstr "Updated. Please log in again." + +#: app/apis/project.py:125 +msgid "完结项目成功" +msgstr "Project completed successfully." + +#: app/apis/project.py:150 +msgid "操作无效" +msgstr "Invalid operation." + +#: app/apis/project.py:155 +msgid "恢复项目成功" +msgstr "Project restored successfully." + +#: app/apis/project.py:203 +msgid "添加目标语言成功" +msgstr "Successfully added target language." + +#: app/apis/project.py:371 +msgid "自动标记进行中" +msgstr "Auto-tagging in progress." + +#: app/apis/project.py:376 +msgid "未发现需要标记的图片" +msgstr "No images found to be marked." + +#: app/apis/project.py:381 +msgid "已开始自动标记" +msgstr "Auto-tagging started." + +#: app/apis/project.py:411 +msgid "销毁计划创建成功" +msgstr "Destruction plan created successfully." + +#: app/apis/project.py:438 +msgid "销毁计划取消成功" +msgstr "Destruction plan cancellation successful." + +#: app/apis/project.py:468 +msgid "完结计划创建成功" +msgstr "Plan completion created successfully." + +#: app/apis/project.py:495 +msgid "完结计划取消成功" +msgstr "The completion plan was successfully canceled." + +#: app/apis/project_set.py:66 +msgid "默认项目集不能进行设置" +msgstr "The default project set cannot be configured." + +#: app/apis/project_set.py:100 +msgid "默认项目集不能删除" +msgstr "The default project set cannot be deleted." + +#: app/apis/source.py:104 +msgid "只有图片文件能添加原文" +msgstr "Only image files can add the original text." + +#: app/apis/source.py:141 +msgid "只有图片文件能修改原文" +msgstr "Only image files can modify the original text." + +#: app/apis/source.py:167 +msgid "只有图片文件能删除原文" +msgstr "Only image files can delete the original text." + +#: app/apis/source.py:196 +msgid "只有图片文件能修改原文顺序" +msgstr "Only image files can change the original text order." + +#: app/apis/team.py:128 app/apis/team.py:326 app/apis/team.py:387 +#: app/apis/team.py:451 app/apis/term.py:84 app/apis/term.py:238 +msgid "创建成功" +msgstr "Created successfully." + +#: app/apis/team.py:218 +msgid "此团队含有未完结的项目,不能解散(请先完结或者转移项目)" +msgstr "" +"This team has unfinished projects and cannot be disbanded (please " +"complete or transfer the projects first)." + +#: app/apis/team.py:221 +msgid "解散成功" +msgstr "Disbanded successfully." + +#: app/apis/team.py:308 app/apis/team.py:352 +msgid "您没有权限在这个团队创建项目" +msgstr "You do not have permission to create projects in this team." + +#: app/apis/team.py:338 +msgid "创建导出任务成功" +msgstr "Export task created successfully." + +#: app/apis/team.py:447 +msgid "您没有权限在这个团队创建项目集" +msgstr "You do not have permission to create a project collection in this team." + +#: app/apis/team.py:478 app/apis/team.py:498 app/apis/team.py:529 +#: app/apis/team.py:556 +msgid "您没有权限查看本团队项目统计" +msgstr "You do not have permission to view the statistics of this team project." + +#: app/apis/term.py:73 +msgid "您没有「创建术语库」权限,不能在此团队创建术语库" +msgstr "" +"You do not have the \"Create Termbase\" permission and cannot create a " +"termbase in this team." + +#: app/apis/term.py:113 +msgid "您没有「设置他人术语库」权限,只能设置自己创建的术语库" +msgstr "" +"You do not have the \"Set Others' Termbase\" permission and can only set " +"the termbase you created." + +#: app/apis/term.py:148 +msgid "您没有「删除他人术语库」权限,只能删除自己创建的术语库" +msgstr "" +"You do not have the \"Delete Others' Termbase\" permission and can only " +"delete termbases you created." + +#: app/apis/term.py:180 +msgid "您没有「查看他人术语库」权限,只能查看自己创建的术语库中的术语" +msgstr "" +"You do not have the \"View Others' Termbase\" permission and can only " +"view terms in the termbase you created." + +#: app/apis/term.py:225 +msgid "您没有「在他人术语库中增加术语」权限,只能在自己创建的术语库中增加术语" +msgstr "" +"You do not have the \"add terms to others' termbases\" permission and can" +" only add terms to termbases you created." + +#: app/apis/term.py:271 +msgid "您没有「修改他人术语」权限,只能修改自己创建的术语或自己术语库中的术语" +msgstr "" +"You do not have the \"Modify Others' Terms\" permission and can only " +"modify terms you created or terms in your own termbase." + +#: app/apis/term.py:306 +msgid "您没有「删除他人术语」权限,只能删除自己创建的术语或自己术语库中的术语" +msgstr "" +"You do not have the \"Delete Others' Terms\" permission and can only " +"delete terms you created or terms in your own termbase." + +#: app/apis/user.py:124 app/apis/user.py:175 +msgid "注册成功" +msgstr "Registration successful." + +#: app/constants/file.py:61 +msgid "解析未开始" +msgstr "Analysis not started." + +#: app/constants/file.py:62 +msgid "解析排队中" +msgstr "Parsing in queue." + +#: app/constants/file.py:63 +msgid "解析中" +msgstr "Analyzing." + +#: app/constants/file.py:64 +msgid "解析失败" +msgstr "Parsing failed." + +#: app/constants/file.py:65 +msgid "解析成功" +msgstr "Parsing successful." + +#: app/constants/file.py:71 +msgid "自动标记未开始" +msgstr "Auto Mark as Not Started" + +#: app/constants/file.py:72 app/constants/output.py:17 +#: app/constants/project.py:31 +msgid "排队中" +msgstr "In queue." + +#: app/constants/file.py:73 +msgid "自动标记中" +msgstr "Auto-tagging in progress." + +#: app/constants/file.py:74 +msgid "自动标记失败" +msgstr "Automatic tagging failed." + +#: app/constants/file.py:75 app/constants/file.py:123 +msgid "自动标记完成" +msgstr "Auto Mark Complete" + +#: app/constants/file.py:89 +msgid "其他错误" +msgstr "Other error." + +#: app/constants/file.py:90 +msgid "未知字符集" +msgstr "Unknown character set." + +#: app/constants/file.py:92 +msgid "文件无法读取,请确认文件完好或尝试重新上传" +msgstr "" +"The file cannot be read. Please ensure the file is intact or try re-" +"uploading." + +#: app/constants/file.py:95 +msgid "图片读取失败,请稍后再试(1)" +msgstr "Failed to load image, please try again later (1)." + +#: app/constants/file.py:98 +msgid "图片读取失败,请稍后再试(2)" +msgstr "Failed to load image, please try again later (2)." + +#: app/constants/file.py:100 +msgid "图片超过 20MB 无法标记" +msgstr "Images larger than 20MB cannot be tagged." + +#: app/constants/file.py:102 +msgid "自动标记服务离线,请稍后再试" +msgstr "Automatic tagging service is offline, please try again later." + +#: app/constants/file.py:118 +msgid "重试中" +msgstr "Retrying." + +#: app/constants/file.py:119 +msgid "翻找图片中" +msgstr "Searching in images." + +#: app/constants/file.py:120 +msgid "整理数据中" +msgstr "Organizing data." + +#: app/constants/file.py:121 +msgid "图片识别中" +msgstr "Image recognition in progress." + +#: app/constants/file.py:122 +msgid "标记中" +msgstr "Marked in progress." + +#: app/constants/locale.py:15 +msgid "自动" +msgstr "Auto" + +#: app/constants/locale.py:15 +msgid "遵循浏览器设置" +msgstr "Follow browser settings." + +#: app/constants/locale.py:16 +msgid "中文(简体)" +msgstr "Chinese (Simplified)" + +#: app/constants/locale.py:17 +msgid "中文(繁体)" +msgstr "Chinese (Traditional)" + +#: app/constants/locale.py:18 +msgid "英文" +msgstr "English" + +#: app/constants/output.py:18 +msgid "翻译整理中" +msgstr "Translation in progress." + +#: app/constants/output.py:19 +msgid "源文件整理中" +msgstr "The source file is being organized." + +#: app/constants/output.py:20 +msgid "压缩中" +msgstr "Compressing." + +#: app/constants/output.py:21 +msgid "已完成" +msgstr "Completed." + +#: app/constants/output.py:22 +msgid "导出错误,请重试" +msgstr "Export error, please try again." + +#: app/constants/project.py:16 app/constants/project.py:32 +msgid "进行中" +msgstr "In progress." + +#: app/constants/project.py:17 +msgid "已完结" +msgstr "Completed." + +#: app/constants/project.py:18 +msgid "等待完结" +msgstr "Waiting to be completed." + +#: app/constants/project.py:19 +msgid "等待销毁" +msgstr "Pending Destruction" + +#: app/constants/project.py:20 +msgid "已删除" +msgstr "Deleted" + +#: app/constants/project.py:33 +msgid "成功" +msgstr "Success." + +#: app/constants/project.py:34 +msgid "错误" +msgstr "Error." + +#: app/constants/project.py:46 +msgid "从 Labelplus 文本导入中断,请重试,如仍出现同样错误,请联系开发团队" +msgstr "" +"Import from Labelplus text interrupted, please try again. If the same " +"error occurs, please contact the development team." + +#: app/constants/project.py:51 +msgid "从 Labelplus 文本导入时,没有有效的翻译目标语言" +msgstr "No valid target translation language when importing text from Labelplus." + +#: app/constants/project.py:53 +msgid "从 Labelplus 文本导入时,项目没有创建人" +msgstr "When importing text from Labelplus, the project has no creator." + +#: app/constants/project.py:55 +msgid "Labelplus 文本解析失败,请联系开发团队" +msgstr "Labelplus text parsing failed, please contact the development team." + +#: app/core/rbac.py:49 +msgid "关闭申请加入" +msgstr "Close join request." + +#: app/core/rbac.py:50 +msgid "只能通过邀请新增成员" +msgstr "Members can only be added by invitation." + +#: app/core/rbac.py:53 +msgid "所有人" +msgstr "Everyone" + +#: app/core/rbac.py:54 +msgid "所以用户都可以申请加入" +msgstr "Therefore, all users can apply to join." + +#: app/core/rbac.py:69 +msgid "无需审核" +msgstr "No review required." + +#: app/core/rbac.py:70 +msgid "用户申请后直接加入" +msgstr "Users join directly after applying." + +#: app/core/rbac.py:73 +msgid "管理员审核" +msgstr "Admin Review" + +#: app/core/rbac.py:74 +msgid "管理员同意申请后加入" +msgstr "Join after the administrator approves the application." + +#: app/core/rbac.py:99 +msgid "访问" +msgstr "Visit" + +#: app/core/rbac.py:100 +msgid "解散" +msgstr "Disband" + +#: app/core/rbac.py:101 +msgid "修改设置" +msgstr "Modify Settings" + +#: app/core/rbac.py:102 +msgid "新建角色" +msgstr "Create Character" + +#: app/core/rbac.py:103 +msgid "删除角色" +msgstr "Delete Role" + +#: app/core/rbac.py:104 +msgid "审核用户加入申请" +msgstr "Review user join request." + +#: app/core/rbac.py:106 +msgid "邀请用户" +msgstr "Invite User" + +#: app/core/rbac.py:107 +msgid "邀请时仅可设置比自己角色等级低的用户" +msgstr "You can only invite users with a lower role level than your own." + +#: app/core/rbac.py:110 +msgid "删除用户" +msgstr "Delete User" + +#: app/core/rbac.py:111 +msgid "仅可删除比自己角色等级低的用户" +msgstr "Only users with a lower role level than your own can be deleted." + +#: app/core/rbac.py:114 +msgid "修改用户角色" +msgstr "Modify User Role" + +#: app/core/rbac.py:115 +msgid "仅可修改比自己角色等级低的角色" +msgstr "Can only modify characters with a lower role level than your own." + +#: app/core/rbac.py:117 +msgid "修改用户备注" +msgstr "Edit User Note" + +#: app/core/rbac.py:284 app/core/rbac.py:322 +msgid "角色等级不能大于等于自己的角色等级" +msgstr "" +"Character level cannot be greater than or equal to your own character " +"level." + +#: app/core/rbac.py:286 app/core/rbac.py:324 +msgid "角色权限不能多于自己的角色权限" +msgstr "Role permissions cannot exceed your own role permissions." + +#: app/core/rbac.py:317 +msgid "不能修改系统角色" +msgstr "Cannot modify system role." + +#: app/core/rbac.py:339 +msgid "不能删除系统角色" +msgstr "Cannot delete system role." + +#: app/core/rbac.py:388 app/core/rbac.py:389 +msgid "此项目/团队不允许申请加入" +msgstr "This project/team does not allow requests to join." + +#: app/core/rbac.py:464 +msgid "用户不存在于团队" +msgstr "User does not exist in the team." + +#: app/core/rbac.py:474 +msgid "退出成功" +msgstr "Logout successful." + +#: app/core/rbac.py:478 +msgid "您没有删除用户权限" +msgstr "You do not have permission to delete users." + +#: app/core/rbac.py:481 +msgid "只能删除角色等级比您低的用户" +msgstr "You can only delete users with a role level lower than yours." + +#: app/core/rbac.py:501 +msgid "您不能修改自己的角色" +msgstr "You cannot modify your role." + +#: app/core/rbac.py:504 +msgid "您没有修改用户角色权限" +msgstr "You do not have permission to modify user roles." + +#: app/core/rbac.py:508 +msgid "只能为比您角色等级低的用户设置角色" +msgstr "Can only assign roles to users with a lower role level than yours." + +#: app/core/rbac.py:511 +msgid "只能为用户设置比您角色等级低的角色" +msgstr "You can only assign roles to users that are lower than your role level." + +#: app/core/rbac.py:539 +msgid "默认角色不能设置为创建者" +msgstr "The default role cannot be set as the creator." + +#: app/decorators/file.py:48 +msgid "不能对文件夹执行 " +msgstr "Cannot perform on folder." + +#: app/decorators/file.py:60 +msgid "不能对文件执行 " +msgstr "Cannot execute on the file." + +#: app/exceptions/auth.py:13 +msgid "验证异常" +msgstr "Authorization error." + +#: app/exceptions/auth.py:23 app/validators/custom_validate.py:103 +msgid "此邮箱未注册" +msgstr "This email is not registered." + +#: app/exceptions/auth.py:33 app/validators/v_code.py:29 +msgid "密码错误" +msgstr "Incorrect password." + +#: app/exceptions/auth.py:44 +msgid "需要令牌" +msgstr "Token required." + +#: app/exceptions/auth.py:55 +msgid "无效的令牌" +msgstr "Invalid token." + +#: app/exceptions/auth.py:65 app/models/user.py:201 app/validators/v_code.py:27 +msgid "用户不存在" +msgstr "User does not exist." + +#: app/exceptions/auth.py:75 +msgid "此用户被封禁" +msgstr "This user is banned." + +#: app/exceptions/auth.py:85 app/validators/custom_message.py:4 +msgid "邮箱格式不正确" +msgstr "Not a valid email address" + +#: app/exceptions/auth.py:95 +msgid "此邮箱已被注册" +msgstr "This email is already registered." + +#: app/exceptions/auth.py:105 app/exceptions/team.py:38 +msgid "仅可使用中文/日文/韩文/英文/数字/_" +msgstr "Only Chinese/Japanese/Korean/English/numbers/_ can be used." + +#: app/exceptions/auth.py:115 +msgid "此昵称已被使用" +msgstr "This nickname is already in use." + +#: app/exceptions/auth.py:125 app/exceptions/team.py:58 +msgid "长度为2到18个字符" +msgstr "Length must be 2 to 18 characters." + +#: app/exceptions/auth.py:135 +msgid "邮箱不在白名单中,请联系管理员添加" +msgstr "" +"The email is not on the whitelist, please contact the administrator to " +"add it." + +#: app/exceptions/base.py:26 +msgid "未定义的错误" +msgstr "Undefined error." + +#: app/exceptions/base.py:36 +msgid "错误的ObjectId" +msgstr "Invalid ObjectId." + +#: app/exceptions/base.py:46 +msgid "角色不存在" +msgstr "Role does not exist." + +#: app/exceptions/base.py:56 +msgid "没有权限" +msgstr "No permission." + +#: app/exceptions/base.py:95 +msgid "逗号分割的数字字符串包含非数字" +msgstr "Comma-separated numeric string contains non-numeric characters." + +#: app/exceptions/base.py:105 +msgid "权限不存在" +msgstr "Permission does not exist." + +#: app/exceptions/base.py:115 +msgid "文件名错误" +msgstr "Filename error." + +#: app/exceptions/base.py:125 +msgid "文件类型不支持此操作" +msgstr "The file type does not support this operation." + +#: app/exceptions/base.py:135 +msgid "上传未包含文件" +msgstr "Upload does not contain a file." + +#: app/exceptions/base.py:145 +msgid "请求参数不能为空或没有需要的值" +msgstr "Request parameters cannot be empty or lack the required values." + +#: app/exceptions/base.py:155 +msgid "请求参数错误" +msgstr "Request parameter error." + +#: app/exceptions/file.py:13 +msgid "文件异常" +msgstr "File error." + +#: app/exceptions/file.py:23 +msgid "文件/文件夹不存在,可能已被删除" +msgstr "File/folder does not exist, it may have been deleted." + +#: app/exceptions/file.py:33 +msgid "文件夹不存在,可能已被删除" +msgstr "The folder does not exist and may have been deleted." + +#: app/exceptions/file.py:43 +msgid "后缀名必须属于原文件的文件类型" +msgstr "The file extension must match the original file type." + +#: app/exceptions/file.py:53 +msgid "源文件不存在" +msgstr "Source file does not exist." + +#: app/exceptions/file.py:60 +msgid "未知" +msgstr "Unknown" + +#: app/exceptions/file.py:62 +msgid "待上传" +msgstr "Pending upload." + +#: app/exceptions/file.py:64 +msgid "用户操作完结时清除" +msgstr "Clear upon user operation completion." + +#: app/exceptions/file.py:66 +msgid "含有敏感信息已删除" +msgstr "Sensitive information has been deleted." + +#: app/exceptions/file.py:78 +msgid "原文不存在,已被删除" +msgstr "The original text does not exist and has been deleted." + +#: app/exceptions/file.py:88 +msgid "正在移动顺序,请稍后尝试" +msgstr "Moving the order, please try again later." + +#: app/exceptions/file.py:98 +msgid "翻译不唯一" +msgstr "Translation is not unique." + +#: app/exceptions/join_process.py:13 +msgid "加入流程异常" +msgstr "Join process error." + +#: app/exceptions/join_process.py:23 +msgid "不支持此团体类型" +msgstr "This group type is not supported." + +#: app/exceptions/join_process.py:35 +msgid "用户已经在 “{name}” 中" +msgstr "User is already in \"{name}\"." + +#: app/exceptions/join_process.py:45 +msgid "已邀请此用户,请等待用户确认" +msgstr "This user has been invited, please wait for the user to confirm." + +#: app/exceptions/join_process.py:55 +msgid "您已经申请,请等待管理员确认" +msgstr "You have already applied, please wait for administrator confirmation." + +#: app/exceptions/join_process.py:65 app/exceptions/join_process.py:75 +msgid "邀请不存在" +msgstr "Invitation does not exist." + +#: app/exceptions/join_process.py:85 +msgid "允许申请类型不存在" +msgstr "The allowed application type does not exist." + +#: app/exceptions/join_process.py:95 +msgid "申请审核类型不存在" +msgstr "The application review type does not exist." + +#: app/exceptions/join_process.py:105 +msgid "无法修改此邀请" +msgstr "Unable to modify this invitation." + +#: app/exceptions/join_process.py:115 +msgid "无法修改此申请" +msgstr "Unable to modify this application." + +#: app/exceptions/join_process.py:125 +msgid "已满员,不可申请加入或邀请新成员" +msgstr "The group is full, unable to apply to join or invite new members." + +#: app/exceptions/join_process.py:135 +msgid "创建者无法退出团体,请转移创建者权限后操作" +msgstr "" +"The creator cannot leave the group. Please transfer creator permissions " +"before proceeding." + +#: app/exceptions/join_process.py:145 +msgid "此团体不向公众开放" +msgstr "This group is not open to the public." + +#: app/exceptions/language.py:13 +msgid "语言异常" +msgstr "Language anomaly." + +#: app/exceptions/language.py:23 +msgid "语言不存在" +msgstr "Language does not exist." + +#: app/exceptions/language.py:33 +msgid "原语言和目标语言不能相同" +msgstr "The source language and the target language cannot be the same." + +#: app/exceptions/language.py:43 +msgid "需要设置目标语言" +msgstr "Target language needs to be set." + +#: app/exceptions/language.py:53 +msgid "已有此目标语言" +msgstr "This target language already exists." + +#: app/exceptions/message.py:13 +msgid "站内信异常" +msgstr "Site message error." + +#: app/exceptions/message.py:23 +msgid "站内信类型错误" +msgstr "Incorrect in-site message type." + +#: app/exceptions/output.py:13 +msgid "导出异常" +msgstr "Export Error" + +#: app/exceptions/output.py:23 +msgid "导出导出文件不存在" +msgstr "The export file does not exist." + +#: app/exceptions/output.py:33 +msgid "导出过于频繁,请稍后再试" +msgstr "Exporting too frequently, please try again later." + +#: app/exceptions/project.py:13 +msgid "项目异常" +msgstr "Project error." + +#: app/exceptions/project.py:23 +msgid "项目不存在" +msgstr "Project does not exist." + +#: app/exceptions/project.py:33 app/exceptions/project.py:267 +msgid "项目已完结" +msgstr "The project is completed." + +#: app/exceptions/project.py:43 +msgid "图片正在自动标记中,请稍后" +msgstr "Images are being automatically tagged, please wait a moment." + +#: app/exceptions/project.py:53 +msgid "图片已自动标记完成,不能再次自动标记" +msgstr "" +"The image has been automatically tagged and cannot be automatically " +"tagged again." + +#: app/exceptions/project.py:63 +msgid "文件不存在" +msgstr "File not found." + +#: app/exceptions/project.py:73 +msgid "文件名重复" +msgstr "Duplicate filename." + +#: app/exceptions/project.py:83 +msgid "文本正在解析,请稍后" +msgstr "The text is being parsed, please wait a moment." + +#: app/exceptions/project.py:93 +msgid "文本已解析成功" +msgstr "Text parsed successfully." + +#: app/exceptions/project.py:103 +msgid "翻译不能为空" +msgstr "Translation cannot be empty." + +#: app/exceptions/project.py:113 +msgid "目标必须是文件夹" +msgstr "The target must be a folder." + +#: app/exceptions/project.py:123 +msgid "目标文件夹不能是自己本身" +msgstr "The destination folder cannot be itself." + +#: app/exceptions/project.py:133 +msgid "目标文件夹与原父级文件夹相同" +msgstr "The target folder is the same as the original parent folder." + +#: app/exceptions/project.py:143 +msgid "不能移动到自己的子文件夹" +msgstr "Cannot move to its own subfolder." + +#: app/exceptions/project.py:153 +msgid "不能操作非激活修订版" +msgstr "Cannot operate on inactive revisions." + +#: app/exceptions/project.py:163 +msgid "此修订版已激活" +msgstr "This revision has been activated." + +#: app/exceptions/project.py:173 +msgid "文件夹不能进行修订版操作" +msgstr "Folders cannot perform revision operations." + +#: app/exceptions/project.py:183 +msgid "原文已有翻译" +msgstr "The original text has already been translated." + +#: app/exceptions/project.py:196 +msgid "备注不能为空" +msgstr "Remarks cannot be empty." + +#: app/exceptions/project.py:206 +msgid "项目集不存在" +msgstr "Project set does not exist." + +#: app/exceptions/project.py:216 +msgid "项目还没有正式完结" +msgstr "The project is not officially completed yet." + +#: app/exceptions/project.py:226 +msgid "项目目标语言不存在" +msgstr "The target language for the project does not exist." + +#: app/exceptions/project.py:236 +msgid "项目已有销毁计划" +msgstr "The project already has a destruction plan." + +#: app/exceptions/project.py:246 +msgid "项目已有完结计划" +msgstr "The project already has a completion plan." + +#: app/exceptions/project.py:256 +msgid "项目没有销毁计划" +msgstr "There is no plan to terminate the project." + +#: app/exceptions/project.py:277 +msgid "\"翻译数据.txt\" 解析失败" +msgstr "\"translation_data.txt\" parsing failed." + +#: app/exceptions/team.py:13 +msgid "团队异常" +msgstr "Team error." + +#: app/exceptions/team.py:23 +msgid "团队不存在" +msgstr "Team does not exist." + +#: app/exceptions/team.py:48 +msgid "此团队名已被使用" +msgstr "This team name is already in use." + +#: app/exceptions/team.py:68 +msgid "仅站点管理员可创建团队" +msgstr "Only site administrators can create teams." + +#: app/exceptions/term.py:13 +msgid "术语库异常" +msgstr "Termbase error." + +#: app/exceptions/term.py:23 +msgid "术语库不存在" +msgstr "Termbase does not exist." + +#: app/exceptions/term.py:33 +msgid "术语不存在" +msgstr "The term does not exist." + +#: app/exceptions/v_code.py:13 +msgid "验证码异常" +msgstr "Captcha error." + +#: app/exceptions/v_code.py:23 +msgid "验证码过期,请重新输入" +msgstr "The captcha has expired, please re-enter it." + +#: app/exceptions/v_code.py:33 +msgid "验证码错误" +msgstr "Incorrect captcha" + +#: app/exceptions/v_code.py:43 +msgid "验证码失效,请重新获取" +msgstr "The captcha has expired, please obtain a new one." + +#: app/exceptions/v_code.py:57 +msgid "请等候{seconds}秒后再试" +msgstr "Please wait {seconds} seconds before trying again." + +#: app/models/application.py:87 +msgid "已被他人拒绝" +msgstr "Rejected by others." + +#: app/models/application.py:89 +msgid "已被他人允许" +msgstr "Allowed by others." + +#: app/models/file.py:101 +msgid "文件名为空" +msgstr "The file name is empty." + +#: app/models/file.py:104 +msgid "文件名过长" +msgstr "Filename too long." + +#: app/models/file.py:107 +msgid "文件名不能是 . 或 .." +msgstr "The file name cannot be . or ..." + +#: app/models/file.py:110 +msgid "文件名不能以 . 结尾" +msgstr "The file name cannot end with a period." + +#: app/models/file.py:113 +msgid "文件名前缀为空" +msgstr "The file name prefix is empty." + +#: app/models/file.py:129 +msgid "文件名不能包含下列任何字符: \\ / : * ? \" < > |" +msgstr "" +"The file name cannot contain any of the following characters: \\ / : * ? " +"\" < > |." + +#: app/models/file.py:1304 +msgid "只有图片文件能移动原文顺序" +msgstr "Only image files can change the original order." + +#: app/models/invitation.py:63 +msgid "已被拒绝" +msgstr "Rejected." + +#: app/models/invitation.py:65 +msgid "已被同意" +msgstr "Agreed." + +#: app/models/message.py:41 +msgid "非系统消息类型" +msgstr "Non-system message type" + +#: app/models/project.py:87 +msgid "仅团队成员" +msgstr "Team members only." + +#: app/models/project.py:114 +msgid "访问项目" +msgstr "Visit project" + +#: app/models/project.py:115 +msgid "删除项目" +msgstr "Delete project" + +#: app/models/project.py:116 +msgid "设置项目" +msgstr "Set Project" + +#: app/models/project.py:118 +msgid "完结项目" +msgstr "Completed Project" + +#: app/models/project.py:119 +msgid "上传图片" +msgstr "Upload Image" + +#: app/models/project.py:120 +msgid "移动文件" +msgstr "Move file" + +#: app/models/project.py:121 +msgid "修改图片名称" +msgstr "Edit Image Name" + +#: app/models/project.py:122 +msgid "删除图片" +msgstr "Delete image" + +#: app/models/project.py:123 +msgid "导出翻译" +msgstr "Export Translation" + +#: app/models/project.py:124 +msgid "新建图片标记" +msgstr "New Image Tag" + +#: app/models/project.py:125 +msgid "移动图片标记" +msgstr "Move Image Tag" + +#: app/models/project.py:126 +msgid "删除图片标记" +msgstr "Delete image tag" + +#: app/models/project.py:127 +msgid "新增翻译" +msgstr "Add Translation" + +#: app/models/project.py:128 +msgid "删除他人翻译" +msgstr "Delete others' translations." + +#: app/models/project.py:129 +msgid "校对翻译" +msgstr "Proofread translation." + +#: app/models/project.py:131 +msgid "选定翻译" +msgstr "Selected translation." + +#: app/models/project.py:132 +msgid "将某条翻译指定导出项" +msgstr "Specify an export item for a particular translation." + +#: app/models/project.py:134 +msgid "新增目标语言" +msgstr "Add target language." + +#: app/models/project.py:135 +msgid "修改目标语言" +msgstr "Modify target language." + +#: app/models/project.py:136 +msgid "删除目标语言" +msgstr "Delete target language." + +#: app/models/project.py:145 app/models/team.py:135 +msgid "创建人" +msgstr "Creator" + +#: app/models/project.py:176 app/models/team.py:167 +msgid "管理员" +msgstr "Administrator" + +#: app/models/project.py:207 +msgid "监理" +msgstr "Supervision" + +#: app/models/project.py:232 +msgid "校对" +msgstr "Proofreading" + +#: app/models/project.py:247 +msgid "翻译" +msgstr "Translate" + +#: app/models/project.py:260 +msgid "嵌字" +msgstr "Kerning" + +#: app/models/project.py:272 +msgid "见习翻译" +msgstr "Trainee Translator" + +#: app/models/project.py:425 +msgid "必须是Language对象" +msgstr "Must be a Language object." + +#: app/models/project.py:560 +msgid "只允许项目所属团队成员申请加入" +msgstr "Only members of the project team are allowed to apply to join." + +#: app/models/project.py:627 +msgid "暂不支持的文件格式" +msgstr "File format not supported" + +#: app/models/project.py:948 +msgid "框内" +msgstr "In the box." + +#: app/models/project.py:950 +msgid "框外" +msgstr "Out of the box" + +#: app/models/project.py:953 +msgid "可使用 LabelPlus Photoshop 脚本导入 psd 中" +msgstr "You can use the LabelPlus Photoshop script to import into PSD." + +#: app/models/team.py:59 +msgid "访问团队" +msgstr "Visit team" + +#: app/models/team.py:60 +msgid "解散团队" +msgstr "Disband Team" + +#: app/models/team.py:61 +msgid "设置团队" +msgstr "Set Team" + +#: app/models/team.py:64 +msgid "管理项目" +msgstr "Manage Projects" + +#: app/models/team.py:65 +msgid "自动获得团队内所有项目的管理员权限" +msgstr "Automatically gain admin access to all projects within the team." + +#: app/models/team.py:68 +msgid "创建术语库" +msgstr "Create a termbase." + +#: app/models/team.py:69 +msgid "在团队内创建术语库" +msgstr "Create a term base within the team." + +#: app/models/team.py:72 +msgid "查看他人术语库" +msgstr "View others' termbase." + +#: app/models/team.py:73 +msgid "若无此权限只能查看自己创建的术语库中的术语" +msgstr "" +"If you do not have this permission, you can only view the terms in the " +"termbase you created." + +#: app/models/team.py:76 +msgid "设置他人术语库" +msgstr "Set others' termbase." + +#: app/models/team.py:77 +msgid "若无此权限只能修改自己创建的术语库设置" +msgstr "" +"Without this permission, you can only modify the settings of the termbase" +" you created." + +#: app/models/team.py:80 +msgid "删除他人术语库" +msgstr "Delete others' termbase." + +#: app/models/team.py:81 +msgid "若无此权限只能删除自己创建的术语库" +msgstr "Without this permission, you can only delete the term base you created." + +#: app/models/team.py:84 +msgid "在他人术语库中增加术语" +msgstr "Add a term to another's terminology database." + +#: app/models/team.py:85 +msgid "若无此权限只能在自己创建的术语库中增加术语" +msgstr "" +"If you do not have this permission, you can only add terms to the " +"termbase you created." + +#: app/models/team.py:88 +msgid "修改他人术语" +msgstr "Modify others' terms." + +#: app/models/team.py:89 +msgid "若无此权限只能修改自己创建的术语或自己术语库中的术语" +msgstr "" +"If you do not have this permission, you can only modify terms you created" +" or terms in your own termbase." + +#: app/models/team.py:94 +msgid "删除他人术语" +msgstr "Delete others' terms." + +#: app/models/team.py:95 +msgid "若无此权限只能删除自己创建的术语或自己术语库中的术语" +msgstr "" +"If you do not have this permission, you can only delete terms you created" +" or terms in your own termbase." + +#: app/models/team.py:100 +msgid "创建项目" +msgstr "Create Project" + +#: app/models/team.py:101 +msgid "在团队内创建项目" +msgstr "Create a project within the team." + +#: app/models/team.py:104 +msgid "创建项目集" +msgstr "Create Portfolio" + +#: app/models/team.py:105 +msgid "在团队内创建项目集" +msgstr "Create a project set within the team." + +#: app/models/team.py:108 +msgid "设置项目集" +msgstr "Set Portfolio" + +#: app/models/team.py:109 +msgid "修改团队内项目集的设置" +msgstr "Modify the settings of the project collection within the team." + +#: app/models/team.py:112 +msgid "删除项目集" +msgstr "Delete project set" + +#: app/models/team.py:113 +msgid "删除团队内的项目集" +msgstr "Delete the portfolio within the team." + +#: app/models/team.py:116 +msgid "使用自动标记额度" +msgstr "Use automatic tagging quota." + +#: app/models/team.py:117 +msgid "可以使用团队的自动标记额度" +msgstr "The team's automatic tagging quota can be used." + +#: app/models/team.py:120 +msgid "使用机器翻译额度" +msgstr "Use machine translation quota." + +#: app/models/team.py:121 +msgid "可以使用团队的机器翻译额度" +msgstr "The team's machine translation quota can be used." + +#: app/models/team.py:124 +msgid "查看项目统计" +msgstr "View project statistics" + +#: app/models/team.py:125 +msgid "可以查看团队项目统计" +msgstr "You can view team project statistics." + +#: app/models/team.py:198 +msgid "资深成员" +msgstr "Senior Member" + +#: app/models/team.py:219 +msgid "成员" +msgstr "Member" + +#: app/models/team.py:238 +msgid "见习成员" +msgstr "Trainee Member" + +#: app/models/user.py:189 +msgid "令牌格式错误,应形如 Bearer x.x.x" +msgstr "Token format error, should be like Bearer x.x.x." + +#: app/models/user.py:204 +msgid "密码已修改,请重新登录" +msgstr "Password has been changed, please log in again." + +#: app/models/user.py:399 +msgid "此用户已有申请,已直接加入" +msgstr "This user already has an application and has been directly added." + +#: app/models/user.py:406 +msgid "此用户是项目所在团队成员,已直接加入" +msgstr "This user is a member of the project team and has been directly added." + +#: app/models/user.py:412 +msgid "邀请成功,请等待用户确认" +msgstr "Invitation successful, please wait for user confirmation." + +#: app/models/user.py:443 +msgid "加入成功,管理员继承自团队" +msgstr "Joined successfully, administrator inherited from the team." + +#: app/models/user.py:456 +msgid "管理员先前邀请了您,已直接加入" +msgstr "" +"The administrator previously invited you, and you have been directly " +"added." + +#: app/models/user.py:463 +msgid "项目不允许申请加入" +msgstr "The project does not allow requests to join." + +#: app/models/user.py:469 +msgid "加入成功" +msgstr "Joined successfully." + +#: app/models/user.py:472 +msgid "申请成功,请等待管理员审核" +msgstr "Application successful, please wait for administrator review." + +#: app/models/v_code.py:304 +msgid "重置您的安全邮箱" +msgstr "Reset your security email." + +#: app/models/v_code.py:305 +msgid "确认您的安全邮箱" +msgstr "Confirm your secure email." + +#: app/models/v_code.py:306 +msgid "重置您的密码" +msgstr "Reset your password." + +#: app/validators/custom_message.py:3 +msgid "必填" +msgstr "Required" + +#: app/validators/custom_validate.py:21 +msgid "不可为空" +msgstr "Cannot be empty." + +#: app/validators/custom_validate.py:35 +msgid "此项不可选" +msgstr "This option is not selectable." + +#: app/validators/custom_validate.py:76 app/validators/custom_validate.py:79 +#: app/validators/custom_validate.py:109 app/validators/custom_validate.py:124 +#: app/validators/custom_validate.py:127 app/validators/custom_validate.py:134 +#: app/validators/custom_validate.py:141 app/validators/custom_validate.py:148 +#: app/validators/custom_validate.py:151 app/validators/custom_validate.py:169 +#: app/validators/custom_validate.py:172 app/validators/custom_validate.py:178 +#: app/validators/custom_validate.py:181 app/validators/custom_validate.py:184 +msgid "长度为{min}到{max}个字符" +msgstr "Length must be between {min} and {max}" + +#: app/validators/custom_validate.py:160 +msgid "等级需要大于{min},小于{max}(您的等级)" +msgstr "Level must be greater than {min} and less than {max} (your level)." + +#: app/validators/source.py:41 app/validators/translation.py:27 +msgid "没有有效参数" +msgstr "No valid parameters." + +#~ msgid "此邮箱已存在,您可以直接登录" +#~ msgstr "" + +#~ msgid "此昵称已存在" +#~ msgstr "" + +#~ msgid "验证码异常基类" +#~ msgstr "" + +#~ msgid "需要登录" +#~ msgstr "" + +#~ msgid "无效的令牌 ({message})" +#~ msgstr "" + +#~ msgid "验证码已过期,请重新申请" +#~ msgstr "" + +#~ msgid "请等候{second}秒后再试" +#~ msgstr "" + +#~ msgid "冷却中,请稍后再试" +#~ msgstr "" + +#~ msgid "这个用户还没有签名" +#~ msgstr "This user has not signature" + +#~ msgid "此昵称已存在,请尝试其他昵称" +#~ msgstr "" + +#~ msgid "此邮箱已存在,您可以直接登录或尝试其他邮箱" +#~ msgstr "" + diff --git a/app/translations/zh/LC_MESSAGES/messages.po b/app/translations/zh/LC_MESSAGES/messages.po new file mode 100644 index 0000000..1798241 --- /dev/null +++ b/app/translations/zh/LC_MESSAGES/messages.po @@ -0,0 +1,1375 @@ +# Chinese translations for PROJECT. +# Copyright (C) 2024 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-11-04 02:29+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh\n" +"Language-Team: zh \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: app/apis/application.py:147 app/apis/invitation.py:200 +msgid "已接受" +msgstr "已接受" + +#: app/apis/application.py:154 app/apis/invitation.py:205 +msgid "已拒绝" +msgstr "已拒绝" + +#: app/apis/application.py:181 +msgid "只有申请人可以删除申请" +msgstr "只有申请人可以删除申请" + +#: app/apis/application.py:183 app/apis/file.py:254 app/apis/invitation.py:236 +#: app/apis/project_set.py:102 app/apis/target.py:31 app/apis/term.py:151 +#: app/apis/term.py:311 app/core/rbac.py:483 +msgid "删除成功" +msgstr "删除成功" + +#: app/apis/avatar.py:56 app/validators/avatar.py:23 +msgid "不支持的头像类型" +msgstr "不支持的头像类型" + +#: app/apis/avatar.py:58 app/validators/avatar.py:25 +msgid "缺少id" +msgstr "缺少id" + +#: app/apis/avatar.py:75 app/apis/invitation.py:166 app/apis/me.py:116 +#: app/apis/me.py:149 app/apis/project.py:93 app/apis/project_set.py:71 +#: app/apis/source.py:204 app/apis/team.py:187 app/apis/term.py:122 +#: app/apis/term.py:278 app/apis/user.py:188 app/apis/user.py:220 +msgid "修改成功" +msgstr "修改成功" + +#: app/apis/file.py:57 app/apis/project.py:53 +msgid "您没有此项目的访问权限" +msgstr "您没有此项目的访问权限" + +#: app/apis/file.py:121 +msgid "您没有此项目的上传文件权限" +msgstr "您没有此项目的上传文件权限" + +#: app/apis/file.py:159 app/apis/file.py:220 +msgid "您没有权限移动文件" +msgstr "您没有权限移动文件" + +#: app/apis/file.py:225 +msgid "移动成功" +msgstr "移动成功" + +#: app/apis/file.py:228 +msgid "您没有权限修改文件名" +msgstr "您没有权限修改文件名" + +#: app/apis/file.py:230 +msgid "改名成功" +msgstr "改名成功" + +#: app/apis/file.py:232 +msgid "需要提供parent_id或name参数" +msgstr "需要提供parent_id或name参数" + +#: app/apis/file.py:252 +msgid "您没有权限删除文件" +msgstr "您没有权限删除文件" + +#: app/apis/file.py:279 app/apis/project.py:366 +msgid "您没有此项目所在团队使用自动标记限额的权限" +msgstr "您没有此项目所在团队使用自动标记限额的权限" + +#: app/apis/file.py:282 app/apis/project.py:369 +msgid "源语言不支持自动标记" +msgstr "源语言不支持自动标记" + +#: app/apis/file.py:284 +msgid "文件不是图像文件" +msgstr "文件不是图像文件" + +#: app/apis/file.py:286 app/apis/project.py:378 +msgid "团队限额不足" +msgstr "团队限额不足" + +#: app/apis/file.py:288 app/constants/file.py:117 +msgid "已加入队列" +msgstr "已加入队列" + +#: app/apis/file.py:316 +msgid "处理成功" +msgstr "处理成功" + +#: app/apis/invitation.py:156 +msgid "只能修改邀请角色等级比您低的邀请" +msgstr "只能修改邀请角色等级比您低的邀请" + +#: app/apis/invitation.py:163 app/models/user.py:383 +msgid "邀请的角色等级需要比您低" +msgstr "邀请的角色等级需要比您低" + +#: app/apis/invitation.py:234 +msgid "只能删除邀请角色等级比您低的邀请" +msgstr "只能删除邀请角色等级比您低的邀请" + +#: app/apis/me.py:180 +msgid "修改成功,请重新登陆" +msgstr "修改成功,请重新登陆" + +#: app/apis/project.py:125 +msgid "完结项目成功" +msgstr "完结项目成功" + +#: app/apis/project.py:150 +msgid "操作无效" +msgstr "操作无效" + +#: app/apis/project.py:155 +msgid "恢复项目成功" +msgstr "恢复项目成功" + +#: app/apis/project.py:203 +msgid "添加目标语言成功" +msgstr "添加目标语言成功" + +#: app/apis/project.py:371 +msgid "自动标记进行中" +msgstr "自动标记进行中" + +#: app/apis/project.py:376 +msgid "未发现需要标记的图片" +msgstr "未发现需要标记的图片" + +#: app/apis/project.py:381 +msgid "已开始自动标记" +msgstr "已开始自动标记" + +#: app/apis/project.py:411 +msgid "销毁计划创建成功" +msgstr "销毁计划创建成功" + +#: app/apis/project.py:438 +msgid "销毁计划取消成功" +msgstr "销毁计划取消成功" + +#: app/apis/project.py:468 +msgid "完结计划创建成功" +msgstr "完结计划创建成功" + +#: app/apis/project.py:495 +msgid "完结计划取消成功" +msgstr "完结计划取消成功" + +#: app/apis/project_set.py:66 +msgid "默认项目集不能进行设置" +msgstr "默认项目集不能进行设置" + +#: app/apis/project_set.py:100 +msgid "默认项目集不能删除" +msgstr "默认项目集不能删除" + +#: app/apis/source.py:104 +msgid "只有图片文件能添加原文" +msgstr "只有图片文件能添加原文" + +#: app/apis/source.py:141 +msgid "只有图片文件能修改原文" +msgstr "只有图片文件能修改原文" + +#: app/apis/source.py:167 +msgid "只有图片文件能删除原文" +msgstr "只有图片文件能删除原文" + +#: app/apis/source.py:196 +msgid "只有图片文件能修改原文顺序" +msgstr "只有图片文件能修改原文顺序" + +#: app/apis/team.py:128 app/apis/team.py:326 app/apis/team.py:387 +#: app/apis/team.py:451 app/apis/term.py:84 app/apis/term.py:238 +msgid "创建成功" +msgstr "创建成功" + +#: app/apis/team.py:218 +msgid "此团队含有未完结的项目,不能解散(请先完结或者转移项目)" +msgstr "此团队含有未完结的项目,不能解散(请先完结或者转移项目)" + +#: app/apis/team.py:221 +msgid "解散成功" +msgstr "解散成功" + +#: app/apis/team.py:308 app/apis/team.py:352 +msgid "您没有权限在这个团队创建项目" +msgstr "您没有权限在这个团队创建项目" + +#: app/apis/team.py:338 +msgid "创建导出任务成功" +msgstr "创建导出任务成功" + +#: app/apis/team.py:447 +msgid "您没有权限在这个团队创建项目集" +msgstr "您没有权限在这个团队创建项目集" + +#: app/apis/team.py:478 app/apis/team.py:498 app/apis/team.py:529 +#: app/apis/team.py:556 +msgid "您没有权限查看本团队项目统计" +msgstr "您没有权限查看本团队项目统计" + +#: app/apis/term.py:73 +msgid "您没有「创建术语库」权限,不能在此团队创建术语库" +msgstr "您没有「创建术语库」权限,不能在此团队创建术语库" + +#: app/apis/term.py:113 +msgid "您没有「设置他人术语库」权限,只能设置自己创建的术语库" +msgstr "您没有「设置他人术语库」权限,只能设置自己创建的术语库" + +#: app/apis/term.py:148 +msgid "您没有「删除他人术语库」权限,只能删除自己创建的术语库" +msgstr "您没有「删除他人术语库」权限,只能删除自己创建的术语库" + +#: app/apis/term.py:180 +msgid "您没有「查看他人术语库」权限,只能查看自己创建的术语库中的术语" +msgstr "您没有「查看他人术语库」权限,只能查看自己创建的术语库中的术语" + +#: app/apis/term.py:225 +msgid "您没有「在他人术语库中增加术语」权限,只能在自己创建的术语库中增加术语" +msgstr "您没有「在他人术语库中增加术语」权限,只能在自己创建的术语库中增加术语" + +#: app/apis/term.py:271 +msgid "您没有「修改他人术语」权限,只能修改自己创建的术语或自己术语库中的术语" +msgstr "您没有「修改他人术语」权限,只能修改自己创建的术语或自己术语库中的术语" + +#: app/apis/term.py:306 +msgid "您没有「删除他人术语」权限,只能删除自己创建的术语或自己术语库中的术语" +msgstr "您没有「删除他人术语」权限,只能删除自己创建的术语或自己术语库中的术语" + +#: app/apis/user.py:124 app/apis/user.py:175 +msgid "注册成功" +msgstr "注册成功" + +#: app/constants/file.py:61 +msgid "解析未开始" +msgstr "解析未开始" + +#: app/constants/file.py:62 +msgid "解析排队中" +msgstr "解析排队中" + +#: app/constants/file.py:63 +msgid "解析中" +msgstr "解析中" + +#: app/constants/file.py:64 +msgid "解析失败" +msgstr "解析失败" + +#: app/constants/file.py:65 +msgid "解析成功" +msgstr "解析成功" + +#: app/constants/file.py:71 +msgid "自动标记未开始" +msgstr "自动标记未开始" + +#: app/constants/file.py:72 app/constants/output.py:17 +#: app/constants/project.py:31 +msgid "排队中" +msgstr "排队中" + +#: app/constants/file.py:73 +msgid "自动标记中" +msgstr "自动标记中" + +#: app/constants/file.py:74 +msgid "自动标记失败" +msgstr "自动标记失败" + +#: app/constants/file.py:75 app/constants/file.py:123 +msgid "自动标记完成" +msgstr "自动标记完成" + +#: app/constants/file.py:89 +msgid "其他错误" +msgstr "其他错误" + +#: app/constants/file.py:90 +msgid "未知字符集" +msgstr "未知字符集" + +#: app/constants/file.py:92 +msgid "文件无法读取,请确认文件完好或尝试重新上传" +msgstr "文件无法读取,请确认文件完好或尝试重新上传" + +#: app/constants/file.py:95 +msgid "图片读取失败,请稍后再试(1)" +msgstr "图片读取失败,请稍后再试(1)" + +#: app/constants/file.py:98 +msgid "图片读取失败,请稍后再试(2)" +msgstr "图片读取失败,请稍后再试(2)" + +#: app/constants/file.py:100 +msgid "图片超过 20MB 无法标记" +msgstr "图片超过 20MB 无法标记" + +#: app/constants/file.py:102 +msgid "自动标记服务离线,请稍后再试" +msgstr "自动标记服务离线,请稍后再试" + +#: app/constants/file.py:118 +msgid "重试中" +msgstr "重试中" + +#: app/constants/file.py:119 +msgid "翻找图片中" +msgstr "翻找图片中" + +#: app/constants/file.py:120 +msgid "整理数据中" +msgstr "整理数据中" + +#: app/constants/file.py:121 +msgid "图片识别中" +msgstr "图片识别中" + +#: app/constants/file.py:122 +msgid "标记中" +msgstr "标记中" + +#: app/constants/locale.py:15 +msgid "自动" +msgstr "自动" + +#: app/constants/locale.py:15 +msgid "遵循浏览器设置" +msgstr "遵循浏览器设置" + +#: app/constants/locale.py:16 +msgid "中文(简体)" +msgstr "中文(简体)" + +#: app/constants/locale.py:17 +msgid "中文(繁体)" +msgstr "中文(繁体)" + +#: app/constants/locale.py:18 +msgid "英文" +msgstr "英文" + +#: app/constants/output.py:18 +msgid "翻译整理中" +msgstr "翻译整理中" + +#: app/constants/output.py:19 +msgid "源文件整理中" +msgstr "源文件整理中" + +#: app/constants/output.py:20 +msgid "压缩中" +msgstr "压缩中" + +#: app/constants/output.py:21 +msgid "已完成" +msgstr "已完成" + +#: app/constants/output.py:22 +msgid "导出错误,请重试" +msgstr "导出错误,请重试" + +#: app/constants/project.py:16 app/constants/project.py:32 +msgid "进行中" +msgstr "进行中" + +#: app/constants/project.py:17 +msgid "已完结" +msgstr "已完结" + +#: app/constants/project.py:18 +msgid "等待完结" +msgstr "等待完结" + +#: app/constants/project.py:19 +msgid "等待销毁" +msgstr "等待销毁" + +#: app/constants/project.py:20 +msgid "已删除" +msgstr "已删除" + +#: app/constants/project.py:33 +msgid "成功" +msgstr "成功" + +#: app/constants/project.py:34 +msgid "错误" +msgstr "错误" + +#: app/constants/project.py:46 +msgid "从 Labelplus 文本导入中断,请重试,如仍出现同样错误,请联系开发团队" +msgstr "从 Labelplus 文本导入中断,请重试,如仍出现同样错误,请联系开发团队" + +#: app/constants/project.py:51 +msgid "从 Labelplus 文本导入时,没有有效的翻译目标语言" +msgstr "从 Labelplus 文本导入时,没有有效的翻译目标语言" + +#: app/constants/project.py:53 +msgid "从 Labelplus 文本导入时,项目没有创建人" +msgstr "从 Labelplus 文本导入时,项目没有创建人" + +#: app/constants/project.py:55 +msgid "Labelplus 文本解析失败,请联系开发团队" +msgstr "Labelplus 文本解析失败,请联系开发团队" + +#: app/core/rbac.py:49 +msgid "关闭申请加入" +msgstr "关闭申请加入" + +#: app/core/rbac.py:50 +msgid "只能通过邀请新增成员" +msgstr "只能通过邀请新增成员" + +#: app/core/rbac.py:53 +msgid "所有人" +msgstr "所有人" + +#: app/core/rbac.py:54 +msgid "所以用户都可以申请加入" +msgstr "所以用户都可以申请加入" + +#: app/core/rbac.py:69 +msgid "无需审核" +msgstr "无需审核" + +#: app/core/rbac.py:70 +msgid "用户申请后直接加入" +msgstr "用户申请后直接加入" + +#: app/core/rbac.py:73 +msgid "管理员审核" +msgstr "管理员审核" + +#: app/core/rbac.py:74 +msgid "管理员同意申请后加入" +msgstr "管理员同意申请后加入" + +#: app/core/rbac.py:99 +msgid "访问" +msgstr "访问" + +#: app/core/rbac.py:100 +msgid "解散" +msgstr "解散" + +#: app/core/rbac.py:101 +msgid "修改设置" +msgstr "修改设置" + +#: app/core/rbac.py:102 +msgid "新建角色" +msgstr "新建角色" + +#: app/core/rbac.py:103 +msgid "删除角色" +msgstr "删除角色" + +#: app/core/rbac.py:104 +msgid "审核用户加入申请" +msgstr "审核用户加入申请" + +#: app/core/rbac.py:106 +msgid "邀请用户" +msgstr "邀请用户" + +#: app/core/rbac.py:107 +msgid "邀请时仅可设置比自己角色等级低的用户" +msgstr "邀请时仅可设置比自己角色等级低的用户" + +#: app/core/rbac.py:110 +msgid "删除用户" +msgstr "删除用户" + +#: app/core/rbac.py:111 +msgid "仅可删除比自己角色等级低的用户" +msgstr "仅可删除比自己角色等级低的用户" + +#: app/core/rbac.py:114 +msgid "修改用户角色" +msgstr "修改用户角色" + +#: app/core/rbac.py:115 +msgid "仅可修改比自己角色等级低的角色" +msgstr "仅可修改比自己角色等级低的角色" + +#: app/core/rbac.py:117 +msgid "修改用户备注" +msgstr "修改用户备注" + +#: app/core/rbac.py:284 app/core/rbac.py:322 +msgid "角色等级不能大于等于自己的角色等级" +msgstr "角色等级不能大于等于自己的角色等级" + +#: app/core/rbac.py:286 app/core/rbac.py:324 +msgid "角色权限不能多于自己的角色权限" +msgstr "角色权限不能多于自己的角色权限" + +#: app/core/rbac.py:317 +msgid "不能修改系统角色" +msgstr "不能修改系统角色" + +#: app/core/rbac.py:339 +msgid "不能删除系统角色" +msgstr "不能删除系统角色" + +#: app/core/rbac.py:388 app/core/rbac.py:389 +msgid "此项目/团队不允许申请加入" +msgstr "此项目/团队不允许申请加入" + +#: app/core/rbac.py:464 +msgid "用户不存在于团队" +msgstr "用户不存在于团队" + +#: app/core/rbac.py:474 +msgid "退出成功" +msgstr "退出成功" + +#: app/core/rbac.py:478 +msgid "您没有删除用户权限" +msgstr "您没有删除用户权限" + +#: app/core/rbac.py:481 +msgid "只能删除角色等级比您低的用户" +msgstr "只能删除角色等级比您低的用户" + +#: app/core/rbac.py:501 +msgid "您不能修改自己的角色" +msgstr "您不能修改自己的角色" + +#: app/core/rbac.py:504 +msgid "您没有修改用户角色权限" +msgstr "您没有修改用户角色权限" + +#: app/core/rbac.py:508 +msgid "只能为比您角色等级低的用户设置角色" +msgstr "只能为比您角色等级低的用户设置角色" + +#: app/core/rbac.py:511 +msgid "只能为用户设置比您角色等级低的角色" +msgstr "只能为用户设置比您角色等级低的角色" + +#: app/core/rbac.py:539 +msgid "默认角色不能设置为创建者" +msgstr "默认角色不能设置为创建者" + +#: app/decorators/file.py:48 +msgid "不能对文件夹执行 " +msgstr "不能对文件夹执行 " + +#: app/decorators/file.py:60 +msgid "不能对文件执行 " +msgstr "不能对文件执行 " + +#: app/exceptions/auth.py:13 +msgid "验证异常" +msgstr "验证异常" + +#: app/exceptions/auth.py:23 app/validators/custom_validate.py:103 +msgid "此邮箱未注册" +msgstr "此邮箱未注册" + +#: app/exceptions/auth.py:33 app/validators/v_code.py:29 +msgid "密码错误" +msgstr "密码错误" + +#: app/exceptions/auth.py:44 +msgid "需要令牌" +msgstr "需要令牌" + +#: app/exceptions/auth.py:55 +msgid "无效的令牌" +msgstr "无效的令牌" + +#: app/exceptions/auth.py:65 app/models/user.py:201 app/validators/v_code.py:27 +msgid "用户不存在" +msgstr "用户不存在" + +#: app/exceptions/auth.py:75 +msgid "此用户被封禁" +msgstr "此用户被封禁" + +#: app/exceptions/auth.py:85 app/validators/custom_message.py:4 +msgid "邮箱格式不正确" +msgstr "邮箱格式不正确" + +#: app/exceptions/auth.py:95 +msgid "此邮箱已被注册" +msgstr "此邮箱已被注册" + +#: app/exceptions/auth.py:105 app/exceptions/team.py:38 +msgid "仅可使用中文/日文/韩文/英文/数字/_" +msgstr "仅可使用中文/日文/韩文/英文/数字/_" + +#: app/exceptions/auth.py:115 +msgid "此昵称已被使用" +msgstr "此昵称已被使用" + +#: app/exceptions/auth.py:125 app/exceptions/team.py:58 +msgid "长度为2到18个字符" +msgstr "长度为2到18个字符" + +#: app/exceptions/auth.py:135 +msgid "邮箱不在白名单中,请联系管理员添加" +msgstr "邮箱不在白名单中,请联系管理员添加" + +#: app/exceptions/base.py:26 +msgid "未定义的错误" +msgstr "未定义的错误" + +#: app/exceptions/base.py:36 +msgid "错误的ObjectId" +msgstr "错误的ObjectId" + +#: app/exceptions/base.py:46 +msgid "角色不存在" +msgstr "角色不存在" + +#: app/exceptions/base.py:56 +msgid "没有权限" +msgstr "没有权限" + +#: app/exceptions/base.py:95 +msgid "逗号分割的数字字符串包含非数字" +msgstr "逗号分割的数字字符串包含非数字" + +#: app/exceptions/base.py:105 +msgid "权限不存在" +msgstr "权限不存在" + +#: app/exceptions/base.py:115 +msgid "文件名错误" +msgstr "文件名错误" + +#: app/exceptions/base.py:125 +msgid "文件类型不支持此操作" +msgstr "文件类型不支持此操作" + +#: app/exceptions/base.py:135 +msgid "上传未包含文件" +msgstr "上传未包含文件" + +#: app/exceptions/base.py:145 +msgid "请求参数不能为空或没有需要的值" +msgstr "请求参数不能为空或没有需要的值" + +#: app/exceptions/base.py:155 +msgid "请求参数错误" +msgstr "请求参数错误" + +#: app/exceptions/file.py:13 +msgid "文件异常" +msgstr "文件异常" + +#: app/exceptions/file.py:23 +msgid "文件/文件夹不存在,可能已被删除" +msgstr "文件/文件夹不存在,可能已被删除" + +#: app/exceptions/file.py:33 +msgid "文件夹不存在,可能已被删除" +msgstr "文件夹不存在,可能已被删除" + +#: app/exceptions/file.py:43 +msgid "后缀名必须属于原文件的文件类型" +msgstr "后缀名必须属于原文件的文件类型" + +#: app/exceptions/file.py:53 +msgid "源文件不存在" +msgstr "源文件不存在" + +#: app/exceptions/file.py:60 +msgid "未知" +msgstr "未知" + +#: app/exceptions/file.py:62 +msgid "待上传" +msgstr "待上传" + +#: app/exceptions/file.py:64 +msgid "用户操作完结时清除" +msgstr "用户操作完结时清除" + +#: app/exceptions/file.py:66 +msgid "含有敏感信息已删除" +msgstr "含有敏感信息已删除" + +#: app/exceptions/file.py:78 +msgid "原文不存在,已被删除" +msgstr "原文不存在,已被删除" + +#: app/exceptions/file.py:88 +msgid "正在移动顺序,请稍后尝试" +msgstr "正在移动顺序,请稍后尝试" + +#: app/exceptions/file.py:98 +msgid "翻译不唯一" +msgstr "翻译不唯一" + +#: app/exceptions/join_process.py:13 +msgid "加入流程异常" +msgstr "加入流程异常" + +#: app/exceptions/join_process.py:23 +msgid "不支持此团体类型" +msgstr "不支持此团体类型" + +#: app/exceptions/join_process.py:35 +msgid "用户已经在 “{name}” 中" +msgstr "用户已经在 “{name}” 中" + +#: app/exceptions/join_process.py:45 +msgid "已邀请此用户,请等待用户确认" +msgstr "已邀请此用户,请等待用户确认" + +#: app/exceptions/join_process.py:55 +msgid "您已经申请,请等待管理员确认" +msgstr "您已经申请,请等待管理员确认" + +#: app/exceptions/join_process.py:65 app/exceptions/join_process.py:75 +msgid "邀请不存在" +msgstr "邀请不存在" + +#: app/exceptions/join_process.py:85 +msgid "允许申请类型不存在" +msgstr "允许申请类型不存在" + +#: app/exceptions/join_process.py:95 +msgid "申请审核类型不存在" +msgstr "申请审核类型不存在" + +#: app/exceptions/join_process.py:105 +msgid "无法修改此邀请" +msgstr "无法修改此邀请" + +#: app/exceptions/join_process.py:115 +msgid "无法修改此申请" +msgstr "无法修改此申请" + +#: app/exceptions/join_process.py:125 +msgid "已满员,不可申请加入或邀请新成员" +msgstr "已满员,不可申请加入或邀请新成员" + +#: app/exceptions/join_process.py:135 +msgid "创建者无法退出团体,请转移创建者权限后操作" +msgstr "创建者无法退出团体,请转移创建者权限后操作" + +#: app/exceptions/join_process.py:145 +msgid "此团体不向公众开放" +msgstr "此团体不向公众开放" + +#: app/exceptions/language.py:13 +msgid "语言异常" +msgstr "语言异常" + +#: app/exceptions/language.py:23 +msgid "语言不存在" +msgstr "语言不存在" + +#: app/exceptions/language.py:33 +msgid "原语言和目标语言不能相同" +msgstr "原语言和目标语言不能相同" + +#: app/exceptions/language.py:43 +msgid "需要设置目标语言" +msgstr "需要设置目标语言" + +#: app/exceptions/language.py:53 +msgid "已有此目标语言" +msgstr "已有此目标语言" + +#: app/exceptions/message.py:13 +msgid "站内信异常" +msgstr "站内信异常" + +#: app/exceptions/message.py:23 +msgid "站内信类型错误" +msgstr "站内信类型错误" + +#: app/exceptions/output.py:13 +msgid "导出异常" +msgstr "导出异常" + +#: app/exceptions/output.py:23 +msgid "导出导出文件不存在" +msgstr "导出导出文件不存在" + +#: app/exceptions/output.py:33 +msgid "导出过于频繁,请稍后再试" +msgstr "导出过于频繁,请稍后再试" + +#: app/exceptions/project.py:13 +msgid "项目异常" +msgstr "项目异常" + +#: app/exceptions/project.py:23 +msgid "项目不存在" +msgstr "项目不存在" + +#: app/exceptions/project.py:33 app/exceptions/project.py:267 +msgid "项目已完结" +msgstr "项目已完结" + +#: app/exceptions/project.py:43 +msgid "图片正在自动标记中,请稍后" +msgstr "图片正在自动标记中,请稍后" + +#: app/exceptions/project.py:53 +msgid "图片已自动标记完成,不能再次自动标记" +msgstr "图片已自动标记完成,不能再次自动标记" + +#: app/exceptions/project.py:63 +msgid "文件不存在" +msgstr "文件不存在" + +#: app/exceptions/project.py:73 +msgid "文件名重复" +msgstr "文件名重复" + +#: app/exceptions/project.py:83 +msgid "文本正在解析,请稍后" +msgstr "文本正在解析,请稍后" + +#: app/exceptions/project.py:93 +msgid "文本已解析成功" +msgstr "文本已解析成功" + +#: app/exceptions/project.py:103 +msgid "翻译不能为空" +msgstr "翻译不能为空" + +#: app/exceptions/project.py:113 +msgid "目标必须是文件夹" +msgstr "目标必须是文件夹" + +#: app/exceptions/project.py:123 +msgid "目标文件夹不能是自己本身" +msgstr "目标文件夹不能是自己本身" + +#: app/exceptions/project.py:133 +msgid "目标文件夹与原父级文件夹相同" +msgstr "目标文件夹与原父级文件夹相同" + +#: app/exceptions/project.py:143 +msgid "不能移动到自己的子文件夹" +msgstr "不能移动到自己的子文件夹" + +#: app/exceptions/project.py:153 +msgid "不能操作非激活修订版" +msgstr "不能操作非激活修订版" + +#: app/exceptions/project.py:163 +msgid "此修订版已激活" +msgstr "此修订版已激活" + +#: app/exceptions/project.py:173 +msgid "文件夹不能进行修订版操作" +msgstr "文件夹不能进行修订版操作" + +#: app/exceptions/project.py:183 +msgid "原文已有翻译" +msgstr "原文已有翻译" + +#: app/exceptions/project.py:196 +msgid "备注不能为空" +msgstr "备注不能为空" + +#: app/exceptions/project.py:206 +msgid "项目集不存在" +msgstr "项目集不存在" + +#: app/exceptions/project.py:216 +msgid "项目还没有正式完结" +msgstr "项目还没有正式完结" + +#: app/exceptions/project.py:226 +msgid "项目目标语言不存在" +msgstr "项目目标语言不存在" + +#: app/exceptions/project.py:236 +msgid "项目已有销毁计划" +msgstr "项目已有销毁计划" + +#: app/exceptions/project.py:246 +msgid "项目已有完结计划" +msgstr "项目已有完结计划" + +#: app/exceptions/project.py:256 +msgid "项目没有销毁计划" +msgstr "项目没有销毁计划" + +#: app/exceptions/project.py:277 +msgid "\"翻译数据.txt\" 解析失败" +msgstr "\"翻译数据.txt\" 解析失败" + +#: app/exceptions/team.py:13 +msgid "团队异常" +msgstr "团队异常" + +#: app/exceptions/team.py:23 +msgid "团队不存在" +msgstr "团队不存在" + +#: app/exceptions/team.py:48 +msgid "此团队名已被使用" +msgstr "此团队名已被使用" + +#: app/exceptions/team.py:68 +msgid "仅站点管理员可创建团队" +msgstr "仅站点管理员可创建团队" + +#: app/exceptions/term.py:13 +msgid "术语库异常" +msgstr "术语库异常" + +#: app/exceptions/term.py:23 +msgid "术语库不存在" +msgstr "术语库不存在" + +#: app/exceptions/term.py:33 +msgid "术语不存在" +msgstr "术语不存在" + +#: app/exceptions/v_code.py:13 +msgid "验证码异常" +msgstr "验证码异常" + +#: app/exceptions/v_code.py:23 +msgid "验证码过期,请重新输入" +msgstr "验证码过期,请重新输入" + +#: app/exceptions/v_code.py:33 +msgid "验证码错误" +msgstr "验证码错误" + +#: app/exceptions/v_code.py:43 +msgid "验证码失效,请重新获取" +msgstr "验证码失效,请重新获取" + +#: app/exceptions/v_code.py:57 +msgid "请等候{seconds}秒后再试" +msgstr "请等候{seconds}秒后再试" + +#: app/models/application.py:87 +msgid "已被他人拒绝" +msgstr "已被他人拒绝" + +#: app/models/application.py:89 +msgid "已被他人允许" +msgstr "已被他人允许" + +#: app/models/file.py:101 +msgid "文件名为空" +msgstr "文件名为空" + +#: app/models/file.py:104 +msgid "文件名过长" +msgstr "文件名过长" + +#: app/models/file.py:107 +msgid "文件名不能是 . 或 .." +msgstr "文件名不能是 . 或 .." + +#: app/models/file.py:110 +msgid "文件名不能以 . 结尾" +msgstr "文件名不能以 . 结尾" + +#: app/models/file.py:113 +msgid "文件名前缀为空" +msgstr "文件名前缀为空" + +#: app/models/file.py:129 +msgid "文件名不能包含下列任何字符: \\ / : * ? \" < > |" +msgstr "文件名不能包含下列任何字符: \\ / : * ? \" < > |" + +#: app/models/file.py:1304 +msgid "只有图片文件能移动原文顺序" +msgstr "只有图片文件能移动原文顺序" + +#: app/models/invitation.py:63 +msgid "已被拒绝" +msgstr "已被拒绝" + +#: app/models/invitation.py:65 +msgid "已被同意" +msgstr "已被同意" + +#: app/models/message.py:41 +msgid "非系统消息类型" +msgstr "非系统消息类型" + +#: app/models/project.py:87 +msgid "仅团队成员" +msgstr "仅团队成员" + +#: app/models/project.py:114 +msgid "访问项目" +msgstr "访问项目" + +#: app/models/project.py:115 +msgid "删除项目" +msgstr "删除项目" + +#: app/models/project.py:116 +msgid "设置项目" +msgstr "设置项目" + +#: app/models/project.py:118 +msgid "完结项目" +msgstr "完结项目" + +#: app/models/project.py:119 +msgid "上传图片" +msgstr "上传图片" + +#: app/models/project.py:120 +msgid "移动文件" +msgstr "移动文件" + +#: app/models/project.py:121 +msgid "修改图片名称" +msgstr "修改图片名称" + +#: app/models/project.py:122 +msgid "删除图片" +msgstr "删除图片" + +#: app/models/project.py:123 +msgid "导出翻译" +msgstr "导出翻译" + +#: app/models/project.py:124 +msgid "新建图片标记" +msgstr "新建图片标记" + +#: app/models/project.py:125 +msgid "移动图片标记" +msgstr "移动图片标记" + +#: app/models/project.py:126 +msgid "删除图片标记" +msgstr "删除图片标记" + +#: app/models/project.py:127 +msgid "新增翻译" +msgstr "新增翻译" + +#: app/models/project.py:128 +msgid "删除他人翻译" +msgstr "删除他人翻译" + +#: app/models/project.py:129 +msgid "校对翻译" +msgstr "校对翻译" + +#: app/models/project.py:131 +msgid "选定翻译" +msgstr "选定翻译" + +#: app/models/project.py:132 +msgid "将某条翻译指定导出项" +msgstr "将某条翻译指定导出项" + +#: app/models/project.py:134 +msgid "新增目标语言" +msgstr "新增目标语言" + +#: app/models/project.py:135 +msgid "修改目标语言" +msgstr "修改目标语言" + +#: app/models/project.py:136 +msgid "删除目标语言" +msgstr "删除目标语言" + +#: app/models/project.py:145 app/models/team.py:135 +msgid "创建人" +msgstr "创建人" + +#: app/models/project.py:176 app/models/team.py:167 +msgid "管理员" +msgstr "管理员" + +#: app/models/project.py:207 +msgid "监理" +msgstr "监理" + +#: app/models/project.py:232 +msgid "校对" +msgstr "校对" + +#: app/models/project.py:247 +msgid "翻译" +msgstr "翻译" + +#: app/models/project.py:260 +msgid "嵌字" +msgstr "嵌字" + +#: app/models/project.py:272 +msgid "见习翻译" +msgstr "见习翻译" + +#: app/models/project.py:425 +msgid "必须是Language对象" +msgstr "必须是Language对象" + +#: app/models/project.py:560 +msgid "只允许项目所属团队成员申请加入" +msgstr "只允许项目所属团队成员申请加入" + +#: app/models/project.py:627 +msgid "暂不支持的文件格式" +msgstr "暂不支持的文件格式" + +#: app/models/project.py:948 +msgid "框内" +msgstr "框内" + +#: app/models/project.py:950 +msgid "框外" +msgstr "框外" + +#: app/models/project.py:953 +msgid "可使用 LabelPlus Photoshop 脚本导入 psd 中" +msgstr "可使用 LabelPlus Photoshop 脚本导入 psd 中" + +#: app/models/team.py:59 +msgid "访问团队" +msgstr "访问团队" + +#: app/models/team.py:60 +msgid "解散团队" +msgstr "解散团队" + +#: app/models/team.py:61 +msgid "设置团队" +msgstr "设置团队" + +#: app/models/team.py:64 +msgid "管理项目" +msgstr "管理项目" + +#: app/models/team.py:65 +msgid "自动获得团队内所有项目的管理员权限" +msgstr "自动获得团队内所有项目的管理员权限" + +#: app/models/team.py:68 +msgid "创建术语库" +msgstr "创建术语库" + +#: app/models/team.py:69 +msgid "在团队内创建术语库" +msgstr "在团队内创建术语库" + +#: app/models/team.py:72 +msgid "查看他人术语库" +msgstr "查看他人术语库" + +#: app/models/team.py:73 +msgid "若无此权限只能查看自己创建的术语库中的术语" +msgstr "若无此权限只能查看自己创建的术语库中的术语" + +#: app/models/team.py:76 +msgid "设置他人术语库" +msgstr "设置他人术语库" + +#: app/models/team.py:77 +msgid "若无此权限只能修改自己创建的术语库设置" +msgstr "若无此权限只能修改自己创建的术语库设置" + +#: app/models/team.py:80 +msgid "删除他人术语库" +msgstr "删除他人术语库" + +#: app/models/team.py:81 +msgid "若无此权限只能删除自己创建的术语库" +msgstr "若无此权限只能删除自己创建的术语库" + +#: app/models/team.py:84 +msgid "在他人术语库中增加术语" +msgstr "在他人术语库中增加术语" + +#: app/models/team.py:85 +msgid "若无此权限只能在自己创建的术语库中增加术语" +msgstr "若无此权限只能在自己创建的术语库中增加术语" + +#: app/models/team.py:88 +msgid "修改他人术语" +msgstr "修改他人术语" + +#: app/models/team.py:89 +msgid "若无此权限只能修改自己创建的术语或自己术语库中的术语" +msgstr "若无此权限只能修改自己创建的术语或自己术语库中的术语" + +#: app/models/team.py:94 +msgid "删除他人术语" +msgstr "删除他人术语" + +#: app/models/team.py:95 +msgid "若无此权限只能删除自己创建的术语或自己术语库中的术语" +msgstr "若无此权限只能删除自己创建的术语或自己术语库中的术语" + +#: app/models/team.py:100 +msgid "创建项目" +msgstr "创建项目" + +#: app/models/team.py:101 +msgid "在团队内创建项目" +msgstr "在团队内创建项目" + +#: app/models/team.py:104 +msgid "创建项目集" +msgstr "创建项目集" + +#: app/models/team.py:105 +msgid "在团队内创建项目集" +msgstr "在团队内创建项目集" + +#: app/models/team.py:108 +msgid "设置项目集" +msgstr "设置项目集" + +#: app/models/team.py:109 +msgid "修改团队内项目集的设置" +msgstr "修改团队内项目集的设置" + +#: app/models/team.py:112 +msgid "删除项目集" +msgstr "删除项目集" + +#: app/models/team.py:113 +msgid "删除团队内的项目集" +msgstr "删除团队内的项目集" + +#: app/models/team.py:116 +msgid "使用自动标记额度" +msgstr "使用自动标记额度" + +#: app/models/team.py:117 +msgid "可以使用团队的自动标记额度" +msgstr "可以使用团队的自动标记额度" + +#: app/models/team.py:120 +msgid "使用机器翻译额度" +msgstr "使用机器翻译额度" + +#: app/models/team.py:121 +msgid "可以使用团队的机器翻译额度" +msgstr "可以使用团队的机器翻译额度" + +#: app/models/team.py:124 +msgid "查看项目统计" +msgstr "查看项目统计" + +#: app/models/team.py:125 +msgid "可以查看团队项目统计" +msgstr "可以查看团队项目统计" + +#: app/models/team.py:198 +msgid "资深成员" +msgstr "资深成员" + +#: app/models/team.py:219 +msgid "成员" +msgstr "成员" + +#: app/models/team.py:238 +msgid "见习成员" +msgstr "见习成员" + +#: app/models/user.py:189 +msgid "令牌格式错误,应形如 Bearer x.x.x" +msgstr "令牌格式错误,应形如 Bearer x.x.x" + +#: app/models/user.py:204 +msgid "密码已修改,请重新登录" +msgstr "密码已修改,请重新登录" + +#: app/models/user.py:399 +msgid "此用户已有申请,已直接加入" +msgstr "此用户已有申请,已直接加入" + +#: app/models/user.py:406 +msgid "此用户是项目所在团队成员,已直接加入" +msgstr "此用户是项目所在团队成员,已直接加入" + +#: app/models/user.py:412 +msgid "邀请成功,请等待用户确认" +msgstr "邀请成功,请等待用户确认" + +#: app/models/user.py:443 +msgid "加入成功,管理员继承自团队" +msgstr "加入成功,管理员继承自团队" + +#: app/models/user.py:456 +msgid "管理员先前邀请了您,已直接加入" +msgstr "管理员先前邀请了您,已直接加入" + +#: app/models/user.py:463 +msgid "项目不允许申请加入" +msgstr "项目不允许申请加入" + +#: app/models/user.py:469 +msgid "加入成功" +msgstr "加入成功" + +#: app/models/user.py:472 +msgid "申请成功,请等待管理员审核" +msgstr "申请成功,请等待管理员审核" + +#: app/models/v_code.py:304 +msgid "重置您的安全邮箱" +msgstr "重置您的安全邮箱" + +#: app/models/v_code.py:305 +msgid "确认您的安全邮箱" +msgstr "确认您的安全邮箱" + +#: app/models/v_code.py:306 +msgid "重置您的密码" +msgstr "重置您的密码" + +#: app/validators/custom_message.py:3 +msgid "必填" +msgstr "必填" + +#: app/validators/custom_validate.py:21 +msgid "不可为空" +msgstr "不可为空" + +#: app/validators/custom_validate.py:35 +msgid "此项不可选" +msgstr "此项不可选" + +#: app/validators/custom_validate.py:76 app/validators/custom_validate.py:79 +#: app/validators/custom_validate.py:109 app/validators/custom_validate.py:124 +#: app/validators/custom_validate.py:127 app/validators/custom_validate.py:134 +#: app/validators/custom_validate.py:141 app/validators/custom_validate.py:148 +#: app/validators/custom_validate.py:151 app/validators/custom_validate.py:169 +#: app/validators/custom_validate.py:172 app/validators/custom_validate.py:178 +#: app/validators/custom_validate.py:181 app/validators/custom_validate.py:184 +msgid "长度为{min}到{max}个字符" +msgstr "长度为{min}到{max}个字符" + +#: app/validators/custom_validate.py:160 +msgid "等级需要大于{min},小于{max}(您的等级)" +msgstr "等级需要大于{min},小于{max}(您的等级)" + +#: app/validators/source.py:41 app/validators/translation.py:27 +msgid "没有有效参数" +msgstr "没有有效参数" + diff --git a/app/utils/mongo.py b/app/utils/mongo.py index 19b5fae..d76be3c 100644 --- a/app/utils/mongo.py +++ b/app/utils/mongo.py @@ -1,4 +1,9 @@ -def mongo_order(objects, order_by, default_order_by): +from typing import TypeVar, List + +T = TypeVar("T") + + +def mongo_order(objects: List[T], order_by, default_order_by) -> List[T]: """处理排序""" # 设置排序默认值 if order_by is None or order_by == []: @@ -12,7 +17,7 @@ def mongo_order(objects, order_by, default_order_by): return objects -def mongo_slice(objects, skip, limit): +def mongo_slice(objects: List[T], skip, limit) -> List[T]: """切片处理""" if skip: objects = objects.skip(skip) diff --git a/deps-top.txt b/deps-top.txt index 6d1551d..0a69d06 100644 --- a/deps-top.txt +++ b/deps-top.txt @@ -1,11 +1,7 @@ -# 本文件只包含顶层或需要特别指定版本的pip包版本 -# requirements.txt 含有 `pip install -r 本文件` 后的所有包版本 -# -# 更新deps的步骤: -# 1. 更新本文件 -# 2. venv/bin/pip install -r deps-top.txt --use-pep517 -# 3. venv/bin/pipdeptree --freeze > requirements.txt +# THIS FILE is for leaf packages or version overrides +# requirements.txt contains all package versions. It should be updated with `make requirements.txt` # +python-dotenv==1.0.1 Flask==2.2.5 # Jinja2==3.0.3 itsdangerous==2.0.1 # MUST NOT upgrade this, we still use TimedJSONWebSignatureSerializer @@ -19,16 +15,16 @@ pytest-dotenv==0.5.2 pytest-html==4.1.1 pytest-md==0.2.0 -flask-babel==1.0.0 # i18n +flask-babel==4.0.0 # i18n mongoengine==0.20.0 # Mongo数据库 mongomock==4.1.2 -Pillow==8.0.1 # 图片处理 +Pillow==8.4.0 marshmallow==3.0.0b20 # 字段验证 (flask-apikit需要) requests==2.22.0 # HTTP请求 oss2==2.7.0 # 阿里云OSS celery==5.3.6 # 任务调度 celery[redis]==5.3.6 -flower==0.9.5 # Celery监控 +flower==2.0.1 # web ui for celery redis==5.0.3 # Redis数据库 chardet==3.0.4 # 文件编码预测 aliyun-python-sdk-core-v3==2.13.3 # 用于签发阿里云pip diff --git a/docs/models.md b/docs/models.md index c259a2c..2dbf57a 100644 --- a/docs/models.md +++ b/docs/models.md @@ -4,80 +4,52 @@ Persistent models in MongoDB. ## Core -### User - - - -### Team - -`Team` - -### Projects -`Project` `ProjectSet` `Files` +### Global -### Translations - -`Source` `Target` - -## etc - -- `Application` +- `Language` - `VCode` +- `Captcha` +- `SiteSetting` - +- `Team` +- `TeamPermission` +- `TeamUserRelation` +- `TeamRole` +- `Application` +- `ApplicationStatus` +- `Invitation` +- `InvitationStatus` + +### Unused? Terms + +- `TermBank` +- `TermGroup` +- `Term` + +### Project level + +- `Project` + - `Target[]` + - `File[]` +- `ProjectSet` +- `ProjectRole` +- `ProjectUserRelation` +- `ProjectAllowApplyType` +- `ProjectPermission` +- `Output` + +### Inside project + +- `File` + - `FileTargetCache` +- `Source`: `(rank, x, y, content)` +- `Translation`: `(User, Source, Target) -> (content, proof_content)` +- `Tip`: `(User, Source, Target) -> (content)` diff --git a/manage.py b/manage.py index 011ed98..3ff8a6a 100644 --- a/manage.py +++ b/manage.py @@ -2,7 +2,6 @@ import re import click import logging - from app import flask_app from app.factory import init_db @@ -39,55 +38,11 @@ def migrate(): @click.command() -@click.option( - "--action", - prompt="""请选择下一步操作: -1. 生成 .pot 模板文件 -2. 为某个语言生成 .po 文件 -3. 重新生成 .pot 模板文件并更新 .po 文件 -4. 生产 .mo 文件 -""", - type=int, -) -def local(action): - cfg_path = "babel.cfg" - locale_folder = "app/locales" - pot_filename = "messages.pot" - pot_path = os.path.join(locale_folder, pot_filename) - if not os.path.isdir(locale_folder): - os.makedirs(locale_folder) - if action == 1: - os.system( - "pybabel extract -F {cfg_path} -k lazy_gettext -o {pot_path} .".format( # noqa: E501 - cfg_path=cfg_path, pot_path=pot_path - ) - ) - elif action == 2: - if not os.path.isfile(pot_path): - pass - lang_name = click.prompt("Please enter a language name") - os.system( - "pybabel init -i {pot_path} -d {locale_folder} -l {lang_name}".format( # noqa: E501 - pot_path=pot_path, - locale_folder=locale_folder, - lang_name=lang_name, - ) - ) - elif action == 3: - os.system( - "pybabel extract -F {cfg_path} -k lazy_gettext -o {pot_path} .".format( # noqa: E501 - cfg_path=cfg_path, pot_path=pot_path - ) - ) - os.system( - "pybabel update -i {pot_path} -d {locale_folder}".format( - pot_path=pot_path, locale_folder=locale_folder - ) - ) - elif action == 4: - os.system( - "pybabel compile -d {locale_folder}".format(locale_folder=locale_folder) - ) +def list_translations(): + from app.factory import babel + + with flask_app.app_context(): + print(babel.list_translations()) @click.command("mit_file") @@ -121,9 +76,9 @@ def mit_preprocess_dir(dir: str): print(" ", q.translated) -main.add_command(local) main.add_command(docs) main.add_command(migrate) +main.add_command(list_translations) main.add_command(mit_preprocess_file) main.add_command(mit_preprocess_dir) diff --git a/requirements.txt b/requirements.txt index 03cd15a..90117ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,25 +5,25 @@ Flask-APIKit==0.0.7 click==8.1.3 itsdangerous==2.0.1 Jinja2==3.1.4 - MarkupSafe==2.1.5 - Werkzeug==3.0.3 - MarkupSafe==2.1.5 + MarkupSafe==3.0.2 + Werkzeug==3.0.6 + MarkupSafe==3.0.2 marshmallow==3.0.0b20 -Flask-Babel==1.0.0 - Babel==2.15.0 +flask-babel==4.0.0 + babel==2.16.0 Flask==2.2.5 click==8.1.3 itsdangerous==2.0.1 Jinja2==3.1.4 - MarkupSafe==2.1.5 - Werkzeug==3.0.3 - MarkupSafe==2.1.5 + MarkupSafe==3.0.2 + Werkzeug==3.0.6 + MarkupSafe==3.0.2 Jinja2==3.1.4 - MarkupSafe==2.1.5 - pytz==2024.1 -flower==0.9.5 + MarkupSafe==3.0.2 + pytz==2024.2 +flower==2.0.1 celery==5.3.6 - billiard==4.2.0 + billiard==4.2.1 click==8.1.3 click-didyoumean==0.3.1 click==8.1.3 @@ -31,61 +31,62 @@ flower==0.9.5 click==8.1.3 click-repl==0.3.0 click==8.1.3 - prompt_toolkit==3.0.47 + prompt_toolkit==3.0.48 wcwidth==0.2.13 - kombu==5.3.7 + kombu==5.4.2 amqp==5.2.0 vine==5.1.0 + tzdata==2024.2 vine==5.1.0 python-dateutil==2.9.0.post0 six==1.16.0 - tzdata==2024.1 + tzdata==2024.2 vine==5.1.0 - humanize==4.9.0 - prometheus-client==0.8.0 - pytz==2024.1 + humanize==4.11.0 + prometheus_client==0.21.0 + pytz==2024.2 tornado==6.4.1 google-cloud-storage==1.33.0 google-auth==1.35.0 cachetools==4.2.4 - pyasn1_modules==0.4.0 - pyasn1==0.6.0 + pyasn1_modules==0.4.1 + pyasn1==0.6.1 rsa==4.9 - pyasn1==0.6.0 + pyasn1==0.6.1 setuptools==65.5.0 six==1.16.0 google-cloud-core==1.7.3 google-api-core==2.10.2 google-auth==1.35.0 cachetools==4.2.4 - pyasn1_modules==0.4.0 - pyasn1==0.6.0 + pyasn1_modules==0.4.1 + pyasn1==0.6.1 rsa==4.9 - pyasn1==0.6.0 + pyasn1==0.6.1 setuptools==65.5.0 six==1.16.0 - googleapis-common-protos==1.63.1 - protobuf==4.25.3 - protobuf==4.25.3 + googleapis-common-protos==1.65.0 + protobuf==4.25.5 + protobuf==4.25.5 requests==2.22.0 - certifi==2024.6.2 + certifi==2024.8.30 chardet==3.0.4 idna==2.8 urllib3==1.25.11 google-auth==1.35.0 cachetools==4.2.4 - pyasn1_modules==0.4.0 - pyasn1==0.6.0 + pyasn1_modules==0.4.1 + pyasn1==0.6.1 rsa==4.9 - pyasn1==0.6.0 + pyasn1==0.6.1 setuptools==65.5.0 six==1.16.0 six==1.16.0 google-resumable-media==1.3.3 - google-crc32c==1.5.0 + google-crc32c==1.6.0 six==1.16.0 requests==2.22.0 - certifi==2024.6.2 + certifi==2024.8.30 chardet==3.0.4 idna==2.8 urllib3==1.25.11 @@ -99,25 +100,25 @@ mongomock==4.1.2 oss2==2.7.0 aliyun-python-sdk-core-v3==2.13.3 jmespath==0.10.0 - pycryptodome==3.20.0 - aliyun-python-sdk-kms==2.16.3 - aliyun-python-sdk-core==2.15.1 - cryptography==42.0.8 - cffi==1.16.0 + pycryptodome==3.21.0 + aliyun-python-sdk-kms==2.16.5 + aliyun-python-sdk-core==2.16.0 + cryptography==43.0.3 + cffi==1.17.1 pycparser==2.22 jmespath==0.10.0 crcmod==1.7 - pycryptodome==3.20.0 + pycryptodome==3.21.0 requests==2.22.0 - certifi==2024.6.2 + certifi==2024.8.30 chardet==3.0.4 idna==2.8 urllib3==1.25.11 -Pillow==8.0.1 +Pillow==8.4.0 pip==24.0 pipdeptree==2.13.2 pytest-cov==5.0.0 - coverage==7.5.3 + coverage==7.6.4 pytest==8.2.1 iniconfig==2.0.0 packaging==24.1 @@ -130,7 +131,7 @@ pytest-dotenv==0.5.2 python-dotenv==1.0.1 pytest-html==4.1.1 Jinja2==3.1.4 - MarkupSafe==2.1.5 + MarkupSafe==3.0.2 pytest==8.2.1 iniconfig==2.0.0 packaging==24.1 @@ -152,4 +153,4 @@ pytest-xdist==3.6.1 packaging==24.1 pluggy==1.5.0 redis==5.0.3 -ruff==0.4.9 +ruff==0.7.1 diff --git a/tests/api/test_auth_api.py b/tests/api/test_auth_api.py index 51da622..0104898 100644 --- a/tests/api/test_auth_api.py +++ b/tests/api/test_auth_api.py @@ -1,5 +1,5 @@ from mongomock import ObjectId -from app import Locale +from app.constants.locale import Locale from app.exceptions import BadTokenError, NeedTokenError, UserBannedError from app.exceptions.base import NoPermissionError from app.models.site_setting import SiteSetting