From e998a0edf50a07a123a7bf93f9e50b7304c44d9b Mon Sep 17 00:00:00 2001 From: hymbz Date: Fri, 22 Sep 2023 23:29:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20:sparkles:=20=E5=AE=9E=E7=8E=B0=20i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.cjs | 18 ++ .vscode/extensions.json | 5 + .vscode/i18n-ally-custom-framework.yml | 36 +++ .vscode/settings.json | 7 + README.md | 8 +- docs/README-out.md | 9 +- locales/en.json | 1 + locales/zh.json | 223 +++++++++++++++++- package.json | 6 + pnpm-lock.yaml | 79 +++++++ rollup.config.ts | 12 +- src/components/Manga/components/ComicImg.tsx | 3 +- src/components/Manga/components/EndPage.tsx | 17 +- src/components/Manga/components/Scrollbar.tsx | 2 +- ...s.module.css => SettingHotkeys.module.css} | 9 +- ...{SettingHotKeys.tsx => SettingHotkeys.tsx} | 45 ++-- .../Manga/components/SettingTranslation.tsx | 56 ++--- .../Manga/components/SettingsItemSelect.tsx | 17 +- src/components/Manga/components/TouchArea.tsx | 16 +- src/components/Manga/defaultButtonList.tsx | 24 +- src/components/Manga/defaultSettingList.tsx | 88 ++++--- src/components/Manga/display.tsx | 12 +- src/components/Manga/hooks/useInit.ts | 14 +- .../Manga/hooks/useStore/ImageState.ts | 8 - .../Manga/hooks/useStore/OptionState.ts | 24 +- .../Manga/hooks/useStore/OtherState.ts | 28 +-- src/components/Manga/hooks/useStore/index.ts | 2 +- .../Manga/hooks/useStore/slice/ExternalLib.ts | 2 +- .../Manga/hooks/useStore/slice/Helper.ts | 2 +- .../Manga/hooks/useStore/slice/HotKeys.ts | 43 ---- .../Manga/hooks/useStore/slice/Hotkeys.ts | 43 ++++ .../Manga/hooks/useStore/slice/Image.ts | 6 +- .../Manga/hooks/useStore/slice/Operate.ts | 40 ++-- .../Manga/hooks/useStore/slice/Scrollbar.ts | 7 +- .../useStore/slice/Translation/cotrans.ts | 24 +- .../useStore/slice/Translation/helper.ts | 39 +-- .../hooks/useStore/slice/Translation/index.ts | 22 +- .../useStore/slice/Translation/selfhosted.ts | 23 +- .../Manga/hooks/useStore/slice/index.ts | 2 +- src/components/Manga/index.module.css | 14 +- src/components/Manga/index.tsx | 4 +- src/components/useComponents/Manga.tsx | 18 +- src/helper/i18n.ts | 53 +++++ src/helper/index.ts | 4 +- src/helper/request.ts | 3 +- src/helper/useInit.tsx | 34 +-- src/helper/useSiteOptions.ts | 24 +- src/helper/useSpeedDial.tsx | 14 +- src/main.ts | 1 + src/pwa/src/index.tsx | 25 +- src/pwa/src/md/install.md | 9 - src/pwa/src/md/tip.md | 7 - src/pwa/src/store.ts | 5 +- src/pwa/src/unzip/fflate.ts | 5 +- src/pwa/src/unzip/index.ts | 7 +- src/pwa/src/unzip/libarchive.ts | 15 +- src/pwa/src/unzip/libunrar.ts | 9 +- src/pwa/vite.config.ts | 7 + src/site/ehentai.tsx | 49 ++-- src/site/nhentai.tsx | 25 +- src/site/other.tsx | 23 +- tsconfig.json | 3 +- 62 files changed, 917 insertions(+), 463 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/i18n-ally-custom-framework.yml create mode 100644 locales/en.json rename src/components/Manga/components/{SettingHotKeys.module.css => SettingHotkeys.module.css} (94%) rename src/components/Manga/components/{SettingHotKeys.tsx => SettingHotkeys.tsx} (62%) delete mode 100644 src/components/Manga/hooks/useStore/slice/HotKeys.ts create mode 100644 src/components/Manga/hooks/useStore/slice/Hotkeys.ts create mode 100644 src/helper/i18n.ts delete mode 100644 src/pwa/src/md/install.md delete mode 100644 src/pwa/src/md/tip.md diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f423a119..c50237e5 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -60,6 +60,7 @@ const publicConfig = { 'consistent-return': 'off', }, + reportUnusedDisableDirectives: true, }; /** 与公用规则合并 */ @@ -177,5 +178,22 @@ module.exports = { 'import/no-extraneous-dependencies': 'off', }, }), + buildConfig({ + plugins: ['i18n', 'i18next'], + files: ['src/**/*.ts', 'src/**/*.tsx'], + excludedFiles: [ + 'display.*', + '*.test.*', + 'vite.config.ts', + '**/helper/import.ts', + '**/helper/dmzjApi.ts', + 'src/*.*', + 'src/site/!(other|nhentai|ehentai).tsx', + ], + rules: { + 'i18next/no-literal-string': 'error', + 'i18n/no-chinese-character': 'error', + }, + }), ], }; diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..f8e513bb --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "lokalise.i18n-ally" + ] +} diff --git a/.vscode/i18n-ally-custom-framework.yml b/.vscode/i18n-ally-custom-framework.yml new file mode 100644 index 00000000..11b4dfa0 --- /dev/null +++ b/.vscode/i18n-ally-custom-framework.yml @@ -0,0 +1,36 @@ +# .vscode/i18n-ally-custom-framework.yml + +# An array of strings which contain Language Ids defined by VS Code +# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers +languageIds: + - javascript + - typescript + - javascriptreact + - typescriptreact + +# An array of RegExes to find the key usage. **The key should be captured in the first match group**. +# You should unescape RegEx strings in order to fit in the YAML file +# To help with this, you can use https://www.freeformatter.com/json-escape.html +usageMatchRegex: + # The following example shows how to detect `t("your.i18n.keys")` + # the `{key}` will be placed by a proper keypath matching regex, + # you can ignore it and use your own matching rules as well + - "[^\\w\\d]t\\(\\s*['\"`]({key})['\"`]" + +# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys +# and works like how the i18next framework identifies the namespace scope from the +# useTranslation() hook. +# You should unescape RegEx strings in order to fit in the YAML file +# To help with this, you can use https://www.freeformatter.com/json-escape.html +# scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]" + +# An array of strings containing refactor templates. +# The "$1" will be replaced by the keypath specified. +# Optional: uncomment the following two lines to use + +refactorTemplates: + - t('$1') + + +# If set to true, only enables this custom framework (will disable all built-in frameworks) +monopoly: true diff --git a/.vscode/settings.json b/.vscode/settings.json index 8965aad0..a9b49c36 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,4 +5,11 @@ "**/node_modules/*/**", "**/*.js" ], + "i18n-ally.displayLanguage": "zh", + "i18n-ally.localesPaths": [ + "locales" + ], + "i18n-ally.pathMatcher": "{locale}.{ext}", + "i18n-ally.keystyle": "nested", + "i18n-ally.sortKeys": true, } diff --git a/README.md b/README.md index 0b063230..8139e80b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ 这是一个因为目前大部分漫画站都不支持双页显示,所以每次遇到 **漫画中的跨页大图被分割成两页** 就很不爽的人为了有更好的漫画阅读体验而写的油猴脚本,为主流漫画站增加了**双页阅读模式**和优化体验的增强功能。 -脚本会在网页右下角弹出用于 **进入阅读模式** 的悬浮按钮,其上的快捷按钮用于切换站点增强功能的开启与否,脚本默认开启了**自动进入阅读模式**的功能,也可在这里关闭。脚本没有全局设置,所有修改都只会在当前站点生效保存。~~反正平时也就只上那几个站点~~ +脚本会在网页右下角弹出用于 **进入阅读模式** 的悬浮按钮,其上的快捷按钮用于切换站点增强功能的开启与否,默认会开启**自动进入阅读模式**的功能,也可在这里关闭。脚本没有全局设置,所有修改都只会在当前站点生效保存。~~反正平时也就只上那几个站点~~ 对于支持站点以外的网站,脚本也提供了「[简易阅读模式](#简易阅读模式)」,除了得手动跳转上/下一话外,和支持站点的使用体验没有区别。 @@ -99,7 +99,7 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h - 动漫之家 - 解锁隐藏漫画 - [ehentai](#ehentai) - - [nhentai 匹配](#nhentai匹配) + - [关联 nhentai](#关联-nhentai) - [nhentai](#nhentai) - [彻底屏蔽漫画](#彻底屏蔽漫画) - [自动翻页](#自动翻页) @@ -152,9 +152,9 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h 除悬浮按钮外,也会在右侧边栏会增加一个「Load comic」按钮,功能和悬浮按钮一样。 -### nhentai匹配 +### 关联 nhentai -根据漫画标题匹配 nhentai 的本子,结果会以标签的形式显示在标签栏中,标签内容为 nhentai 上的漫画 ID ,鼠标悬停在标签上可以看到漫画标题。 +根据漫画标题关联匹配 nhentai 的本子,结果会以标签的形式显示在标签栏中,标签内容为 nhentai 上的漫画 ID ,鼠标悬停在标签上可以看到漫画标题。 点击标签后,标签菜单有两个选项: diff --git a/docs/README-out.md b/docs/README-out.md index 55ef8f2b..a8ee478b 100644 --- a/docs/README-out.md +++ b/docs/README-out.md @@ -11,7 +11,7 @@ 这是一个因为目前大部分漫画站都不支持双页显示,所以每次遇到 **漫画中的跨页大图被分割成两页** 就很不爽的人为了有更好的漫画阅读体验而写的油猴脚本,为主流漫画站增加了**双页阅读模式**和优化体验的增强功能。 -脚本会在网页右下角弹出用于 **进入阅读模式** 的悬浮按钮,其上的快捷按钮用于切换站点增强功能的开启与否,脚本默认开启了**自动进入阅读模式**的功能,也可在这里关闭。脚本没有全局设置,所有修改都只会在当前站点生效保存。~~反正平时也就只上那几个站点~~ +脚本会在网页右下角弹出用于 **进入阅读模式** 的悬浮按钮,其上的快捷按钮用于切换站点增强功能的开启与否,默认会开启**自动进入阅读模式**的功能,也可在这里关闭。脚本没有全局设置,所有修改都只会在当前站点生效保存。~~反正平时也就只上那几个站点~~ 对于支持站点以外的网站,脚本也提供了「[简易阅读模式](#简易阅读模式)」,除了得手动跳转上/下一话外,和支持站点的使用体验没有区别。 @@ -99,11 +99,12 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h - 动漫之家 - 解锁隐藏漫画 - [ehentai](#ehentai) - - [nhentai 匹配](#nhentai匹配) + - [关联 nhentai](#关联-nhentai) - [nhentai](#nhentai) - [彻底屏蔽漫画](#彻底屏蔽漫画) - [自动翻页](#自动翻页) + - PonpomuYuri @@ -151,9 +152,9 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h 除悬浮按钮外,也会在右侧边栏会增加一个「Load comic」按钮,功能和悬浮按钮一样。 -### nhentai匹配 +### 关联 nhentai -根据漫画标题匹配 nhentai 的本子,结果会以标签的形式显示在标签栏中,标签内容为 nhentai 上的漫画 ID ,鼠标悬停在标签上可以看到漫画标题。 +根据漫画标题关联匹配 nhentai 的本子,结果会以标签的形式显示在标签栏中,标签内容为 nhentai 上的漫画 ID ,鼠标悬停在标签上可以看到漫画标题。 点击标签后,标签菜单有两个选项: diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/en.json @@ -0,0 +1 @@ +{} diff --git a/locales/zh.json b/locales/zh.json index 1c39dc20..b4972fa2 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -1,5 +1,224 @@ { - "tooltip": { - "fill_page": "填充页" + "alert": { + "comic_load_error": "漫画加载出错", + "get_comic_img_failed": "获取漫画图片失败", + "img_load_failed": "图片加载失败", + "repeat_load": "加载图片中,请稍候", + "server_connection_failed": "无法连接到服务器" + }, + "button": { + "close_current_page_translation": "关闭当前页的翻译", + "download": "下载", + "download_completed": "下载完成", + "download_failed": "下载失败", + "downloading": "下载中", + "exit": "退出", + "page_fill": "页面填充", + "page_mode_double": "双页模式", + "page_mode_single": "单页模式", + "scroll_mode": "卷轴模式", + "setting": "设置", + "start_packaging": "开始打包", + "translate_current_page": "翻译当前页", + "zoom_in": "放大" + }, + "end_page": { + "next_button": "下一话", + "prev_button": "上一话", + "tip": { + "end_jump": "已到结尾,继续向下翻页将跳至下一话", + "exit": "已到结尾,继续翻页将退出", + "start_jump": "已到开头,继续向上翻页将跳至上一话" } + }, + "hotkeys": { + "exit": "退出", + "jump_to_end": "跳至尾页", + "jump_to_home": "跳至首页", + "switch_dir": "切换阅读方向", + "switch_page_fill": "切换页面填充", + "switch_scroll_mode": "切换卷轴模式", + "switch_single_double_page_mode": "切换单双页模式", + "turn_page_down": "向下翻页", + "turn_page_left": "向左翻页", + "turn_page_right": "向右翻页", + "turn_page_up": "向上翻页", + "use_read_mode": "进入阅读模式" + }, + "img_status": { + "error": "加载出错", + "loading": "正在加载", + "wait": "等待加载" + }, + "language": { + "en": "英文", + "zh": "中文" + }, + "other": { + "auto_use_read_mode": "自动进入阅读模式", + "disable": "禁用", + "fab_hidden": "隐藏悬浮按钮", + "fab_show": "显示悬浮按钮", + "fill_page": "填充页", + "img_loading": "图片加载中", + "loading_img": "加载图片中", + "read_mode": "阅读模式", + "use_comic_read_mode": "进入漫画阅读模式" + }, + "pwa": { + "alert": { + "img_data_error": "图片数据错误", + "img_not_found": "找不到图片", + "img_not_found_dir": "文件夹下没有图片文件或含有图片文件的压缩包", + "img_not_found_files": "请选择图片文件或含有图片文件的压缩包", + "repeat_load": "正在加载其他文件中...", + "unzip_error": "解压出错", + "unzip_password_error": "解压密码错误", + "userscript_not_installed": "未安装 ComicRead 脚本" + }, + "button": { + "install": "安装", + "no_more_prompt": "不再提示", + "resume_read": "恢复阅读", + "select_dir": "选择文件夹", + "select_files": "选择文件" + }, + "install_md": "### 每次都要打开这个网页很麻烦?\n如果你希望\n1. 能有独立的窗口,像是在使用本地软件一样\n1. 加入本地压缩文件的打开方式之中,方便直接打开\n1. 离线使用~~(主要是担心国内网络抽风无法访问这个网页~~\n### 欢迎将本页面作为 PWA 应用安装到电脑上😃👍", + "message": { + "input_password": "请输入密码", + "unzipping": "解压缩中" + }, + "tip_md": "# ComicRead PWA\n使用 [ComicRead](https://github.com/hymbz/ComicReadScript) 的阅读模式阅读**本地**漫画\n---\n### 将图片文件、文件夹、压缩包直接拖入即可开始阅读" + }, + "setting": { + "hotkeys": { + "add": "添加新快捷键", + "recover": "恢复默认快捷键" + }, + "language": "语言", + "option": { + "always_load_all_img": "始终加载所有图片", + "background_color": "背景颜色", + "click_page_turn_enabled": "启用点击翻页", + "click_page_turn_lr_reverse": "左右反转点击区域", + "click_page_turn_vertical": "上下翻页", + "dark_mode": "启用夜间模式", + "dir_ltr": "从左到右(美漫)", + "dir_rtl": "从右到左(日漫)", + "disable_zoom_in": "禁止放大图片", + "first_page_fill": "默认启用首页填充", + "jump_to_next": "翻页至上/下一话", + "paragraph_dir": "阅读方向", + "paragraph_display": "显示", + "paragraph_hotkeys": "快捷键", + "paragraph_operation": "操作", + "paragraph_other": "其他", + "paragraph_scrollbar": "滚动条", + "paragraph_translation": "翻译", + "reverse_page_turn_key": "左右翻页键反转", + "scrollbar_auto_hidden": "自动隐藏滚动条", + "scrollbar_show": "显示滚动条", + "scrollbar_show_img_status": "显示图片加载状态", + "show_comment": "在结束页显示评论", + "show_touch_area": "显示点击区域" + }, + "translation": { + "cotrans_tip": "

