diff --git a/.gitignore b/.gitignore index 2c8c8cf..3daa43e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ build # Auto-generated data assets/en.lproj/Localizable.strings +assets/po/base.pot meta.swift +*~ diff --git a/CMakeLists.txt b/CMakeLists.txt index 774eaa7..b77faff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,9 @@ include_directories( fcitx5-webview/webview "${HOMEBREW_PATH}/include" # nlohmann-json ) + +add_definitions(-DFCITX_GETTEXT_DOMAIN=\"fcitx5-macos\") + add_subdirectory(macosfrontend) add_subdirectory(macosnotifications) add_subdirectory(webpanel) diff --git a/README.md b/README.md index bd2eac1..b81c1cd 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ Fcitx5 only packges keyboard engine. To install other engines, see [fcitx5-macos-plugins](https://github.com/fcitx-contrib/fcitx5-macos-plugins). ## Translation + +### Swift sources To update .strings files for each supported locale, run ```sh cmake --build build --target GenerateStrings @@ -59,6 +61,25 @@ cmake --build build --target GenerateStrings This will, e.g., update assets/zh-Hans/Localizable.strings, and then the translator can work on it. +### C++ sources +First, create assets/po/base.pot file: +```sh +cmake --build build --target pot +``` + +To add a new language, do +```sh +cd assets/po && msginit +``` +and then add this locale to assets/CMakeLists.txt. + +Then, use a PO file editor to translate strings. + +Finally, to merge new strings into PO files, do +```sh +cd assets/po && msgmerge -U .po base.pot +``` + ## Credits * [fcitx5](https://github.com/fcitx/fcitx5): LGPL-2.1-or-later * [fcitx5-android](https://github.com/fcitx5-android/fcitx5-android): LGPL-2.1-or-later diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index b509d44..0fe2691 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -30,3 +30,63 @@ install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/zh-Hant.lproj" install(DIRECTORY "${PREBUILT_INSTALL_PATH}/share/icons" DESTINATION "${CMAKE_INSTALL_PREFIX}/share" ) + +# +# Swift i18n through Localizable.strings +# Use 'GenerateStrings' to update .strings files. +# +file(GLOB_RECURSE LOCALIZABLE_SWIFT_SOURCES ${CMAKE_SOURCE_DIR}/src/*.swift) +set(LOCALIZABLE_STRINGS_FILES + ${PROJECT_SOURCE_DIR}/assets/en.lproj/Localizable.strings + ${PROJECT_SOURCE_DIR}/assets/zh-Hans.lproj/Localizable.strings +) +add_custom_command( + OUTPUT ${LOCALIZABLE_STRINGS_FILES} + COMMAND genstrings ${LOCALIZABLE_SWIFT_SOURCES} -SwiftUI -o ${PROJECT_SOURCE_DIR}/assets/en.lproj + COMMAND ${PROJECT_SOURCE_DIR}/assets/update_translations.py ${LOCALIZABLE_STRINGS_FILES} + DEPENDS ${LOCALIZABLE_SWIFT_SOURCES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating Localizable.strings..." +) +add_custom_target(GenerateStrings + DEPENDS ${PROJECT_SOURCE_DIR}/assets/en.lproj/Localizable.strings +) + +# +# C++ i18n through gettext +# Use 'pot' to generate the POT file. +# +set(TRANSLATABLE_CXX_SOURCES + src/*.cpp + src/*.h + macosfrontend/*.cpp + macosfrontend/*.h + macosnotifications/*.cpp + macosnotifications/*.h + webpanel/*.cpp + webpanel/*.h +) +add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/assets/po/base.pot + COMMAND xgettext --c++ --keyword=_ ${TRANSLATABLE_CXX_SOURCES} -o ${CMAKE_SOURCE_DIR}/assets/po/base.pot + DEPENDS ${LOCALIZABLE_CXX_SOURCES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Generating base.pot..." +) +add_custom_target(pot + DEPENDS ${CMAKE_SOURCE_DIR}/assets/po/base.pot +) + +foreach(LOCALE zh_CN) + set(MO_FILE ${CMAKE_CURRENT_BINARY_DIR}/po/${LOCALE}.mo) + add_custom_command( + OUTPUT ${MO_FILE} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/po/${LOCALE}.po + COMMAND msgfmt ${CMAKE_CURRENT_SOURCE_DIR}/po/${LOCALE}.po -o ${MO_FILE} + ) + add_custom_target(${LOCALE}_mo ALL DEPENDS ${MO_FILE}) + install(FILES ${MO_FILE} + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/locale/${LOCALE}/LC_MESSAGES + RENAME fcitx5-macos.mo + ) +endforeach() diff --git a/assets/po/zh_CN.po b/assets/po/zh_CN.po new file mode 100644 index 0000000..5edf539 --- /dev/null +++ b/assets/po/zh_CN.po @@ -0,0 +1,273 @@ +# Chinese translations for fcitx5-macos package +# fcitx 软件包的简体中文翻译. +# Copyright (C) 2024 fcitx5-macos's contributors +# This file is distributed under the same license as the fcitx5-macos package. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: fcitx 5\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 19:11-0400\n" +"PO-Revision-Date: 2024-04-04 00:50+0800\n" +"Last-Translator: ksqsf \n" +"Language-Team: \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: macosfrontend/macosfrontend.h:42 +msgid "App default IM" +msgstr "应用默认输入法" + +#: macosfrontend/macosfrontend.h:44 +msgid "Simulate key release" +msgstr "模拟按键释放" + +#: macosfrontend/macosfrontend.h:47 +msgid "Delay of simulated key release in milliseconds" +msgstr "模拟按键释放延迟毫秒数" + +#: macosnotifications/macosnotifications.h:20 +msgid "Hidden Notifications" +msgstr "隐藏通知" + +#: webpanel/webpanel.h:43 webpanel/webpanel.h:87 +msgid "Override default" +msgstr "覆盖默认值" + +#: webpanel/webpanel.h:44 +msgid "Highlight color" +msgstr "高亮颜色" + +#: webpanel/webpanel.h:47 webpanel/webpanel.h:93 +msgid "Highlight color on hover" +msgstr "悬停时高亮颜色" + +#: webpanel/webpanel.h:50 webpanel/webpanel.h:96 +msgid "Highlight text color" +msgstr "高亮文本颜色" + +#: webpanel/webpanel.h:53 webpanel/webpanel.h:99 +msgid "Highlight text color on press" +msgstr "按下时高亮文本颜色" + +#: webpanel/webpanel.h:56 webpanel/webpanel.h:102 +msgid "Highlight label color" +msgstr "高亮标签颜色" + +#: webpanel/webpanel.h:59 webpanel/webpanel.h:105 +msgid "Highlight comment color" +msgstr "高亮注释颜色" + +#: webpanel/webpanel.h:62 webpanel/webpanel.h:108 +msgid "Highlight mark color" +msgstr "高亮标记颜色" + +#: webpanel/webpanel.h:64 +msgid "Panel color" +msgstr "面板颜色" + +#: webpanel/webpanel.h:66 webpanel/webpanel.h:112 +msgid "Text color" +msgstr "文本颜色" + +#: webpanel/webpanel.h:68 webpanel/webpanel.h:114 +msgid "Label color" +msgstr "标签颜色" + +#: webpanel/webpanel.h:70 webpanel/webpanel.h:116 +msgid "Comment color" +msgstr "注释颜色" + +#: webpanel/webpanel.h:73 webpanel/webpanel.h:119 +msgid "Paging button color" +msgstr "翻页按钮颜色" + +#: webpanel/webpanel.h:76 webpanel/webpanel.h:122 +msgid "Disabled paging button color" +msgstr "禁用的翻页按钮颜色" + +#: webpanel/webpanel.h:78 webpanel/webpanel.h:124 +msgid "Preedit color" +msgstr "预编辑颜色" + +#: webpanel/webpanel.h:80 +msgid "Border color" +msgstr "边框颜色" + +#: webpanel/webpanel.h:82 +msgid "Divider color" +msgstr "分隔线颜色" + +#: webpanel/webpanel.h:89 +msgid "Same with light mode" +msgstr "与浅色模式相同" + +#: webpanel/webpanel.h:133 +msgid "Layout" +msgstr "布局" + +#: webpanel/webpanel.h:136 +msgid "Show paging buttons" +msgstr "显示翻页按钮" + +#: webpanel/webpanel.h:141 +msgid "Image URL" +msgstr "图片 URL" + +#: webpanel/webpanel.h:142 +msgid "Blur" +msgstr "模糊" + +#: webpanel/webpanel.h:144 +msgid "Radius of blur (px)" +msgstr "模糊半径(px)" + +#: webpanel/webpanel.h:146 +msgid "Shadow" +msgstr "阴影" + +#: webpanel/webpanel.h:154 +msgid "Text font family" +msgstr "文本字体族" + +#: webpanel/webpanel.h:156 +msgid "Text font size" +msgstr "文本字号" + +#: webpanel/webpanel.h:158 +msgid "Label font family" +msgstr "标签字体族" + +#: webpanel/webpanel.h:160 +msgid "Label font size" +msgstr "标签字号" + +#: webpanel/webpanel.h:162 +msgid "Comment font family" +msgstr "注释字体族" + +#: webpanel/webpanel.h:164 +msgid "Comment font size" +msgstr "注释字号" + +#: webpanel/webpanel.h:167 +msgid "Preedit font family" +msgstr "预编辑字体族" + +#: webpanel/webpanel.h:169 +msgid "Preedit font size" +msgstr "预编辑字号" + +#: webpanel/webpanel.h:171 +msgid "User font dir" +msgstr "用户字体目录" + +#: webpanel/webpanel.h:172 +msgid "System font dir" +msgstr "系统字体目录" + +#: webpanel/webpanel.h:176 +msgid "Style" +msgstr "样式" + +#: webpanel/webpanel.h:178 +msgid "Text" +msgstr "文本" + +#: webpanel/webpanel.h:182 +msgid "Mark style" +msgstr "标记样式" + +#: webpanel/webpanel.h:184 +msgid "Mark text" +msgstr "标记文本" + +#: webpanel/webpanel.h:186 +msgid "Hover behavior" +msgstr "悬停行为" + +#: webpanel/webpanel.h:191 +msgid "Border width (px)" +msgstr "边框宽度(px)" + +#: webpanel/webpanel.h:194 +msgid "Border radius (px)" +msgstr "边框半径(px)" + +#: webpanel/webpanel.h:195 +msgid "Margin (px)" +msgstr "外边距(px)" + +#: webpanel/webpanel.h:198 +msgid "Highlight radius (px)" +msgstr "高亮半径(px)" + +#: webpanel/webpanel.h:201 +msgid "Top padding (px)" +msgstr "顶填充(px)" + +#: webpanel/webpanel.h:203 +msgid "Right padding (px)" +msgstr "右填充(px)" + +#: webpanel/webpanel.h:205 +msgid "Bottom padding (px)" +msgstr "底填充(px)" + +#: webpanel/webpanel.h:208 +msgid "Left padding (px)" +msgstr "左填充(px)" + +#: webpanel/webpanel.h:210 +msgid "Gap between label and text (px)" +msgstr "标签与文本间隔(px)" + +#: webpanel/webpanel.h:213 +msgid "Horizontal divider width (px)" +msgstr "水平分隔线宽度(px)" + +#: webpanel/webpanel.h:220 +msgid "Type here to preview style" +msgstr "在这里输入以预览样式" + +#: webpanel/webpanel.h:221 +msgid "Follow cursor" +msgstr "跟随光标" + +#: webpanel/webpanel.h:222 +msgid "Theme" +msgstr "主题" + +#: webpanel/webpanel.h:224 +msgid "Light mode" +msgstr "浅色模式" + +#: webpanel/webpanel.h:225 +msgid "Dark mode" +msgstr "深色模式" + +#: webpanel/webpanel.h:226 +msgid "Typography" +msgstr "版式" + +#: webpanel/webpanel.h:227 +msgid "Background" +msgstr "背景" + +#: webpanel/webpanel.h:228 +msgid "Font" +msgstr "字体" + +#: webpanel/webpanel.h:229 +msgid "Cursor" +msgstr "鼠标" + +#: webpanel/webpanel.h:230 +msgid "Highlight" +msgstr "高亮" + +#: webpanel/webpanel.h:231 +msgid "Size" +msgstr "尺寸" diff --git a/assets/zh-Hans.lproj/Localizable.strings b/assets/zh-Hans.lproj/Localizable.strings index e21d7ea..996b302 100644 Binary files a/assets/zh-Hans.lproj/Localizable.strings and b/assets/zh-Hans.lproj/Localizable.strings differ diff --git a/macosfrontend/CMakeLists.txt b/macosfrontend/CMakeLists.txt index c642227..7eb6c75 100644 --- a/macosfrontend/CMakeLists.txt +++ b/macosfrontend/CMakeLists.txt @@ -1,5 +1,3 @@ -add_definitions(-DFCITX_GETTEXT_DOMAIN=\"fcitx5-macos\") - _swift_generate_cxx_header_target( macosfrontend_swift_h SwiftFrontend diff --git a/macosnotifications/CMakeLists.txt b/macosnotifications/CMakeLists.txt index 7690e83..c60862c 100644 --- a/macosnotifications/CMakeLists.txt +++ b/macosnotifications/CMakeLists.txt @@ -1,5 +1,3 @@ -add_definitions(-DFCITX_GETTEXT_DOMAIN=\"fcitx5-macos\") - _swift_generate_cxx_header_target( notify_swift_h SwiftNotify diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1aa0524..94cab9a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,6 +41,7 @@ target_link_libraries(Fcitx5Objs add_executable(Fcitx5 MACOSX_BUNDLE server.swift + locale.swift controller.swift ${CONFIG_UI_FILES} ) @@ -59,21 +60,3 @@ add_custom_command(TARGET Fcitx5 POST_BUILD install(TARGETS Fcitx5 BUNDLE DESTINATION "${APP_INSTALL_PATH}" ) - -# Update Localizable.strings -set(LOCALIZABLE_SOURCES controller.swift server.swift ${CONFIG_UI_FILES}) -set(LOCALIZABLE_STRINGS_FILES - ${PROJECT_SOURCE_DIR}/assets/en.lproj/Localizable.strings - ${PROJECT_SOURCE_DIR}/assets/zh-Hans.lproj/Localizable.strings -) -add_custom_command( - OUTPUT ${LOCALIZABLE_STRINGS_FILES} - COMMAND genstrings ${LOCALIZABLE_SOURCES} -SwiftUI -o ${PROJECT_SOURCE_DIR}/assets/en.lproj - COMMAND ${PROJECT_SOURCE_DIR}/assets/update_translations.py ${LOCALIZABLE_STRINGS_FILES} - DEPENDS ${LOCALIZABLE_SOURCES} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Generating Localizable.strings..." -) -add_custom_target(GenerateStrings - DEPENDS ${PROJECT_SOURCE_DIR}/assets/en.lproj/Localizable.strings -) diff --git a/src/fcitx-public.h b/src/fcitx-public.h index ab72f2c..2d6fa0c 100644 --- a/src/fcitx-public.h +++ b/src/fcitx-public.h @@ -8,7 +8,7 @@ // Identical to fcitx::ICUUID. Replicated for Swift interop. typedef std::array ICUUID; -void start_fcitx_thread() noexcept; +void start_fcitx_thread(const char *locale) noexcept; void stop_fcitx_thread() noexcept; void restart_fcitx_thread() noexcept; diff --git a/src/fcitx.cpp b/src/fcitx.cpp index 68f20e8..0247180 100644 --- a/src/fcitx.cpp +++ b/src/fcitx.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,7 @@ static std::string join_paths(const std::vector &paths, static std::thread fcitx_thread; static std::atomic fcitx_thread_started; +static std::string current_locale; Fcitx &Fcitx::shared() { static Fcitx fcitx; @@ -95,10 +97,31 @@ void Fcitx::setupEnv() { std::string libime_model_dirs = join_paths({ user_prefix / "lib" / "libime" // ~/Library/fcitx5/lib/libime }); - setenv("LANGUAGE", "en", 1); // Needed by libintl-lite setenv("FCITX_ADDON_DIRS", fcitx_addon_dirs.c_str(), 1); setenv("XDG_DATA_DIRS", xdg_data_dirs.c_str(), 1); setenv("LIBIME_MODEL_DIRS", libime_model_dirs.c_str(), 1); + + // Set LANGUAGE for libintl-lite. + std::string val = current_locale; + size_t dot_pos = val.find('.'); + if (dot_pos != std::string::npos) { + val = val.substr(0, dot_pos); + } + val += ":C"; + setenv("LANGUAGE", val.c_str(), 1); + FCITX_DEBUG() << "Fcitx LANGUAGE " << val.c_str(); + + fcitx::registerDomain(FCITX_GETTEXT_DOMAIN, + (app_contents_path / "share" / "locale").c_str()); + + // Register text domains of well-known addons. + fs::path localedir = user_prefix / "share" / "locale"; + fcitx::registerDomain("fcitx5-chinese-addons", localedir.c_str()); + fcitx::registerDomain("fcitx5-hallelujah", localedir.c_str()); + fcitx::registerDomain("fcitx5-libthai", localedir.c_str()); + fcitx::registerDomain("fcitx5-lua", localedir.c_str()); + fcitx::registerDomain("fcitx5-rime", localedir.c_str()); + fcitx::registerDomain("fcitx5-skk", localedir.c_str()); } void Fcitx::setupInstance() { @@ -152,13 +175,15 @@ bool in_fcitx_thread() noexcept { return std::this_thread::get_id() == fcitx_thread.get_id(); } -void start_fcitx_thread() noexcept { +void start_fcitx_thread(const char *locale) noexcept { bool expected = false; if (!fcitx_thread_started.compare_exchange_strong(expected, true)) { FCITX_FATAL() << "Trying to start multiple fcitx threads, which is forbidden"; std::terminate(); } + std::string locale_str = locale; + std::swap(current_locale, locale_str); auto &fcitx = Fcitx::shared(); fcitx.setup(); // Start the event loop in another thread. @@ -177,7 +202,7 @@ void stop_fcitx_thread() noexcept { void restart_fcitx_thread() noexcept { stop_fcitx_thread(); - start_fcitx_thread(); + start_fcitx_thread(current_locale.c_str()); } std::string imGetGroupNames() noexcept { diff --git a/src/locale.swift b/src/locale.swift new file mode 100644 index 0000000..c655849 --- /dev/null +++ b/src/locale.swift @@ -0,0 +1,35 @@ +// This file aims to convert locale from system to a string that fcitx5 recognizes. +// Given fcitx5 has a limited number of locales in po/, we do not need to convert all locales. +// User sets system locale in System Settings -> General -> Language & Region. +// The first language in Preferred Languages and the Region count. +// However, if the language is not commonly used in the region, it results in funny behavior. +// e.g. 简体中文 with US region, the system locale is zh-Hans_US, but we need zh_CN. +// In this situation, script is Hans (otherwise nil), and identifier = languageCode-script_regionCode +// We also need zh_SG to fall back to zh_CN. + +import Foundation +import Logging + +func getLocale() -> String { + let locale = Locale.current + FCITX_INFO("System locale = \(locale.identifier)") + + if let languageCode = locale.language.languageCode?.identifier { + if languageCode == "zh" { + if let scriptCode = locale.language.script?.identifier { + if scriptCode == "Hans" { + return "zh_CN" + } else { + return "zh_TW" + } + } + if locale.region?.identifier == "SG" { + return "zh_CN" + } else { + return "zh_TW" + } + } + return languageCode + } + return "C" +} diff --git a/src/server.swift b/src/server.swift index 7f4dcf1..3be0fa6 100644 --- a/src/server.swift +++ b/src/server.swift @@ -29,7 +29,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { // Initialize notifications. AppDelegate.notificationDelegate.requestAuthorization() - start_fcitx_thread() + let locale = getLocale() + start_fcitx_thread(locale) } func applicationWillTerminate(_ notification: Notification) { diff --git a/tests/testconfig.cpp b/tests/testconfig.cpp index 1f73413..b344311 100644 --- a/tests/testconfig.cpp +++ b/tests/testconfig.cpp @@ -5,7 +5,7 @@ #include "config/config-public.h" int main() { - start_fcitx_thread(); + start_fcitx_thread("C"); sleep(1); // Can get information about input methods. diff --git a/tests/testconfig.swift b/tests/testconfig.swift index 2d78a9c..0e307c9 100644 --- a/tests/testconfig.swift +++ b/tests/testconfig.swift @@ -5,7 +5,7 @@ import SwiftyJSON @_cdecl("main") func main() -> Int { - start_fcitx_thread() + start_fcitx_thread("C") Thread.sleep(forTimeInterval: 1) try! testGetConfigFromFcitx() try! testDecode() diff --git a/webpanel/webpanel.h b/webpanel/webpanel.h index 89b6bdd..cd1a854 100644 --- a/webpanel/webpanel.h +++ b/webpanel/webpanel.h @@ -87,7 +87,7 @@ FCITX_CONFIGURATION( _("Override default"), false}; Option sameWithLightMode{this, "SameWithLightMode", _("Same with light mode"), false}; - Option highlightColor{this, "HighlightColor", "Highlight color", + Option highlightColor{this, "HighlightColor", _("Highlight color"), Color(0, 0, 255, 255)}; Option highlightHoverColor{this, "HighlightHoverColor", _("Highlight color on hover"),