diff --git a/crowdin.yml b/crowdin.yml index 0be504ba7d8..3dd19366ef0 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,7 +1,7 @@ --- "base_path": "." "base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) -"preserve_hierarchy": false # flatten tree on crowdin +"preserve_hierarchy": true # false will flatten tree on crowdin, but doesn't work with dest option "pull_request_labels": [ "crowdin", "l10n" @@ -10,6 +10,7 @@ "files": [ { "source": "/locale/*.po", + "dest": "/%original_file_name%", "translation": "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", "languages_mapping": { "two_letters_code": { @@ -17,6 +18,13 @@ "en-GB": "en_GB", "en-US": "en_US" } - } + }, + "update_option": "update_as_unapproved" + }, + { + "source": "/src_assets/common/assets/web/public/assets/locale/en.json", + "dest": "/sunshine.json", + "translation": "/src_assets/common/assets/web/public/assets/locale/%two_letters_code%.%file_extension%", + "update_option": "update_as_unapproved" } ] diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index f29e6d05f8d..d23b96cc582 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -47,6 +47,40 @@ editing the `conf` file in a text editor. Use the examples as reference. `General `__ ----------------------------------------------------- +`locale `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + The locale used for Sunshine's user interface. + +**Choices** + +.. table:: + :widths: auto + + ======= =========== + Value Description + ======= =========== + de German + en English + en-GB English (UK) + en-US English (United States) + es Spanish + fr French + it Italian + ru Russian + sv Swedish + zh Chinese (Simplified) + ======= =========== + +**Default** + ``en`` + +**Example** + .. code-block:: text + + locale = en + `sunshine_name `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 2ca912805e1..0148b8170e4 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -30,42 +30,74 @@ localization there. Extraction ---------- -There should be minimal cases where strings need to be extracted from source code; however it may be necessary in some -situations. For example if a system tray icon is added it should be localized as it is user interfacing. -- Wrap the string to be extracted in a function as shown. - .. code-block:: cpp +.. tab:: UI - #include - #include + Sunshine uses `Vue I18n `__ for localizing the UI. + The following is a simple example of how to use it. - std::string msg = boost::locale::translate("Hello world!"); + - Add the string to `src_assets/common/assets/web/public/assets/locale/en.json`, in English. + .. code-block:: json -.. tip:: More examples can be found in the documentation for - `boost locale `__. + { + "index": { + "welcome": "Hello, Sunshine!" + } + } -.. warning:: This is for information only. Contributors should never include manually updated template files, or - manually compiled language files in Pull Requests. + .. note:: The json keys should be sorted alphabetically. You can use `jsonabc `__ + to sort the keys. -Strings are automatically extracted from the code to the `locale/sunshine.po` template file. The generated file is -used by CrowdIn to generate language specific template files. The file is generated using the -`.github/workflows/localize.yml` workflow and is run on any push event into the `nightly` branch. Jobs are only run if -any of the following paths are modified. + - Use the string in a Vue component. + .. code-block:: html -.. code-block:: yaml + - - 'src/**' + .. tip:: More formatting examples can be found in the + `Vue I18n guide `__. -When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is -required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, -`xgettext `__ must be installed. +.. tab:: C++ -**Extract, initialize, and update** - .. code-block:: bash + There should be minimal cases where strings need to be extracted from C++ source code; however it may be necessary in + some situations. For example the system tray icon could be localized as it is user interfacing. - python ./scripts/_locale.py --extract --init --update + - Wrap the string to be extracted in a function as shown. + .. code-block:: cpp -**Compile** - .. code-block:: bash + #include + #include - python ./scripts/_locale.py --compile + std::string msg = boost::locale::translate("Hello world!"); + + .. tip:: More examples can be found in the documentation for + `boost locale `__. + + .. warning:: This is for information only. Contributors should never include manually updated template files, or + manually compiled language files in Pull Requests. + + Strings are automatically extracted from the code to the `locale/sunshine.po` template file. The generated file is + used by CrowdIn to generate language specific template files. The file is generated using the + `.github/workflows/localize.yml` workflow and is run on any push event into the `nightly` branch. Jobs are only run if + any of the following paths are modified. + + .. code-block:: yaml + + - 'src/**' + + When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is + required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, + `xgettext `__ must be installed. + + **Extract, initialize, and update** + .. code-block:: bash + + python ./scripts/_locale.py --extract --init --update + + **Compile** + .. code-block:: bash + + python ./scripts/_locale.py --compile diff --git a/package.json b/package.json index b685e0966a4..706c23384ce 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "bootstrap": "5.3.3", "vite": "4.5.2", "vite-plugin-ejs": "1.6.4", - "vue": "3.4.5" + "vue": "3.4.5", + "vue-i18n": "9.10.2" } } diff --git a/src/config.cpp b/src/config.cpp index 21562f83487..b2660f7d2c1 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -441,6 +441,7 @@ namespace config { }; sunshine_t sunshine { + "en", // locale 2, // min_log_level 0, // flags {}, // User file @@ -1101,6 +1102,19 @@ namespace config { config::sunshine.flags[config::flag::UPNP].flip(); } + string_restricted_f(vars, "locale", config::sunshine.locale, { + "de"sv, // German + "en"sv, // English + "en-GB"sv, // English (UK) + "en-US"sv, // English (US) + "es"sv, // Spanish + "fr"sv, // French + "it"sv, // Italian + "ru"sv, // Russian + "sv"sv, // Swedish + "zh"sv, // Chinese + }); + std::string log_level_string; string_f(vars, "min_log_level", log_level_string); diff --git a/src/config.h b/src/config.h index 6c48f466b8e..f30e7e6a05a 100644 --- a/src/config.h +++ b/src/config.h @@ -160,6 +160,7 @@ namespace config { bool elevated; }; struct sunshine_t { + std::string locale; int min_log_level; std::bitset flags; std::string credentials_file; diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 0657902dd16..de25bf0e7cf 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -550,6 +550,24 @@ namespace confighttp { } } + void + getLocale(resp_https_t response, req_https_t request) { + // we need to return the locale whether authenticated or not + + print_req(request); + + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + + outputTree.put("status", "true"); + outputTree.put("locale", config::sunshine.locale); + } + void saveConfig(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) return; @@ -743,6 +761,7 @@ namespace confighttp { server.resource["^/api/apps$"]["POST"] = saveApp; server.resource["^/api/config$"]["GET"] = getConfig; server.resource["^/api/config$"]["POST"] = saveConfig; + server.resource["^/api/configLocale$"]["GET"] = getLocale; server.resource["^/api/restart$"]["POST"] = restart; server.resource["^/api/password$"]["POST"] = savePassword; server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; diff --git a/src_assets/common/assets/web/Navbar.vue b/src_assets/common/assets/web/Navbar.vue index 948fb4d7244..9e4e1be64f5 100644 --- a/src_assets/common/assets/web/Navbar.vue +++ b/src_assets/common/assets/web/Navbar.vue @@ -11,22 +11,22 @@ diff --git a/src_assets/common/assets/web/ResourceCard.vue b/src_assets/common/assets/web/ResourceCard.vue index e2481ca475a..aee837689cf 100644 --- a/src_assets/common/assets/web/ResourceCard.vue +++ b/src_assets/common/assets/web/ResourceCard.vue @@ -1,35 +1,32 @@