Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
feat(i18n): 🌐 add multilanguage (resolve #18)
Browse files Browse the repository at this point in the history
  • Loading branch information
AnzhiZhang committed Jul 2, 2022
1 parent 23bb19e commit 6a5ffd9
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
pip install -r requirements.txt
- name: Make
run: pyinstaller -i icon.ico --clean --noconsole --add-binary "icon.ico;." --onefile main.py
run: pyinstaller -i icon.ico --clean --noconsole --add-binary "icon.ico;." --add-data="lang;." --onefile main.py

- name: Upload Release Asset
uses: actions/upload-release-asset@v1
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"conventionalCommits.scopes": [
"ui",
"download",
"config"
"config",
"i18n"
]
}
30 changes: 30 additions & 0 deletions lang/en-us.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
langName: English
license.title: License Notice
license.content: |
Copyright © 2022 Andy Zhang and contributors
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
window.search.search: Search
window.show.resultWarning.title: Warning
window.show.resultWarning.content: Got 0 results!
window.filters.sort: 'Sort: '
window.filters.gameVersion: 'Game Version: '
window.filters.modpackVersion: 'Modpack Version: '
window.buttons.import: Import
window.buttons.download: Download
window.buttons.exit: Exit
download.file.title: Downloading Configuration...
download.mods.title: Downloading Mods...
download.getURLFail.title: Warning
download.getURLFail.content: Fail to get mod download URLs, this may caused by a network issue, please try again.
download.finish.title: Finished
download.finish.content: |
Please import to launcher
Issues:
https://github.com/AnzhiZhang/CurseForgeModpackDownloader
download.downloadFail: '{0} mods download fail, please download them manually!'
download.getModWarning.title: Warning
download.getModWarning.content: |
{0}
Please get more info in log.
30 changes: 30 additions & 0 deletions lang/zh-cn.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
langName: 简体中文
license.title: 版权声明
license.content: |
Copyright © 2022 Andy Zhang and contributors
本程序是自由软件:你可以再分发之和/或依照由自由软件基金会发布的 GNU 通用公共许可证修改之,无论是版本 3 许可证,还是(按你的决定)任何以后版都可以。
发布该程序是希望它能有用,但是并无保障;甚至连可销售和符合某个特定的目的都不保证。请参看 GNU 通用公共许可证,了解详情。
你应该随程序获得一份 GNU 通用公共许可证的复本。如果没有,请看 https://www.gnu.org/licenses/。
window.search.search: 搜索
window.show.resultWarning.title: 警告
window.show.resultWarning.content: 共搜索到 0 个结果!
window.filters.sort: 排序:
window.filters.gameVersion: 游戏版本:
window.filters.modpackVersion: 整合包版本:
window.buttons.import: 导入
window.buttons.download: 下载
window.buttons.exit: 退出
download.file.title: 下载配置文件…………
download.mods.title: 正在下载模组…………
download.getURLFail.title: 警告
download.getURLFail.content: 获取模组下载地址失败,这可能是由于网络不稳定,请重试
download.finish.title: 下载完成
download.finish.content: |
请直接导入启动器
问题反馈:
https://github.com/AnzhiZhang/CurseForgeModpackDownloader
download.downloadFail: '{0} 个模组下载失败,请手动下载!'
download.getModWarning.title: 警告
download.getModWarning.content: |
{0}
请查看日志获取详细信息
9 changes: 3 additions & 6 deletions utils/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@

NAME = 'CurseForgeModpackDownloader'
NAME_WITH_SPACE = 'CurseForge Modpack Downloader (CMPDL)'
LICENSE = (
'Copyright © 2022 Andy Zhang and contributors\n'
'本程序是自由软件:你可以再分发之和/或依照由自由软件基金会发布的 GNU 通用公共许可证修改之,无论是版本 3 许可证,还是(按你的决定)任何以后版都可以。\n'
'发布该程序是希望它能有用,但是并无保障;甚至连可销售和符合某个特定的目的都不保证。请参看 GNU 通用公共许可证,了解详情。\n'
'你应该随程序获得一份 GNU 通用公共许可证的复本。如果没有,请看 https://www.gnu.org/licenses/。'
)