将使用 Cotrans 提供的接口翻译图片,该服务器由维护者用爱发电自费维护

\n

多人同时使用时需要排队等待,等待队列达到上限后再上传新图片会报错,需要过段时间再试

\n

所以还请 注意用量

\n

更推荐使用本地部署的项目,不占用服务器资源也不需要排队

", + "options": { + "detection_resolution": "文本扫描清晰度", + "direction": "渲染字体方向", + "direction_auto": "跟随原文本", + "direction_horizontal": "仅限水平", + "direction_vertical": "仅限垂直", + "forceRetry": "忽略缓存强制重试", + "localUrl": "自定义服务器 URL", + "target_language": "目标语言", + "text_detector": "文本扫描器", + "translator": "翻译服务" + }, + "server": "翻译服务器", + "server_selfhosted": "本地部署", + "translate_all_img_switch": "翻译全部图片" + } + }, + "site": { + "add_feature": { + "associate_nhentai": "关联nhentai", + "auto_page_turn": "自动翻页", + "block_totally": "彻底屏蔽漫画", + "open_link_new_page": "在新页面中打开链接", + "shortcut_page_turn": "快捷键翻页" + }, + "ehentai": { + "fetch_img_page_source_fail": "获取图片页源码失败", + "fetch_img_page_url_fail": "从详情页获取图片页地址失败", + "fetch_img_url_fail": "从图片页获取图片地址失败", + "html_changed_load_fail": "页面结构发生改变,无法加载漫画", + "html_changed_nhentai_fail": "页面结构发生改变,匹配 nhentai 漫画功能无法正常生效", + "ip_banned": "IP地址被禁", + "nhentai_error": "nhentai 匹配出错", + "nhentai_fail": "匹配失败,请在确认登录 {{nhentai}} 后刷新" + }, + "nhentai": { + "fetch_next_page_fail": "获取下一页漫画数据失败", + "tag_blacklist_fetch_fail": "标签黑名单获取失败" + }, + "remember_current_site": "记住当前站点", + "settings_tip": "设置", + "show_settings_menu": "显示设置菜单", + "simple": { + "auto_read_mode_message": "将在之后默认自动进入阅读模式", + "simple_read_mode": "使用简易阅读模式" + } + }, + "touch_area": { + "menu": "菜 单", + "next": "下 一 页", + "prev": "上 一 页" + }, + "translation": { + "status": { + "default": "未知状态", + "detection": "正在检测文本", + "downscaling": "正在缩小图片", + "error": "翻译出错", + "error-lang": "你选择的翻译服务不支持你选择的语言", + "error-translating": "翻译服务没有返回任何文本", + "error-with-id": "翻译出错", + "finished": "正在整理结果", + "inpainting": "正在修补图片", + "mask-generation": "正在生成文本掩码", + "ocr": "正在识别文本", + "pending": "正在等待", + "pending-pos": "正在等待", + "rendering": "正在渲染", + "saved": "正在返回缓存结果", + "translating": "正在翻译文本", + "upscaling": "正在放大图片" + }, + "tip": { + "check_img_status_failed": "检查图片状态失败", + "download_img_failed": "下载图片失败", + "error": "翻译出错", + "get_translator_list_error": "获取部署服务的可用翻译时出错", + "id_not_returned": "未返回 id", + "img_downloading": "正在下载图片", + "img_not_fully_loaded": "图片未加载完毕", + "pending": "正在等待,列队还有 {{pos}} 张图片", + "translation_completed": "翻译完成", + "upload_error": "图片上传出错", + "upload_return_error": "服务器返回错误", + "wait_translation": "等待翻译" + }, + "translator": { + "baidu": "百度", + "deepl": "DeepL", + "google": "谷歌", + "gpt3.5": "GPT-3.5", + "none": "删除文本", + "offline": "离线模型", + "original": "原文", + "youdao": "有道" + } + } } diff --git a/package.json b/package.json index 986885d7..95b4c84f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "fflate": "^0.8.0", "jsencrypt": "^3.3.2", "libarchive.js": "^1.3.0", + "marked": "^9.0.3", "normalize.css": "^8.0.1", "panzoom": "^9.4.3", "protobufjs": "^7.2.4", @@ -34,14 +35,17 @@ }, "devDependencies": { "@babel/core": "^7.22.10", + "@babel/plugin-proposal-import-attributes-to-assertions": "^7.22.5", "@babel/plugin-transform-runtime": "^7.22.10", "@babel/preset-env": "^7.22.10", "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "^7.22.10", "@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@release-it/conventional-changelog": "^7.0.0", + "@rollup/plugin-alias": "^5.0.0", "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-commonjs": "^25.0.3", + "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.1.0", "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.3", @@ -59,6 +63,8 @@ "eslint": "^8.46.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-i18n": "^2.1.0", + "eslint-plugin-i18next": "^6.0.3", "eslint-plugin-import": "^2.28.0", "eslint-plugin-jsdoc": "^46.4.6", "eslint-plugin-prettier": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92e568d4..8e7b8701 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: libarchive.js: specifier: ^1.3.0 version: 1.3.0 + marked: + specifier: ^9.0.3 + version: 9.0.3 normalize.css: specifier: ^8.0.1 version: 8.0.1 @@ -51,6 +54,9 @@ importers: '@babel/core': specifier: ^7.22.10 version: 7.22.10 + '@babel/plugin-proposal-import-attributes-to-assertions': + specifier: ^7.22.5 + version: 7.22.5(@babel/core@7.22.10) '@babel/plugin-transform-runtime': specifier: ^7.22.10 version: 7.22.10(@babel/core@7.22.10) @@ -69,12 +75,18 @@ importers: '@release-it/conventional-changelog': specifier: ^7.0.0 version: 7.0.0(release-it@16.1.3) + '@rollup/plugin-alias': + specifier: ^5.0.0 + version: 5.0.0(rollup@3.27.2) '@rollup/plugin-babel': specifier: ^6.0.3 version: 6.0.3(@babel/core@7.22.10)(rollup@3.27.2) '@rollup/plugin-commonjs': specifier: ^25.0.3 version: 25.0.3(rollup@3.27.2) + '@rollup/plugin-json': + specifier: ^6.0.0 + version: 6.0.0(rollup@3.27.2) '@rollup/plugin-node-resolve': specifier: ^15.1.0 version: 15.1.0(rollup@3.27.2) @@ -126,6 +138,12 @@ importers: eslint-config-prettier: specifier: ^9.0.0 version: 9.0.0(eslint@8.46.0) + eslint-plugin-i18n: + specifier: ^2.1.0 + version: 2.1.0 + eslint-plugin-i18next: + specifier: ^6.0.3 + version: 6.0.3 eslint-plugin-import: specifier: ^2.28.0 version: 2.28.0(@typescript-eslint/parser@6.3.0)(eslint@8.46.0) @@ -1245,6 +1263,17 @@ packages: '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.21.4) dev: true + /@babel/plugin-proposal-import-attributes-to-assertions@7.22.5(@babel/core@7.22.10): + resolution: {integrity: sha512-o36QnXxoBTvKaFzJtXC///xhxcDBQnWXGuJ1EuLTdEjqkz43IvqYdZjSVy4vvjrQN2SVHE00MwzDxHd+8xHG7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.22.0 + dependencies: + '@babel/core': 7.22.10 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-import-attributes': 7.22.5(@babel/core@7.22.10) + dev: true + /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} engines: {node: '>=6.9.0'} @@ -4015,6 +4044,19 @@ packages: semver: 7.5.1 dev: true + /@rollup/plugin-alias@5.0.0(rollup@3.27.2): + resolution: {integrity: sha512-l9hY5chSCjuFRPsnRm16twWBiSApl2uYFLsepQYwtBuAxNMQ/1dJqADld40P0Jkqm65GRTLy/AC6hnpVebtLsA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + rollup: 3.27.2 + slash: 4.0.0 + dev: true + /@rollup/plugin-babel@5.3.1(@babel/core@7.22.10)(rollup@2.79.1): resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -4119,6 +4161,19 @@ packages: rollup: 3.20.6 dev: true + /@rollup/plugin-json@6.0.0(rollup@3.27.2): + resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2(rollup@3.27.2) + rollup: 3.27.2 + dev: true + /@rollup/plugin-node-resolve@11.2.1(rollup@2.79.1): resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==} engines: {node: '>= 10.0.0'} @@ -7335,6 +7390,19 @@ packages: - supports-color dev: true + /eslint-plugin-i18n@2.1.0: + resolution: {integrity: sha512-VXDiguvLhpKzzNm5bcgF3SjvT66eGvXoFHB+avUcg2QdqNejCfwAwbr0R7NEQMmiut6OT9G8olj6kcSHuUeEyA==} + engines: {node: '>=12.0.0'} + dev: true + + /eslint-plugin-i18next@6.0.3: + resolution: {integrity: sha512-RtQXYfg6PZCjejIQ/YG+dUj/x15jPhufJ9hUDGH0kCpJ6CkVMAWOQ9exU1CrbPmzeykxLjrXkjAaOZF/V7+DOA==} + engines: {node: '>=0.10.0'} + dependencies: + lodash: 4.17.21 + requireindex: 1.1.0 + dev: true + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.0)(eslint@8.38.0): resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} @@ -9487,6 +9555,12 @@ packages: engines: {node: '>=8'} dev: true + /marked@9.0.3: + resolution: {integrity: sha512-pI/k4nzBG1PEq1J3XFEHxVvjicfjl8rgaMaqclouGSMPhk7Q3Ejb2ZRxx/ZQOcQ1909HzVoWCFYq6oLgtL4BpQ==} + engines: {node: '>= 16'} + hasBin: true + dev: false + /mathml-tag-names@2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} dev: true @@ -11761,6 +11835,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /requireindex@1.1.0: + resolution: {integrity: sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==} + engines: {node: '>=0.10.5'} + dev: true + /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true diff --git a/rollup.config.ts b/rollup.config.ts index 9963de07..65922091 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -9,7 +9,9 @@ import commonjs from '@rollup/plugin-commonjs'; import { babel } from '@rollup/plugin-babel'; import styles from 'rollup-plugin-styles'; import solidPlugin from 'vite-plugin-solid'; +import json from '@rollup/plugin-json'; +import alias from '@rollup/plugin-alias'; import replace from '@rollup/plugin-replace'; import { watchExternal } from 'rollup-plugin-watch-external'; @@ -51,7 +53,9 @@ export const buildOptions = ( preventAssignment: true, }), + alias({ entries: { helper: resolve(__dirname, 'src/helper') } }), + json({ namedExports: false, indent: ' ' }), nodeResolve({ browser: true, extensions: ['.js', '.ts', '.tsx'] }), commonjs(), styles({ modules: true }), @@ -62,7 +66,13 @@ export const buildOptions = ( extensions: ['.ts', '.tsx'], exclude: ['node_modules/**'], presets: ['@babel/preset-env', '@babel/preset-typescript', 'solid'], - plugins: ['@babel/plugin-transform-runtime'], + plugins: [ + '@babel/plugin-transform-runtime', + [ + '@babel/plugin-proposal-import-attributes-to-assertions', + { deprecatedAssertSyntax: true }, + ], + ], }), watchFiles && isDevMode && watchExternal({ entries: watchFiles }), diff --git a/src/components/Manga/components/ComicImg.tsx b/src/components/Manga/components/ComicImg.tsx index 3a7ceebf..9617e177 100644 --- a/src/components/Manga/components/ComicImg.tsx +++ b/src/components/Manga/components/ComicImg.tsx @@ -1,6 +1,7 @@ import type { Component } from 'solid-js'; import { createMemo } from 'solid-js'; +import { t } from 'helper/i18n'; import type { State } from '../hooks/useStore'; import { setState, store } from '../hooks/useStore'; import { activePage, updateImgType } from '../hooks/useStore/slice'; @@ -90,7 +91,7 @@ const handleImgError = (i: number, e: HTMLImageElement) => { } img.loadType = 'error'; - console.error('图片加载失败', e); + console.error(t('alert.img_load_failed'), e); state.onLoading?.(state.imgList, img); }); diff --git a/src/components/Manga/components/EndPage.tsx b/src/components/Manga/components/EndPage.tsx index 1f53ba6b..ea89b7e9 100644 --- a/src/components/Manga/components/EndPage.tsx +++ b/src/components/Manga/components/EndPage.tsx @@ -8,6 +8,7 @@ import { Show, } from 'solid-js'; +import { t } from 'helper/i18n'; import { setState, store } from '../hooks/useStore'; import { bindRef, focus, turnPage } from '../hooks/useStore/slice'; @@ -58,13 +59,13 @@ export const EndPage: Component = () => { const tip = createMemo(() => { switch (delayType()) { case 'start': - if (store.onPrev && store.option.flipToNext) - return '已到开头,继续向上翻页将跳至上一话'; + if (store.onPrev && store.option.jumpToNext) + return t('end_page.tip.start_jump'); break; case 'end': - if (store.onNext && store.option.flipToNext) - return '已到结尾,继续向下翻页将跳至下一话'; - if (store.onExit) return '已到结尾,继续翻页将退出'; + if (store.onNext && store.option.jumpToNext) + return t('end_page.tip.end_jump'); + if (store.onExit) return t('end_page.tip.exit'); break; } return ''; @@ -88,7 +89,7 @@ export const EndPage: Component = () => { tabIndex={store.endPageType ? 0 : -1} on:click={() => store.onPrev?.()} > - 上一话 + {t('end_page.prev_button')}
{
- + {([a, b]) => } diff --git a/src/components/Manga/components/SettingHotKeys.module.css b/src/components/Manga/components/SettingHotkeys.module.css similarity index 94% rename from src/components/Manga/components/SettingHotKeys.module.css rename to src/components/Manga/components/SettingHotkeys.module.css index 3bfc2ace..6dd5d4d7 100644 --- a/src/components/Manga/components/SettingHotKeys.module.css +++ b/src/components/Manga/components/SettingHotkeys.module.css @@ -1,4 +1,4 @@ -.hotKeys { +.hotkeys { position: relative; z-index: 1; @@ -24,7 +24,7 @@ } } -.hotKeysItem { +.hotkeysItem { cursor: pointer; display: flex; @@ -72,7 +72,7 @@ } } -.hotKeysHeader { +.hotkeysHeader { position: absolute; top: 0; left: 0; @@ -85,7 +85,6 @@ padding: 0 0.5em; & > p { - margin: 0; background-color: var(--page_bg); } @@ -106,6 +105,6 @@ } } -.hotKeys:hover div[title] { +.hotkeys:hover div[title] { transform: scale(1); } diff --git a/src/components/Manga/components/SettingHotKeys.tsx b/src/components/Manga/components/SettingHotkeys.tsx similarity index 62% rename from src/components/Manga/components/SettingHotKeys.tsx rename to src/components/Manga/components/SettingHotkeys.tsx index 84d0848c..d6b4551c 100644 --- a/src/components/Manga/components/SettingHotKeys.tsx +++ b/src/components/Manga/components/SettingHotkeys.tsx @@ -4,15 +4,16 @@ import MdAdd from '@material-design-icons/svg/round/add.svg'; import { type Component, For, Index } from 'solid-js'; +import { getKeyboardCode, keyboardCodeToText } from 'helper'; +import { t } from 'helper/i18n'; import { - delHotKeys, + delHotkeys, focus, - hotKeysMap, - setHotKeys, + hotkeysMap, + setHotkeys, } from '../hooks/useStore/slice'; -import { defaultHoeKeys } from '../hooks/useStore/OtherState'; +import { defaultHotkeys } from '../hooks/useStore/OtherState'; import { store } from '../hooks/useStore'; -import { getKeyboardCode, keyboardCodeToText } from '../../../helper'; import classes from '../index.module.css'; @@ -20,9 +21,9 @@ const KeyItem: Component<{ operateName: string; i: number; }> = (props) => { - const code = () => store.hotKeys[props.operateName][props.i]; + const code = () => store.hotkeys[props.operateName][props.i]; - const del = () => delHotKeys(code()); + const del = () => delHotkeys(code()); const handleKeyDown = (e: KeyboardEvent) => { e.stopPropagation(); @@ -36,18 +37,18 @@ const KeyItem: Component<{ return; case 'Backspace': - setHotKeys(props.operateName, props.i, ''); + setHotkeys(props.operateName, props.i, ''); return; } const newCode = getKeyboardCode(e); - if (!Reflect.has(hotKeysMap(), newCode)) - setHotKeys(props.operateName, props.i, newCode); + if (!Reflect.has(hotkeysMap(), newCode)) + setHotkeys(props.operateName, props.i, newCode); }; return (
( - +export const SettingHotkeys: Component = () => ( + {([name, keys]) => ( -
-
-

{name}

+
+
+

{t(`hotkeys.${name}`) || name}

setHotKeys(name, store.hotKeys[name].length, '')} + title={t('setting.hotkeys.add')} + on:click={() => setHotkeys(name, store.hotkeys[name].length, '')} >
{ - const newKeys = defaultHoeKeys[name] ?? []; - newKeys.forEach(delHotKeys); - setHotKeys(name, newKeys); + const newKeys = defaultHotkeys[name] ?? []; + newKeys.forEach(delHotkeys); + setHotkeys(name, newKeys); }} > diff --git a/src/components/Manga/components/SettingTranslation.tsx b/src/components/Manga/components/SettingTranslation.tsx index 8ccc071f..1e463619 100644 --- a/src/components/Manga/components/SettingTranslation.tsx +++ b/src/components/Manga/components/SettingTranslation.tsx @@ -1,5 +1,6 @@ import { createMemo, Show } from 'solid-js'; +import { t } from 'helper/i18n'; import { SettingsItemSwitch } from './SettingsItemSwitch'; import { SettingsItemSelect } from './SettingsItemSelect'; import { createStateSetFn, setOption } from '../hooks/useStore/slice'; @@ -23,34 +24,24 @@ export const SettingTranslation = () => { return ( <> -
-

- 将使用{' '} - - Cotrans - {' '} - 提供的接口翻译图片,该服务器由维护者用爱发电自费维护 -

-

- 多人同时使用时需要排队等待,等待队列达到上限后再上传新图片会报错,需要过段时间再试 -

-

- 所以还请注意用量 -

-

更推荐使用本地部署的项目,不抢服务器资源也不需要排队

-
+ {/* eslint-disable-next-line solid/no-innerhtml */} +
- + { onChange={createStateSetFn('translation.options.size')} /> { /> - + setImgTranslationEnbale( @@ -127,7 +121,7 @@ export const SettingTranslation = () => { /> { setOption((draftOption) => { diff --git a/src/components/Manga/components/SettingsItemSelect.tsx b/src/components/Manga/components/SettingsItemSelect.tsx index 164daf02..ec58bc16 100644 --- a/src/components/Manga/components/SettingsItemSelect.tsx +++ b/src/components/Manga/components/SettingsItemSelect.tsx @@ -1,23 +1,24 @@ -import { For, type Component, createEffect } from 'solid-js'; +import { For, createEffect } from 'solid-js'; +import type { JSX } from 'solid-js'; import { SettingsItem } from './SettingsItem'; import classes from '../index.module.css'; -export interface SettingsItemSelectProps { +export interface SettingsItemSelectProps { options: ([string, string] | [string])[]; name: string; - value: string; + value: T; class?: string; classList?: ClassList; - onChange: (val: string) => void; + onChange: (val: T) => void; } /** 选择器式菜单项 */ -export const SettingsItemSelect: Component = ( - props, -) => { +export const SettingsItemSelect = ( + props: SettingsItemSelectProps, +): JSX.Element => { let ref: HTMLSelectElement; createEffect(() => { @@ -35,7 +36,7 @@ export const SettingsItemSelect: Component = ( + + ), true, diff --git a/src/components/Manga/display.tsx b/src/components/Manga/display.tsx index a67b7257..4b01f379 100644 --- a/src/components/Manga/display.tsx +++ b/src/components/Manga/display.tsx @@ -145,17 +145,7 @@ export default function DisplayManga() { console.log('end func 点击'); }; - const option: MangaProps['option'] = { - scrollbar: { - enabled: true, - autoHidden: false, - showProgress: true, - }, - clickPage: { - enabled: false, - overturn: false, - }, - }; + const option: MangaProps['option'] = {}; return ; } diff --git a/src/components/Manga/hooks/useInit.ts b/src/components/Manga/hooks/useInit.ts index ae4d0e5b..a9b9af6a 100644 --- a/src/components/Manga/hooks/useInit.ts +++ b/src/components/Manga/hooks/useInit.ts @@ -1,14 +1,14 @@ import { debounce, throttle } from 'throttle-debounce'; import { createEffect, onCleanup } from 'solid-js'; +import { assign, isEqualArray } from 'helper'; import type { MangaProps } from '..'; import { store, setState } from './useStore'; import { updatePageData, handleResize } from './useStore/slice'; import type { Option } from './useStore/OptionState'; import { autoCloseFill } from '../handleComicData'; import { playAnimation } from '../helper'; -import { assign, isEqualArray } from '../../../helper'; -import { defaultHoeKeys } from './useStore/OtherState'; +import { defaultHotkeys } from './useStore/OtherState'; /** 初始化 */ export const useInit = (props: MangaProps, rootRef: HTMLElement) => { @@ -22,9 +22,9 @@ export const useInit = (props: MangaProps, rootRef: HTMLElement) => { setState((state) => { if (props.option) state.option = assign(state.option, props.option as Option); - state.hotKeys = { - ...JSON.parse(JSON.stringify(defaultHoeKeys)), - ...props.hotKeys, + state.hotkeys = { + ...JSON.parse(JSON.stringify(defaultHotkeys)), + ...props.hotkeys, }; }); }); @@ -80,8 +80,8 @@ export const useInit = (props: MangaProps, rootRef: HTMLElement) => { state.onOptionChange = props.onOptionChange ? debounce(100, props.onOptionChange) : undefined; - state.onHotKeysChange = props.onHotKeysChange - ? debounce(100, props.onHotKeysChange) + state.onHotkeysChange = props.onHotkeysChange + ? debounce(100, props.onHotkeysChange) : undefined; }); }); diff --git a/src/components/Manga/hooks/useStore/ImageState.ts b/src/components/Manga/hooks/useStore/ImageState.ts index dbddeebd..8aca525e 100644 --- a/src/components/Manga/hooks/useStore/ImageState.ts +++ b/src/components/Manga/hooks/useStore/ImageState.ts @@ -14,14 +14,6 @@ declare global { type PageList = Array<[number] | [number, number]>; } -/** 加载状态的中文描述 */ -export const loadTypeMap: Record = { - error: '加载出错', - loading: '正在加载', - wait: '等待加载', - loaded: '', -}; - /** 页面填充数据 */ export type FillEffect = Record; diff --git a/src/components/Manga/hooks/useStore/OptionState.ts b/src/components/Manga/hooks/useStore/OptionState.ts index f2e8d330..2b8eb2a8 100644 --- a/src/components/Manga/hooks/useStore/OptionState.ts +++ b/src/components/Manga/hooks/useStore/OptionState.ts @@ -6,17 +6,17 @@ export interface Option { /** 自动隐藏 */ autoHidden: boolean; /** 显示图片加载状态 */ - showProgress: boolean; + showImgStatus: boolean; }; /** 单页模式 */ onePageMode: boolean; /** 卷轴模式 */ scrollMode: boolean; /** 点击翻页 */ - clickPage: { + clickPageTurn: { enabled: boolean; /** 左右反转点击区域 */ - overturn: boolean; + reverse: boolean; /** 将点击区域改为上下翻页 */ vertical: boolean; }; @@ -29,9 +29,9 @@ export interface Option { /** 黑暗模式 */ darkMode: boolean; /** 左右翻页键交换 */ - swapTurnPage: boolean; + reversePageTurnKey: boolean; /** 滚动到底后继续滚动会跳至下一话 */ - flipToNext: boolean; + jumpToNext: boolean; /** 始终加载所有图片 */ alwaysLoadAllImg: boolean; /** 卷轴模式下图片的缩放比例 */ @@ -42,7 +42,7 @@ export interface Option { /** 翻译 */ translation: { /** 翻译服务器 */ - server: '禁用' | '本地部署' | 'cotrans'; + server: 'disable' | 'selfhosted' | 'cotrans'; /** 本地部署的项目 url */ localUrl: string | undefined; /** 忽略缓存强制重试 */ @@ -63,26 +63,26 @@ export const defaultOption: Readonly