Skip to content

Commit

Permalink
add en locale (#22)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jokester authored Nov 4, 2024
1 parent d765b17 commit cad8b67
Show file tree
Hide file tree
Showing 31 changed files with 3,195 additions and 490 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/check-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 5 additions & 1 deletion CONTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
### init venv for development

```
$ python3.10 -mvenv venv
$ make create-venv deps
$ venv/bin/pip install -r requirements.txt
```

Expand All @@ -19,3 +19,7 @@ $ venv/bin/ruff .
```


### run within docker



2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ COPY . /app
WORKDIR /app

EXPOSE 5000

RUN make babel-update-mo
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
37 changes: 12 additions & 25 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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)
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion app/apis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from flask import Blueprint, Flask

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# logger.setLevel(logging.DEBUG)

"""
@apiDefine TokenHeader
Expand Down
7 changes: 5 additions & 2 deletions app/apis/member.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import Union
from flask import request

from app.core.responses import MoePagination
from app.core.views import MoeAPIView
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

Expand Down Expand Up @@ -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/<group_type>/<group_id>/users/<user_id> 修改团体的成员
@apiVersion 1.0.0
Expand Down Expand Up @@ -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/<group_type>/<group_id>/users/<user_id> 删除团体的成员
@apiVersion 1.0.0
Expand Down
2 changes: 1 addition & 1 deletion app/apis/type.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
5 changes: 4 additions & 1 deletion app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
# 脱敏的生产环境配置(严禁记录密钥)
# 开发测试配置可放在 configs 文件夹下(已 gitignore)或项目外
# ===========
import dotenv
from os import environ as env
import urllib.parse as urlparse

dotenv.load_dotenv()

# -----------
# 基础设置
# -----------
Expand Down Expand Up @@ -33,7 +36,7 @@
# -----------
# i18n
# -----------
BABEL_DEFAULT_LOCALE = "zh_Hans_CN"
BABEL_DEFAULT_LOCALE = "zh"
BABEL_DEFAULT_TIMEZONE = "UTC"
# -----------
# 其他设置
Expand Down
4 changes: 3 additions & 1 deletion app/constants/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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的类型
Expand Down
14 changes: 9 additions & 5 deletions app/decorators/file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from functools import wraps
from typing import TYPE_CHECKING

from app.exceptions import (
FileNotActivatedError,
Expand All @@ -7,25 +8,28 @@
)
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)

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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion app/decorators/url.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from functools import wraps
from typing import Optional

from mongoengine.errors import ValidationError

Expand All @@ -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中获取相对应的模型对象
Expand Down
14 changes: 12 additions & 2 deletions app/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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) # 文件储存


Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit cad8b67

Please sign in to comment.