if getattr(sys, 'frozen', False):
BASE_DIR = getattr(sys, '_MEIPASS')
Expand All @@ -23,6 +17,8 @@ class PATH:
WORKING_DIR = os.getcwd()
DATA_DIR = os.path.join(WORKING_DIR, f'.{NAME}')

LANG_DIR_PATH = os.path.join(BASE_DIR, 'lang')

if platform.system() == 'Windows':
ICON_PATH = os.path.join(BASE_DIR, 'icon.ico')
else:
Expand All @@ -44,6 +40,7 @@ class WINDOW:
class CONFIG:
FILE_PATH = os.path.join(PATH.DATA_DIR, 'config.yml')
DEFAULT = {
'language': 'en-us',
'curseForgeAPIKey': ''
}

Expand Down
32 changes: 24 additions & 8 deletions utils/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ def main(self):

def run():
toplevel = Toplevel()
toplevel.title('正在下载模组…………')
toplevel.title(
self.factory.language.translate('download.mods.title')
)
toplevel.resizable(False, False)
toplevel.protocol("WM_DELETE_WINDOW", lambda: None)
toplevel.iconbitmap(PATH.ICON_PATH)
Expand Down Expand Up @@ -126,17 +128,22 @@ def clear():
except Exception as e:
self.logger.exception(f'Exception: {e}')
clear()
showwarning('警告', '获取模组下载地址失败,这可能是由于网络不稳定,请重试')
showwarning(
self.factory.language.translate(
'download.getURLFail.title'
),
self.factory.language.translate(
'download.getURLFail.content'
)
)
else:
self.download_mods(download_urls, update)
self.write_mmc_files()
self.make_zip()
clear()
showinfo(
'下载完成',
'请直接导入启动器\n'
'下载地址及问题反馈:\n'
'https://github.com/AnzhiZhang/CurseForgeModpackDownloader'
self.factory.language.translate('download.finish.title'),
self.factory.language.translate('download.finish.content')
)
self.factory.logger.info(
'Successfully downloaded %s',
Expand Down Expand Up @@ -238,7 +245,10 @@ def download(url):
# 下载失败日志
failed_download_count = len(failed_mods['download'])
if failed_download_count > 0:
warning_text += f'{failed_download_count} 个模组下载失败,请手动下载!\n'
warning_text += self.factory.language.translate(
'download.downloadFail',
failed_download_count
)
self.logger.error(
f'{failed_download_count} 个模组下载失败,请手动下载:\n' +
'\n'.join(failed_mods['download'])
Expand All @@ -254,7 +264,13 @@ def download(url):

# 弹窗提示
if warning_text != '':
showwarning('警告', warning_text + '请查看日志获取详细信息')
showwarning(
self.factory.language.translate('download.getModWarning.title'),
self.factory.language.translate(
'download.getModWarning.content',
warning_text
),
)

def write_mmc_files(self):
# 获取版本信息
Expand Down
6 changes: 6 additions & 0 deletions utils/factory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from utils.logger import Logger
from utils.config import Config
from utils.language import Language
from utils.requester import Requester
from utils.constant import NAME, PATH

Expand All @@ -8,6 +9,7 @@ class Factory:
def __init__(self):
self.__logger = Logger(NAME, PATH.LOG_FILE_PATH)
self.__config = Config()
self.__language = Language(self)
self.__requester = Requester(self)

@property
Expand All @@ -18,6 +20,10 @@ def logger(self):
def config(self):
return self.__config

@property
def language(self):
return self.__language

@property
def requester(self):
return self.__requester
39 changes: 39 additions & 0 deletions utils/language.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import os

import yaml

from typing import TYPE_CHECKING, Dict, List

if TYPE_CHECKING:
from utils.factory import Factory

from utils.constant import PATH


class Language:
def __init__(self, factory: 'Factory'):
self.__data: Dict[str, Dict[str, str]] = {}
self.__lang = factory.config['language']
self.__load()

def __load(self):
for i in os.listdir(PATH.LANG_DIR_PATH):
path = os.path.join(PATH.LANG_DIR_PATH, i)
with open(path, encoding='utf-8') as f:
self.__data[os.path.splitext(i)[0]] = yaml.safe_load(f)

def translate(self, key: str, *args) -> str:
"""
Translate words from key.
:param key: Key of words.
:param lang: Language name.
:return: Words.
"""
return self.__data.get(self.__lang).get(key).format(*args)

def get_languages_list(self) -> List[str]:
"""
Get languages list.
:return: Language name list.
"""
return list(self.__data.keys())
18 changes: 14 additions & 4 deletions utils/window/frames/buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ def __init__(self, master: 'Main'):

self.__import_button = Button(
self,
text='导入',
text=self.__main_window.factory.language.translate(
'window.buttons.import'
),
background='white',
command=lambda:
Download(
Expand All @@ -30,13 +32,17 @@ def __init__(self, master: 'Main'):
)
self.__download_button = Button(
self,
text='下载',
text=self.__main_window.factory.language.translate(
'window.buttons.download'
),
background='white',
command=self.download
)
self.__exit_button = Button(
self,
text='退出',
text=self.__main_window.factory.language.translate(
'window.buttons.exit'
),
background='white',
command=master.quit
)
Expand All @@ -48,7 +54,11 @@ def __init__(self, master: 'Main'):
def download(self):
def run():
toplevel = Toplevel(self)
toplevel.title('下载配置文件…………')
toplevel.title(
self.__main_window.factory.language.translate(
'download.file.title'
)
)
toplevel.resizable(False, False)
toplevel.protocol("WM_DELETE_WINDOW", lambda: None)
toplevel.iconbitmap(PATH.ICON_PATH)
Expand Down
21 changes: 18 additions & 3 deletions utils/window/frames/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,26 @@ def __init__(self, master: 'Main'):

self.main_window = master

self.sort_label = Label(self, text='排序方式:')
self.sort_label = Label(
self,
text=self.main_window.factory.language.translate(
'window.filters.sort'
)
)
self.sort_combobox = Combobox(self, width=15, state='readonly')
self.game_version_label = Label(self, text='游戏版本:')
self.game_version_label = Label(
self,
text=self.main_window.factory.language.translate(
'window.filters.gameVersion'
)
)
self.game_version_combobox = Combobox(self, width=10, state='readonly')
self.modpack_version_label = Label(self, text='整合包版本:')
self.modpack_version_label = Label(
self,
text=self.main_window.factory.language.translate(
'window.filters.modpackVersion'
)
)
self.modpack_version_combobox = Combobox(self, state='readonly')

# Update list when combobox selected
Expand Down
4 changes: 3 additions & 1 deletion utils/window/frames/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ def __init__(self, master: 'Main'):
self.search_entry = Entry(self)
self.search_button = Button(
self,
text='搜索',
text=self.main_window.factory.language.translate(
'window.search.search'
),
background='white',
command=self.on_search
)
Expand Down
9 changes: 8 additions & 1 deletion utils/window/frames/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ def run():
# Request and check result
result = request(self.__search_index)['data']
if len(result) == 0:
showwarning('提示', '共搜索到 0 个结果!')
showwarning(
self.__main_window.factory.language.translate(
'window.show.resultWarning.title'
),
self.__main_window.factory.language.translate(
'window.show.resultWarning.content'
)
)

# Add request results
for i in result:
Expand Down
10 changes: 8 additions & 2 deletions utils/window/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from tkinter.messagebox import askokcancel
from tkinter.simpledialog import askstring

from utils.constant import NAME_WITH_SPACE, LICENSE, WINDOW, PATH
from utils.constant import NAME_WITH_SPACE, WINDOW, PATH
from utils.factory import Factory
from utils.window import frames

Expand Down Expand Up @@ -47,7 +47,13 @@ def __init__(self, factory: Factory):

def main(self):
# ask license
if not ('--no-license' in sys.argv or askokcancel('版权声明', LICENSE)):
if not (
'--no-license' in sys.argv or
askokcancel(
self.factory.language.translate('license.title'),
self.factory.language.translate('license.content')
)
):
return

# ask api key
Expand Down

0 comments on commit 6a5ffd9

Please sign in to comment.