aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRalf Luther <luther.ralf@gmail.com>2020-10-18 18:00:39 +0200
committerRalf Luther <luther.ralf@gmail.com>2020-10-18 18:08:43 +0200
commitd4e2752e53966dcd7567e44cefdb4f3fe60f9970 (patch)
tree1ee68cddc32e2e1ba378662b02e9dd850487ae8d
parente35374f8186171a626f49c7e3b27158577d8d715 (diff)
crowdin: switch completely to python3
Cleanup the script and config files for Python3. Added a crowdin.xml to the platform_manifest with all our translatable repos, which makes syncing easier. So please, if a repo is added to removed, it must be changed in the platform_manifest's crowdin.xml too. Otherwise the script will fail! Change-Id: Iac26e195c3850b7c7bfa5c9f32f39e3e5142d116
-rwxr-xr-xREADME.mkdn52
-rwxr-xr-xREADME_old.mkdn124
-rw-r--r--config/q10.0.yaml298
-rw-r--r--config/q10.0.yml12
-rw-r--r--config/q10.0_extra_packages.xml1
-rwxr-xr-xcrowdin_sync.py249
-rwxr-xr-xcrowdin_sync3.py682
7 files changed, 171 insertions, 1247 deletions
diff --git a/README.mkdn b/README.mkdn
index 1886c0f..aff5e2e 100755
--- a/README.mkdn
+++ b/README.mkdn
@@ -1,4 +1,4 @@
-crowdin_sync3.py
+crowdin_sync.py
================
Introduction
@@ -6,9 +6,6 @@ Introduction
This script is forked and modified from LineageOS and CarbonROM. It is used to synchronize AICP translations with Crowdin.
It can handle automatic commiting to Gerrit, submitting open commits on Gerrit as well as pushing/downloading to/from Crowdin.
-_**Note:** To retain compatibility with the depreciated crowdin-CLI version 2.xx, this script has been renamed and requires to the renaming of the new crwodin-CLI JAR-files
-from version 3.x to "crowdin-cli3.jar"._
-
Prerequisites
-------------
1. Python version >3.6.x is needed to execute this script (as it is necessary for building the sources anyway).
@@ -24,14 +21,12 @@ Prerequisites
6. The prebuilt java version of crowdin-cli >= 3.2.x (see: https://github.com/crowdin/crowdin-cli/releases) is required for this to work.
It can be downloaded by pasting this link in a browser: https://downloads.crowdin.com/cli/v3/crowdin-cli.zip
- If you plan to use ONLY the new version, please rename the JAR-file to "crowdin-cli3.jar" for now after the installation finished.
-
*Note: The current limitation is that the JAR-file must be accessible via the path "/usr/local/bin/". You can of course change that in the python script file.*
-7. Currently the crowdin-cli tool requires either Linux or macOS and an appropiate Java version >= 1.8.xx to work.
+7. Currently the crowdin-cli tool requires either Windows10 WSL, Linux or macOS and an appropiate Java version >= 1.8.xx to work.
-8. <code>/config/AICP-version_extra_packages.xml</code> must be copied to <code>.repo/local_manifests</code> of each "AICP-version" tree.
- This makes sure you will sync all the extra packages not included in the main manifest.
+8. The script relies on the XML-file <code>platform/manifest/crowdin.xml</code> of each "AICP-version" tree.
+ This makes sure you will sync all the translatable and extra packages.
Please remember that you should comment out the packages you already have in your local aicp_manifest.xml file
as these are device dependent.
@@ -46,80 +41,79 @@ Needed directory structure:
* /home
* /your_username
* /aicp
- * /config/q10.0.yml #For the v3-version the configuration file extension changed to ".yml"
- * /config/q10.0.yaml #For the v2-version the configuration file extension is ".yaml"
+ * crowdin_sync.py #Python script for the crowdin-CLI tool
+ * /config/q10.0.yml #Configuration file for the transaltable repositories
* /q10.0
- * /.repo
- * /local_manifests/q10.0_extra_packages.xml
- * crowdin_sync.py #Python script for the version 2.x of the crowdin-CLI tool
- * crowdin_sync3.py #Python script for the version 3.x of the crowdin-CLI tool
+ * /.platform
+ * /manifest/crowdin.xml #Make sure that changes for used repositories are pushed in here too!
Enviroment variables to export:
export AICP_CROWDIN_PROJECT_ID=aicp_project-id
export AICP_CROWDIN_API_TOKEN=your_aicp_personal-access-token #must be generated by you and will be displayed ONLY ONCE afterwards!
- export AICP_CROWDIN_API_KEY=aicp_api_key
export AICP_CROWDIN_BASE_PATH_q10_0=your_base_path_and_branch
Example:
export AICP_CROWDIN_PROJECT_ID=123123 #Open project settings and go to API section on the project website
export AICP_CROWDIN_API_TOKEN=68f83--your-personal-token--... #Open profile settings and go to API & SSO > New Token > create Token
- export AICP_CROWDIN_API_KEY=54e01e81--your-api-key--f6a2724a #Can be found in your project settings page and is needed for version 2 only!
export AICP_CROWDIN_BASE_PATH_q10_0=/home/your_username/aicp/q10.0 #This is dependent on the real path to your source tree
-Execute (for v2 just use "crowdin_sync.py" instead of the described name):
+Execute:
-The python script "crowdin_sync3.py" and the "config" directory should be copied into the base folder structure, e.g. /home/your_username/aicp, like shown above.
+The python script "crowdin_sync.py" and the "config" directory should be copied into the base folder structure, e.g. /home/your_username/aicp, like shown above.
-<code>./crowdin_sync3.py --username your_gerrit_username --branch AICP_version [--upload-sources] [--upload-translations] [--download] [--local-download] [--submit]</code>
+<code>./crowdin_sync.py --username your_gerrit_username --branch AICP_version [--upload-sources] [--upload-translations] [--download] [--local-download] [--submit]</code>
The script's incorporated commandline arguments that can be invoked are:
-<pre><code>usage: crowdin_sync3.py --username USERNAME --branch BRANCH [--config CONFIG] [--upload-sources] [--upload-translations] [--download] [--local-download] [--submit]<br />
+<pre><code>usage: crowdin_sync.py --username USERNAME --branch BRANCH [--config CONFIG] [--upload-sources] [--upload-translations] [--download] [--local-download] [--submit]<br />
<br />
Synchronising AICP translations with Crowdin<br />
<br />
optional arguments:<br />
--username USERNAME Gerrit username<br />
--branch BRANCH AICP branch<br />
---config CONFIG Custom yaml config file to use<br />
+--config CONFIG Custom yml config file to use<br />
--upload-sources Upload sources to AICP Crowdin<br />
--upload-translations Upload AICP translations to Crowdin<br />
--download Download AICP translations from Crowdin<br />
--local-download Local download AICP translations from Crowdin to PC<br />
---submit Merge open AICP translations on Gerrit<br /></code></pre>
+--submit Merge open AICP translations on Gerrit<br />
+--owner Specify an owner of the commits on merging via Gerrit<br /></code></pre>
Examples:
-<code>./crowdin_sync3.py --username GerritName --branch q10.0 --upload-sources</code>
+<code>./crowdin_sync.py --username Gerrit-Username --branch q10.0 --upload-sources</code>
Will upload specified local files from the YAML-config to Crowdin. Translations already there will be preserved.
-<code>./crowdin_sync3.py --username GerritName --branch q10.0 --upload-translations</code>
+<code>./crowdin_sync.py --username Gerrit-Username --branch q10.0 --upload-translations</code>
Will upload local translations to Crowdin, based on YAML-config and from your local sources.
-<code>./crowdin_sync3.py --username GerritName --branch q10.0 --download</code>
+<code>./crowdin_sync.py --username Gerrit-Username --branch q10.0 --download</code>
Will download translations from Crowdin of the specified branch (q10.0), based on YAML-config, to your local sources,
delete empty translations and upload updated or new translations to AICP Gerrit for review.
-<code>./crowdin_sync3.py --branch q10.0 --local-download</code>
+<code>./crowdin_sync.py --branch q10.0 --local-download</code>
Will download translations from Crowdin of the specified branch (q10.0), based on YAML-config, to your local sources
and delete empty translations. This is useful to perform local builds and test the imported translations.
-<code>./crowdin_sync3.py --username GerritName --branch q10.0 --submit</code>
+<code>./crowdin_sync.py --username Gerrit-Username --branch q10.0 --submit</code>
+<code>./crowdin_sync.py --username Gerrit-Username --branch q10.0 --submit --owner "Name"</code>
Will search for open translations commits on AICP's Gerrit on the specified branch (q10.0) and
automatically review, verify and submit them into the repositories. This is useful after successful builds and requires
Gerrit Admin rights to preform this action.
+The optional "--owner" option filters the submitted files, so we can make sure that no accidental merges happen.
Notes:
------
- - The script and the crowdin-cli JAR file provide some output that make the actions performed show up
+ - The script and the crowdin-cli JAR file provide some output that show off the actions performed
in the terminal, so you can follow the execution of the commands.
- The crowdin JAR file will display a message in the terminal, if it is outdated and found a
newer version available for download. This is not an error, just a reminder for you!
diff --git a/README_old.mkdn b/README_old.mkdn
deleted file mode 100755
index 05fb05e..0000000
--- a/README_old.mkdn
+++ /dev/null
@@ -1,124 +0,0 @@
-crowdin_sync.py
-==============
-
-Introduction
-------------
-This script is forked and modified from Cyanogenmod/LineageOS and CarbonROM. It is used to synchronize AICP translations with Crowdin.
-It can handle automatic commiting to Gerrit, submitting open commits on Gerrit as well as pushing/downloading to/from Crowdin.
-
-
-Prerequisites
--------------
-1. Python version 2.7.x is needed to execute this script (as it is necessary for building the sources anyway).
-
-2. The package "python-lxml" is used for removing empty or faulty translations after download. So it needs to be installed on the system. (also see: https://lxml.de/)
-
-3. The package "python-yaml" must be installed as it is used for parsing the YAML configuration files for determining which files to push.
-
-4. The package "python-git" is used for Git integration and must be installed (also see: https://gitpython.readthedocs.io/en/stable/).
-
-5. The installs of the package "gitdb" with its dependency via <code>pip install gitdb</code> for python 2.7.x.
-
-6. The prebuilt java version of crowdin-cli >= 2.0.x (see: https://support.crowdin.com/cli-tool/) is required for this to work.
- It can be download from here: https://downloads.crowdin.com/cli/v2/crowdin-cli.zip
-
- Follow the installation instructions on the web page and set up the cli-tool correctly.
-
- *Note: The current limitation is that the JAR-file must be accessible via the path "/usr/local/bin/". You can of course change that in the python script file.*
-
-7. Currently the crowdin-cli tool requires either Linux or macOS and an appropiate Java version >= 1.8.xx to work.
-
-8. <code>/config/AICP-version_extra_packages.xml</code> must be copied to <code>.repo/local_manifests</code> of each "AICP-version" tree.
- This makes sure you will sync all the extra packages not included in the main manifest.
- Please remember that you should comment out the packages you already have in your local aicp_manifest.xml file
- as these are device dependent.
-
-
-Executing
----------
-Export the needed environment variables to set the API key and the base path to your **.bashrc** or **.bash-profile**.
-The base path should contain all AICP trees in subfolders, named after AICP branches.
-
-Needed directory structure:
-
-* /home
-* /your_username
- * /aicp
- * /config/.yaml #all YAML config files go in here!
- * /q10.0
- * /.repo
- * /local_manifests/q10.0_extra_packages.xml
- * crowdin_sync.py
-
-Enviroment variables to export:
-
- export AICP_CROWDIN_API_KEY=aicp_api_key
- export AICP_CROWDIN_BASE_PATH_q10_0=your_base_path_and_branch
-
-Example:
-
- export AICP_CROWDIN_API_KEY=54e01e81--your-api-key--f6a2724a #Can be found in your project settings page!
- export AICP_CROWDIN_BASE_PATH_q10_0=/home/your_username/aicp/q10.0 #This is dependent on the real path to your source tree
-
-Execute:
-The python script "crowdin_sync.py" and the "config" directory should be copied into the base folder structure, e.g. /home/your_username/aicp, like shown above.
-
-<code>./crowdin_sync.py --username your_gerrit_username --branch AICP_version [--upload-sources] [--upload-translations] [--download] [--local-download] [--submit]</code>
-
-The script incorporates also a little help that can be invoked by executing:
-
-<code>./crowdin_sync.py --help</code>
-
-It will display the following:
-
-<pre><code>usage: crowdin_sync.py [--help] --username USERNAME --branch BRANCH [--config CONFIG] [--upload-sources] [--upload-translations] [--download] [--local-download] [--submit]<br />
-<br />
-Synchronising AICP translations with Crowdin<br />
-<br />
-optional arguments:<br />
---help show this help message and exit<br />
---username USERNAME Gerrit username<br />
---branch BRANCH AICP branch<br />
--c CONFIG, --config CONFIG Custom yaml config file to use<br />
---upload-sources Upload sources to AICP Crowdin<br />
---upload-translations Upload AICP translations to Crowdin<br />
---download Download AICP translations from Crowdin<br />
---local-download Local download AICP translations from Crowdin to PC<br />
---submit Merge open AICP translations on GerritName<br /></code></pre>
-
-Examples:
-
-<code>./crowdin_sync.py --username GerritName --branch q10.0 --upload-sources</code>
-
-Will upload specified local files from the YAML-config to Crowdin. Translations already there will be preserved.
-
-<code>./crowdin_sync.py --username GerritName --branch q10.0 --upload-translations</code>
-
-Will upload local translations to Crowdin, based on YAML-config and from your local sources.
-
-<code>./crowdin_sync.py --username GerritName --branch q10.0 --download</code>
-
-Will download translations from Crowdin of the specified branch (q10.0), based on YAML-config, to your local sources,
-delete empty translations and upload updated or new translations to AICP Gerrit for review.
-
-<code>./crowdin_sync.py --branch q10.0 --local-download</code>
-
-Will download translations from Crowdin of the specified branch (q10.0), based on YAML-config, to your local sources
-and delete empty translations. This is useful to perform local builds and test the imported translations.
-
-<code>./crowdin_sync.py --username GerritName --branch q10.0 --submit</code>
-
-Will search for open translations commits on AICP's Gerrit on the specified branch (q10.0) and
-automatically review, verify and submit them into the repositories. This is useful after successful builds and requires
-Gerrit Admin rights to preform this action.
-
-
-Notes:
-------
- - The script and the crowdin-cli JAR file provide some output that make the actions performed show up
- in the terminal, so you can follow the execution of the commands.
- - The crowdin JAR file will display a message in the terminal, if it is outdated and found a
- newer version available for download. This is not an error, just a reminder for you!
- - When committing a translation fails, the reason of it cannot be determined everytime. The script will
- simply display the message "Nothing to commit" or an error message mentioning the specific file/commit.
- The script will continue when this happens and display "Finished! Nothing to do or commit anymore.".
diff --git a/config/q10.0.yaml b/config/q10.0.yaml
deleted file mode 100644
index 5ce365b..0000000
--- a/config/q10.0.yaml
+++ /dev/null
@@ -1,298 +0,0 @@
-# q10.0.yaml
-#
-# Crowdin configuration file for AICP
-#
-# Copyright (C) 2014-2016 The CyanogenMod Project
-# Copyright (C) 2017-2019 The LineageOS Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Your crowdin's credentials
-api_key_env: AICP_CROWDIN_API_KEY
-base_path_env: AICP_CROWDIN_BASE_PATH_q10_0
-project_identifier: aicp
-preserve_hierarchy: true
-
-files:
-# Frameworks
-
- # frameworks-res
- -
- source: '/frameworks/base/core/res/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: &anchor
- android_code:
- # we need this mapping since Crowdin expects directories
- # to be named like "values-uk-rUA"
- # acording to specification instead of just "uk"
- af: af
- am: am
- ar: ar
- as: as-rIN
- ast: ast-rES
- az: az-rAZ
- be: be-rBY
- bg: bg
- bn: bn-rBD
- bs: bs-rBA
- ca: ca
- cs: cs
- cy: cy
- da: da
- de: de
- el: el
- en-AU: en-rAU
- en-GB: en-rGB
- en-IN: en-rIN
- en-PT: en-rPT
- eo: eo
- es-ES: es
- es-MX: es-rMX
- es-US: es-rUS
- et: et-rEE
- eu: eu-rES
- fa: fa
- fi: fi
- fr: fr
- fr-CA: fr-rCA
- fy-NL: fy-rNL
- gl: gl-rES
- gu-IN: gu-rIN
- he: iw
- hi: hi
- hr: hr
- hu: hu
- hy-AM: hy-rAM
- id: in
- is: is-rIS
- it: it
- ja: ja
- ka: ka-rGE
- kk: kk-rKZ
- km: km-rKH
- kn: kn-rIN
- ko: ko
- ku: ku
- ky: ky-rKG
- lb: lb
- lo: lo-rLA
- lt: lt
- lv: lv
- mk: mk-rMK
- ml-IN: ml-rIN
- mn: mn-rMN
- mr: mr-rIN
- ms: ms-rMY
- my: my-rMM
- nb: nb
- ne-NP: ne-rNP
- nl: nl
- or: or-rIN
- pa-IN: pa-rIN
- pl: pl
- pt-PT: pt-rPT
- pt-BR: pt-rBR
- rm-CH: rm
- ro: ro
- ru: ru
- si-LK: si-rLK
- sk: sk
- sl: sl
- sq: sq-rAL
- sr: sr
- sv-SE: sv
- sw: sw
- ta: ta-rIN
- te: te-rIN
- th: th
- tl: tl
- tr: tr
- ug: ug
- uk: uk
- ur-PK: ur-rPK
- uz: uz-rUZ
- vi: vi
- zh-CN: zh-rCN
- zh-HK: zh-rHK
- zh-TW: zh-rTW
- zu: zu
-
- # frameworks-opt-slimrecent
- -
- source: '/frameworks/opt/slimrecent/res/values/strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # frameworks-opt-aicpgear
- -
- source: '/frameworks/opt/aicpgear/preference/res/values/color_picker_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/frameworks/opt/aicpgear/preference/res/values/seekbar_preference_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # frameworks-base-packages-SettingsLib
- -
- source: '/frameworks/base/packages/SettingsLib/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # SystemUI
- -
- source: '/frameworks/base/packages/SystemUI/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/frameworks/base/packages/SystemUI/res-keyguard/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # PackageInstaller
- -
- source: '/frameworks/base/packages/PackageInstaller/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
-# Packages
-
- # AicpExtras
- -
- source: '/packages/apps/AicpExtras/res/values/strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/packages/apps/AicpExtras/res/values/changelog_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/packages/apps/AicpExtras/res/values/dslv_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/packages/apps/AicpExtras/res/values/master_switch_preference_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/packages/apps/AicpExtras/res/values/stats_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/packages/apps/AicpExtras/res/values/switch_bar_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # Updater
- -
- source: '/packages/apps/Updater/res/values/strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # Dialer
- -
- source: '/packages/apps/Dialer/java/com/android/dialer/app/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/packages/apps/Dialer/java/com/android/dialer/lookup/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # JamesDSPManager
- -
- source: '/packages/apps/JamesDSPManager/app/src/main/res/values/strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/packages/apps/JamesDSPManager/app/src/main/res/values/arrays.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
-# # FMRadio
-# -
-# source: '/packages/apps/FMRadio/res/values/cm_strings.xml'
-# translation: '/%original_path%-%android_code%/%original_file_name%'
-# languages_mapping: *anchor
-# -
-# source: '/packages/apps/FMRadio/res/values/strings.xml'
-# translation: '/%original_path%-%android_code%/%original_file_name%'
-# languages_mapping: *anchor
-
- # Launcher3
- -
- source: '/packages/apps/Launcher3/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # Settings
- -
- source: '/packages/apps/Settings/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
-# # SmartNavigation
-# -
-# source: '/packages/apps/SmartNav/res/values/strings.xml'
-# translation: '/%original_path%-%android_code%/%original_file_name%'
-# languages_mapping: *anchor
-
-# Providers
-
-# # MediaProvider
-# -
-# source: '/packages/providers/MediaProvider/res/values/aicp_strings.xml'
-# translation: '/%original_path%-%android_code%/%original_file_name%'
-# languages_mapping: *anchor
-
-# Services
-
- # OmniJaws
- -
- source: '/packages/services/OmniJaws/res/values/strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
- -
- source: '/packages/services/OmniJaws/res/values/aicp_strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
-# Device specific
-
-# # device_oppo_common DeviceHandler for OnePlus
-# -
-# source: '/device/oppo/common/DeviceHandler/res/values/strings.xml'
-# translation: '/%original_path%-%android_code%/%original_file_name%'
-# languages_mapping: *anchor
-# -
-# source: '/device/oppo/common/overlay/packages/apps/Settings/res/values/devicehandlerstrings.xml'
-# translation: '/%original_path%-%android_code%/%original_file_name%'
-# languages_mapping: *anchor
-
- # DeviceSettings
- -
- source: '/packages/resources/devicesettings/res/values/strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # DeviceParts
- -
- source: '/packages/resources/deviceparts/res/values/strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
-
- # packages_apps_FlipFlap
- -
- source: '/packages/apps/FlipFlap/res/values/strings.xml'
- translation: '/%original_path%-%android_code%/%original_file_name%'
- languages_mapping: *anchor
diff --git a/config/q10.0.yml b/config/q10.0.yml
index d3bae7d..c532dd7 100644
--- a/config/q10.0.yml
+++ b/config/q10.0.yml
@@ -237,18 +237,6 @@
"languages_mapping": *anchor
},
- # FMRadio
- {
- "source": "/packages/apps/FMRadio/res/values/cm_strings.xml",
- "translation": "/packages/apps/FMRadio/res/values-%android_code%/%original_file_name%",
- "languages_mapping": *anchor
- },
- {
- "source": "/packages/apps/FMRadio/res/values/strings.xml",
- "translation": "/packages/apps/FMRadio/res/values-%android_code%/%original_file_name%",
- "languages_mapping": *anchor
- },
-
# Launcher3
{
"source": "/packages/apps/Launcher3/res/values/aicp_strings.xml",
diff --git a/config/q10.0_extra_packages.xml b/config/q10.0_extra_packages.xml
index 562ac5f..cc95fb6 100644
--- a/config/q10.0_extra_packages.xml
+++ b/config/q10.0_extra_packages.xml
@@ -6,6 +6,5 @@
<project path="device/oppo/common" name="AICP/device_oppo_common" />
<project path="packages/apps/FlipFlap" name="AICP/packages_apps_FlipFlap" />
<project path="packages/apps/MusicFX" name="AICP/packages_apps_MusicFX" />
- <project path="packages/apps/FMRadio" name="AICP/packages_apps_FMRadio" />
<project path="packages/apps/JamesDSPManager" name="AICP/packages_apps_JamesDSPManager" />
</manifest>
diff --git a/crowdin_sync.py b/crowdin_sync.py
index a6a26a9..a84bfc1 100755
--- a/crowdin_sync.py
+++ b/crowdin_sync.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# crowdin_sync.py
#
@@ -23,8 +23,6 @@
# ################################# IMPORTS ################################## #
-from __future__ import print_function
-
import argparse
import json
import git
@@ -138,14 +136,22 @@ def clean_xml_file(base_path, project_path, filename):
try:
fh = open(path, 'r+')
except:
- print('Something went wrong while opening file %s' % (path))
+ print(f'\nSomething went wrong while opening file {path}')
return
XML = fh.read()
+ content = ''
+
+ # Take the original xml declaration and prepend it
+ declaration = XML.split('\n')[0]
+ if '<?' in declaration:
+ content = declaration + '\n'
+ XML = XML[XML.find('\n')+1:]
+
try:
tree = etree.fromstring(XML)
except etree.XMLSyntaxError as err:
- print('%s: XML Error: %s' % (filename, err.error_log))
+ print(f'{filename}: XML Error: {err.error_log}')
filename, ext = os.path.splitext(path)
if ext == '.xml':
reset_file(path, repo)
@@ -173,8 +179,8 @@ def clean_xml_file(base_path, project_path, filename):
# Every occurance of the string has to be removed when no string with the same name and
# 'product=default' (or no product attribute) was found
if not hasProductDefault:
- print("\n{0}: Found string '{1}' with missing 'product=default' attribute"
- .format(path, stringName), end='')
+ print(f"\n{path}: Found string '{stringName}' with missing 'product=default' attribute",
+ end='')
for string in stringsWithSameName:
tree.remove(string)
alreadyRemoved.append(string)
@@ -189,14 +195,12 @@ def clean_xml_file(base_path, project_path, filename):
continue
p.remove(c)
- content = ''
-
# Take the original xml declaration and prepend it
declaration = XML.split('\n')[0]
if '<?' in declaration:
content = declaration + '\n'
- content += etree.tostring(tree, pretty_print=True, encoding="utf-8", xml_declaration=False)
+ content += etree.tostring(tree, pretty_print=True, encoding="unicode", xml_declaration=False)
if header != '':
content = content.replace('?>\n', '?>\n' + header)
@@ -213,7 +217,7 @@ def clean_xml_file(base_path, project_path, filename):
# Remove files which don't have any translated strings
contentList = list(tree)
if len(contentList) == 0:
- print('\nRemoving ' + path)
+ print(f'\nRemoving {path}')
os.remove(path)
@@ -246,7 +250,8 @@ def reset_file(filepath, repo):
def push_as_commit(config_files, base_path, path, name, branch, username):
- print('\nCommitting %s on branch %s: ' % (name, branch), end='')
+ global _COMMITS_CREATED
+ print(f'\nCommitting {name} on branch {branch}: ', end='')
# Get path
project_path = path
@@ -273,30 +278,37 @@ def push_as_commit(config_files, base_path, path, name, branch, username):
# Push commit
try:
- repo.git.push('ssh://%s@gerrit.aicp-rom.com:29418/%s' % (username, name),
- 'HEAD:refs/for/%s%%topic=Translations-%s' % (branch, branch))
+ repo.git.push(f'ssh://{username}@gerrit.aicp-rom.com:29418/{name}',
+ f'HEAD:refs/for/{branch}', '-o', f'topic=Translations-{branch}')
print('Success')
- except:
- print('Failed', file=sys.stderr)
+ except Exception as e:
+ print(e, '\nFailed to push!', file=sys.stderr)
+ return
_COMMITS_CREATED = True
-def submit_gerrit(branch, username):
+def submit_gerrit(branch, username, owner):
+ # If an owner is specified, modify the query so we only get the ones wanted
+ ownerArg = ''
+ if owner is not None:
+ ownerArg = f'owner:{owner}'
+
# Find all open translation changes
cmd = ['ssh', '-p', '29418',
- '{}@gerrit.aicp-rom.com'.format(username),
+ f'{username}@gerrit.aicp-rom.com',
'gerrit', 'query',
'status:open',
- 'branch:{}'.format(branch),
+ f'branch:{branch}',
+ ownerArg,
'message:"Automatic AICP translation import"',
- 'topic:Translations-{}'.format(branch),
+ f'topic:Translations-{branch}',
'--current-patch-set',
'--format=JSON']
commits = 0
msg, code = run_subprocess(cmd)
if code != 0:
- print('Failed: {0}'.format(msg[1]))
+ print(f'Failed: {msg[1]}')
return
# Each line is one valid JSON object, except the last one, which is empty
@@ -307,7 +319,7 @@ def submit_gerrit(branch, username):
continue
# Add Code-Review +2 and Verified +1 labels and submit
cmd = ['ssh', '-p', '29418',
- '{}@gerrit.aicp-rom.com'.format(username),
+ f'{username}@gerrit.aicp-rom.com',
'gerrit', 'review',
'--verified +1',
'--code-review +2',
@@ -316,7 +328,7 @@ def submit_gerrit(branch, username):
print('Submitting commit %s: ' % js['url'], end='')
if code != 0:
errorText = msg[1].replace('\n\n', '; ').replace('\n', '')
- print('Failed: %s' % errorText)
+ print(f'Failed: {errorText}')
else:
print('Success')
@@ -331,7 +343,8 @@ def check_run(cmd):
p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
ret = p.wait()
if ret != 0:
- print('Failed to run cmd: %s' % ' '.join(cmd), file=sys.stderr)
+ joined = ' '.join(cmd)
+ print(f'Failed to run cmd: {joined}', file=sys.stderr)
sys.exit(ret)
@@ -361,6 +374,8 @@ def parse_args():
help='Locally download AICP translations from Crowdin')
parser.add_argument('--submit', action='store_true',
help='Auto-Merge open AICP translations on Gerrit')
+ parser.add_argument('-o', '--owner',
+ help='Specify the owner of the commits to submit')
return parser.parse_args()
# ################################# PREPARE ################################## #
@@ -379,69 +394,80 @@ def load_xml(x):
try:
return etree.parse(x)
except etree.XMLSyntaxError:
- print('Malformed %s.' % x, file=sys.stderr)
+ print(f'Malformed {x}', file=sys.stderr)
return None
except Exception:
- print('You have no %s.' % x, file=sys.stderr)
+ print(f'You have no {x}', file=sys.stderr)
return None
def check_files(files):
for f in files:
if not os.path.isfile(f):
- print('You have no %s.' % f, file=sys.stderr)
+ print(f'You have no {f}.', file=sys.stderr)
return False
return True
# ################################### MAIN ################################### #
-def upload_sources_crowdin(branch, config):
+def upload_sources_crowdin(project_id, branch, config):
if config:
print('\nUploading sources to Crowdin (custom config)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar',
- '--config=%s/config/%s' % (_DIR, config),
- 'upload', 'sources', '--branch=%s' % branch])
+ check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar', 'upload', 'sources',
+ f'--project-id={project_id}',
+ f'--branch={branch}',
+ '--auto-update',
+ f'--config={_DIR}/config/{config}'])
else:
print('\nUploading sources to Crowdin (AOSP supported languages)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar',
- '--config=%s/config/%s.yaml' % (_DIR, branch),
- 'upload', 'sources', '--branch=%s' % branch])
+ check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar', 'upload', 'sources',
+ f'--project-id={project_id}',
+ f'--branch={branch}',
+ '--auto-update',
+ f'--config={_DIR}/config/{branch}.yml'])
-def upload_translations_crowdin(branch, config):
+def upload_translations_crowdin(project_id, branch, config):
if config:
print('\nUploading translations to Crowdin (custom config)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar',
- '--config=%s/config/%s' % (_DIR, config),
- 'upload', 'translations', '--branch=%s' % branch,
- '--no-import-duplicates', '--import-eq-suggestions',
- '--auto-approve-imported'])
+ check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar', 'upload', 'translations',
+ f'--project-id={project_id}',
+ f'--branch={branch}',
+ '--import-eq-suggestions',
+ '--auto-approve-imported',
+ f'--config={_DIR}/config/{config}'])
else:
print('\nUploading translations to Crowdin '
'(AOSP supported languages)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar',
- '--config=%s/config/%s.yaml' % (_DIR, branch),
- 'upload', 'translations', '--branch=%s' % branch,
- '--no-import-duplicates', '--import-eq-suggestions',
- '--auto-approve-imported'])
+ check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar', 'upload', 'translations',
+ f'--project-id={project_id}',
+ f'--branch={branch}',
+ '--import-eq-suggestions',
+ '--auto-approve-imported',
+ f'--config={_DIR}/config/{branch}.yml'])
-def local_download(base_path, branch, xml, config):
+def local_download(project_id, base_path, branch, xml, config):
if config:
print('\nDownloading translations from Crowdin (custom config)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar',
- '--config=%s/config/%s' % (_DIR, config),
- 'download', '--branch=%s' % branch])
+ check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar', 'download',
+ f'--project-id={project_id}',
+ f'--branch={branch}',
+ '--skip-untranslated-strings',
+ '--export-only-approved',
+ f'--config={_DIR}/config/{config}'])
else:
print('\nDownloading translations from Crowdin '
'(AOSP supported languages)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar',
- '--config=%s/config/%s.yaml' % (_DIR, branch),
- 'download', '--branch=%s' % branch])
-
-
- print('\nRemoving useless empty translation files')
+ check_run(['java', '-jar', '/usr/local/bin/crowdin-cli.jar', 'download',
+ f'--project-id={project_id}',
+ f'--branch={branch}',
+ '--skip-untranslated-strings',
+ '--export-only-approved',
+ f'--config={_DIR}/config/{branch}.yml'])
+
+ print('\nRemoving useless empty translation files (AOSP supported languages)')
empty_contents = {
'<resources/>',
'<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>',
@@ -450,37 +476,66 @@ def local_download(base_path, branch, xml, config):
('<resources xmlns:android="http://schemas.android.com/apk/res/android"'
' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>'),
('<resources xmlns:tools="http://schemas.android.com/tools"'
- ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>')
+ ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>'),
+ ('<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\n</resources>'),
+ ('<resources xmlns:android='
+ '"http://schemas.android.com/apk/res/android">\n</resources>'),
+ ('<resources xmlns:android="http://schemas.android.com/apk/res/android"'
+ ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\n</resources>'),
+ ('<resources xmlns:tools="http://schemas.android.com/tools"'
+ ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\n</resources>'),
+ ('<resources>\n</resources>')
}
+
xf = None
- for xml_file in find_xml(base_path):
- xf = open(xml_file).read()
- for line in empty_contents:
- if line in xf:
- print('Removing ' + xml_file)
- os.remove(xml_file)
- break
+ cmd = ['java', '-jar', '/usr/local/bin/crowdin-cli.jar', 'list', 'translations',
+ f'--project-id={project_id}',
+ '--plain',
+ f'--config={_DIR}/config/{branch}.yml']
+ comm, ret = run_subprocess(cmd)
+ if ret != 0:
+ sys.exit(ret)
+ # Split in list and remove last empty entry
+ xml_list=str(comm[0]).split("\n")[:-1]
+ for xml_file in xml_list:
+ try:
+ print("Checking: " + base_path + '/' + xml_file)
+ tree = etree.XML(open(base_path + '/' + xml_file).read().encode())
+ etree.strip_tags(tree,etree.Comment)
+ treestring = etree.tostring(tree,encoding='UTF-8')
+ xf = "".join([s for s in treestring.decode().strip().splitlines(True) if s.strip()])
+ for line in empty_contents:
+ if line in xf:
+ print("Removing: " + base_path + '/' + xml_file)
+ os.remove(base_path + '/' + xml_file)
+ break
+ except IOError:
+ print("File not found: " + xml_file)
+ sys.exit(1)
+ except etree.XMLSyntaxError:
+ print("XML Syntax error in file: " + xml_file)
+ sys.exit(1)
del xf
-def download_crowdin(base_path, branch, xml, username, config):
- local_download(base_path, branch, xml, config)
+def download_crowdin(project_id, base_path, branch, xml, username, config):
+ local_download(project_id, base_path, branch, xml, config)
print('\nCreating a list of pushable translations')
# Get all files that Crowdin pushed
paths = []
if config:
- files = [('%s/config/%s' % (_DIR, config))]
+ files = [f'{_DIR}/config/{config}']
else:
- files = [('%s/config/%s.yaml' % (_DIR, branch))]
+ files = [f'{_DIR}/config/{branch}.yml']
for c in files:
- cmd = ['java', '-jar', '/usr/local/bin/crowdin-cli.jar',
- '--config=%s' % c, 'list', 'project', '--branch=%s' % branch]
+ cmd = ['java', '-jar', '/usr/local/bin/crowdin-cli.jar', 'list', 'sources',
+ f'--branch={branch}', f'--config={c}', '--plain']
comm, ret = run_subprocess(cmd)
if ret != 0:
sys.exit(ret)
for p in str(comm[0]).split("\n"):
- paths.append(p.replace('/%s' % branch, ''))
+ paths.append(p.replace(f'/{branch}', ''))
print('\nUploading translations to AICP Gerrit')
items = [x for xmlfile in xml for x in xmlfile.findall("//project")]
@@ -492,8 +547,7 @@ def download_crowdin(base_path, branch, xml, username, config):
continue
if "/res" not in path:
- print('WARNING: Cannot determine project root dir of '
- '[%s], skipping.' % path)
+ print(f'WARNING: Cannot determine project root dir of [{path}], skipping.')
continue
# Usually the project root is everything before /res
@@ -504,13 +558,12 @@ def download_crowdin(base_path, branch, xml, username, config):
elif len(parts) == 3:
result = parts[0] + '/res' + parts[1]
else:
- print('WARNING: Splitting the path not successful for [%s], skipping' % path)
+ print(f'WARNING: Splitting the path not successful for [{path}], skipping')
continue
result = result.strip('/')
if result == path.strip('/'):
- print('WARNING: Cannot determine project root dir of '
- '[%s], skipping.' % path)
+ print(f'WARNING: Cannot determine project root dir of [{path}], skipping.')
continue
if result in all_projects:
@@ -559,6 +612,7 @@ def sig_handler(signal_received, frame):
def main():
+ global _COMMITS_CREATED
signal(SIGINT, sig_handler)
args = parse_args()
default_branch = args.branch
@@ -567,47 +621,40 @@ def main():
if args.username is None:
print('Argument -u/--username is required for submitting!')
sys.exit(1)
- submit_gerrit(default_branch, args.username)
+ submit_gerrit(default_branch, args.username, args.owner)
sys.exit(0)
+ project_id_env = 'AICP_CROWDIN_PROJECT_ID'
+ project_id = os.getenv(project_id_env)
+
base_path_branch_suffix = default_branch.replace('.', '_')
- base_path_env = 'AICP_CROWDIN_BASE_PATH_%s' % base_path_branch_suffix
+ base_path_env = f'AICP_CROWDIN_BASE_PATH_{base_path_branch_suffix}'
base_path = os.getenv(base_path_env)
if base_path is None:
cwd = os.getcwd()
- print('You have not set %s. Defaulting to %s' % (base_path_env, cwd))
+ print(f'You have not set {base_path_env}. Defaulting to {cwd}')
base_path = cwd
if not os.path.isdir(base_path):
- print('%s is not a real directory: %s' % (base_path_env, base_path))
+ print(f'{base_path_env} is not a real directory: {base_path}')
sys.exit(1)
if not check_dependencies():
sys.exit(1)
- xml_default = load_xml(x='%s/platform_manifest/default.xml' % base_path)
+ xml_default = load_xml(x=f'{base_path}/platform_manifest/crowdin.xml')
if xml_default is None:
sys.exit(1)
-# xml_aosp = load_xml(x='%s/platform_manifest/aicp-aosp.xml' % base_path)
-# if xml_aosp is None:
-# sys.exit(1)
-
- xml_extra = load_xml(x='%s/config/%s_extra_packages.xml' % (_DIR, default_branch))
- if xml_extra is None:
- sys.exit(1)
-
- xml_aicp = load_xml(x='%s/platform_manifest/aicp-default.xml' % base_path)
- if xml_aicp is not None:
-# xml_files = (xml_default, xml_aosp, xml_aicp, xml_extra)
- xml_files = (xml_default, xml_aicp, xml_extra)
- else:
-# xml_files = (xml_default, xml_aosp, xml_extra)
+ xml_extra = load_xml(x=f'{_DIR}/config/{default_branch}_extra_packages.xml')
+ if xml_extra is not None:
xml_files = (xml_default, xml_extra)
+ else:
+ xml_files = (xml_default)
if args.config:
- files = [('%s/config/%s' % (_DIR, args.config))]
+ files = [f'{_DIR}/config/{args.config}']
else:
- files = [('%s/config/%s.yaml' % (_DIR, default_branch))]
+ files = [f'{_DIR}/config/{default_branch}.yml']
if not check_files(files):
sys.exit(1)
@@ -616,23 +663,23 @@ def main():
sys.exit(1)
if args.upload_sources:
- upload_sources_crowdin(default_branch, args.config)
+ upload_sources_crowdin(project_id, default_branch, args.config)
if args.upload_translations:
- upload_translations_crowdin(default_branch, args.config)
+ upload_translations_crowdin(project_id, default_branch, args.config)
if args.local_download:
- local_download(base_path, default_branch, xml_files, args.config)
+ local_download(project_id, base_path, default_branch, xml_files, args.config)
if args.download:
- download_crowdin(base_path, default_branch, xml_files, args.username, args.config)
+ download_crowdin(project_id, base_path, default_branch, xml_files, args.username, args.config)
if _COMMITS_CREATED:
print('\nDone!')
sys.exit(0)
else:
print('\nFinished! Nothing to do or commit anymore.')
- sys.exit(-1)
+ sys.exit(2)
if __name__ == '__main__':
main()
diff --git a/crowdin_sync3.py b/crowdin_sync3.py
deleted file mode 100755
index bacc3c5..0000000
--- a/crowdin_sync3.py
+++ /dev/null
@@ -1,682 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-# crowdin_sync.py
-#
-# Updates Crowdin source translations and pushes translations
-# directly to AICP Gerrit.
-#
-# Copyright (C) 2014-2016 The CyanogenMod Project
-# Copyright (C) 2017-2020 The LineageOS Project
-# This code has been modified.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# ################################# IMPORTS ################################## #
-
-import argparse
-import json
-import git
-import os
-import re
-import shutil
-import subprocess
-import sys
-import yaml
-
-from lxml import etree
-from signal import signal, SIGINT
-
-# ################################# GLOBALS ################################## #
-
-_DIR = os.path.dirname(os.path.realpath(__file__))
-_COMMITS_CREATED = False
-
-# ################################ FUNCTIONS ################################# #
-
-
-def run_subprocess(cmd, silent=False):
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- universal_newlines=True)
- comm = p.communicate()
- exit_code = p.returncode
- if exit_code != 0 and not silent:
- print("There was an error running the subprocess.\n"
- "cmd: %s\n"
- "exit code: %d\n"
- "stdout: %s\n"
- "stderr: %s" % (cmd, exit_code, comm[0], comm[1]),
- file=sys.stderr)
- return comm, exit_code
-
-
-def add_target_paths(config_files, repo, base_path, project_path):
- # Add or remove the files given in the config files to the commit
- count = 0
- file_paths = []
- for f in config_files:
- fh = open(f, "r")
- try:
- config = yaml.safe_load(fh)
- for tf in config['files']:
- if project_path in tf['source']:
- target_path = tf['translation']
- lang_codes = tf['languages_mapping']['android_code']
- for l in lang_codes:
- lpath = get_target_path(tf['translation'], tf['source'],
- lang_codes[l], project_path)
- file_paths.append(lpath)
- except yaml.YAMLError as e:
- print(e, '\n Could not parse YAML.')
- exit()
- fh.close()
-
- # Strip all comments
- for f in file_paths:
- clean_xml_file(base_path, project_path, f)
-
- # Modified and untracked files
- modified = repo.git.ls_files(m=True, o=True)
- for m in modified.split('\n'):
- if m in file_paths:
- repo.git.add(m)
- count += 1
-
- deleted = repo.git.ls_files(d=True)
- for d in deleted.split('\n'):
- if d in file_paths:
- repo.git.rm(d)
- count += 1
-
- return count
-
-
-def split_path(path):
- # Split the given string to path and filename
- if '/' in path:
- original_file_name = path[1:][path.rfind("/"):]
- original_path = path[:path.rfind("/")]
- else:
- original_file_name = path
- original_path = ''
-
- return original_path, original_file_name
-
-
-def get_target_path(pattern, source, lang, project_path):
- # Make strings like '/%original_path%-%android_code%/%original_file_name%' valid file paths
- # based on the source string's path
- original_path, original_file_name = split_path(source)
-
- target_path = pattern #.lstrip('/')
- target_path = target_path.replace('%original_path%', original_path)
- target_path = target_path.replace('%android_code%', lang)
- target_path = target_path.replace('%original_file_name%', original_file_name)
- target_path = target_path.replace(project_path, '')
- target_path = target_path.lstrip('/')
- return target_path
-
-
-def clean_xml_file(base_path, project_path, filename):
- path = base_path + '/' + project_path + '/' + filename
-
- # We don't want to create every file, just work with those already existing
- if not os.path.isfile(path):
- return
-
- try:
- fh = open(path, 'r+')
- except:
- print(f'\nSomething went wrong while opening file {path}')
- return
-
- XML = fh.read()
- content = ''
-
- # Take the original xml declaration and prepend it
- declaration = XML.split('\n')[0]
- if '<?' in declaration:
- content = declaration + '\n'
- XML = XML[XML.find('\n')+1:]
-
- try:
- tree = etree.fromstring(XML)
- except etree.XMLSyntaxError as err:
- print(f'{filename}: XML Error: {err.error_log}')
- filename, ext = os.path.splitext(path)
- if ext == '.xml':
- reset_file(path, repo)
- return
-
- # Remove strings with 'product=*' attribute but no 'product=default'
- # This will ensure aapt2 will not throw an error when building these
- productStrings = tree.xpath("//string[@product]")
- alreadyRemoved = []
- for ps in productStrings:
- # if we already removed the items, don't process them
- if ps in alreadyRemoved:
- continue
- stringName = ps.get('name')
- stringsWithSameName = tree.xpath("//string[@name='{0}']".format(stringName))
-
- # We want to find strings with product='default' or no product attribute at all
- hasProductDefault = False
- for string in stringsWithSameName:
- product = string.get('product')
- if product is None or product == 'default':
- hasProductDefault = True
- break
-
- # Every occurance of the string has to be removed when no string with the same name and
- # 'product=default' (or no product attribute) was found
- if not hasProductDefault:
- print(f"\n{path}: Found string '{stringName}' with missing 'product=default' attribute",
- end='')
- for string in stringsWithSameName:
- tree.remove(string)
- alreadyRemoved.append(string)
-
- header = ''
- comments = tree.xpath('//comment()')
- for c in comments:
- p = c.getparent()
- if p is None:
- # Keep all comments in header
- header += str(c).replace('\\n', '\n').replace('\\t', '\t') + '\n'
- continue
- p.remove(c)
-
- # Take the original xml declaration and prepend it
- declaration = XML.split('\n')[0]
- if '<?' in declaration:
- content = declaration + '\n'
-
- content += etree.tostring(tree, pretty_print=True, encoding="unicode", xml_declaration=False)
-
- if header != '':
- content = content.replace('?>\n', '?>\n' + header)
-
- # Sometimes spaces are added, we don't want them
- content = re.sub("[ ]*<\/resources>", "</resources>", content)
-
- # Overwrite file with content stripped by all comments
- fh.seek(0)
- fh.write(content)
- fh.truncate()
- fh.close()
-
- # Remove files which don't have any translated strings
- contentList = list(tree)
- if len(contentList) == 0:
- print(f'\nRemoving {path}')
- os.remove(path)
-
-
-# For files we can't process due to errors, create a backup
-# and checkout the file to get it back to the previous state
-def reset_file(filepath, repo):
- backupFile = None
- parts = filepath.split("/")
- found = False
- for s in parts:
- curPart = s
- if not found and s.startswith("res"):
- curPart = s + "_backup"
- found = True
- if backupFile is None:
- backupFile = curPart
- else:
- backupFile = backupFile + '/' + curPart
-
- path, filename = os.path.split(backupFile)
- if not os.path.exists(path):
- os.makedirs(path)
- if os.path.exists(backupFile):
- i = 1
- while os.path.exists(backupFile + str(i)):
- i+=1
- backupFile = backupFile + str(i)
- shutil.copy(filepath, backupFile)
- repo.git.checkout(filepath)
-
-
-def push_as_commit(config_files, base_path, path, name, branch, username):
- print(f'\nCommitting {name} on branch {branch}: ', end='')
-
- # Get path
- project_path = path
- path = os.path.join(base_path, path)
- if not path.endswith('.git'):
- path = os.path.join(path, '.git')
-
- # Create repo object
- repo = git.Repo(path)
-
- # Add all files to commit
- count = add_target_paths(config_files, repo, base_path, project_path)
-
- if count == 0:
- print('Nothing to commit')
- return
-
- # Create commit; if it fails, probably empty so skipping
- try:
- repo.git.commit(m='Automatic AICP translation import')
- except:
- print('Failed, probably empty: skipping', file=sys.stderr)
- return
-
- # Push commit
- try:
- repo.git.push(f'ssh://{username}@gerrit.aicp-rom.com:29418/{name}',
- f'HEAD:refs/for/{branch}', '-o', f'topic=Translations-{branch}')
- print('Success')
- except Exception as e:
- print(e, '\nFailed to push!', file=sys.stderr)
- return
-
- _COMMITS_CREATED = True
-
-
-def submit_gerrit(branch, username):
- # Find all open translation changes
- cmd = ['ssh', '-p', '29418',
- f'{username}@gerrit.aicp-rom.com',
- 'gerrit', 'query',
- 'status:open',
- f'branch:{branch}',
- 'message:"Automatic AICP translation import"',
- f'topic:Translations-{branch}',
- '--current-patch-set',
- '--format=JSON']
- commits = 0
- msg, code = run_subprocess(cmd)
- if code != 0:
- print(f'Failed: {msg[1]}')
- return
-
- # Each line is one valid JSON object, except the last one, which is empty
- for line in msg[0].strip('\n').split('\n'):
- js = json.loads(line)
- # We get valid JSON, but not every result line is a line we want
- if not 'currentPatchSet' in js or not 'revision' in js['currentPatchSet']:
- continue
- # Add Code-Review +2 and Verified +1 labels and submit
- cmd = ['ssh', '-p', '29418',
- f'{username}@gerrit.aicp-rom.com',
- 'gerrit', 'review',
- '--verified +1',
- '--code-review +2',
- '--submit', js['currentPatchSet']['revision']]
- msg, code = run_subprocess(cmd, True)
- print('Submitting commit %s: ' % js['url'], end='')
- if code != 0:
- errorText = msg[1].replace('\n\n', '; ').replace('\n', '')
- print(f'Failed: {errorText}')
- else:
- print('Success')
-
- commits += 1
-
- if commits == 0:
- print("Nothing to submit!")
- return
-
-
-def check_run(cmd):
- p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
- ret = p.wait()
- if ret != 0:
- joined = ' '.join(cmd)
- print(f'Failed to run cmd: {joined}', file=sys.stderr)
- sys.exit(ret)
-
-
-def find_xml(base_path):
- for dp, dn, file_names in os.walk(base_path):
- for f in file_names:
- if os.path.splitext(f)[1] == '.xml':
- yield os.path.join(dp, f)
-
-# ############################################################################ #
-
-
-def parse_args():
- parser = argparse.ArgumentParser(
- description="Synchronising AICP translations with Crowdin")
- sync = parser.add_mutually_exclusive_group()
- parser.add_argument('--username', help='Gerrit username')
- parser.add_argument('--branch', help='AICP branch', required=True)
- parser.add_argument('-c', '--config', help='Custom yaml config')
- parser.add_argument('--upload-sources', action='store_true',
- help='Upload sources to AICP Crowdin')
- parser.add_argument('--upload-translations', action='store_true',
- help='Upload AICP translations to Crowdin')
- parser.add_argument('--download', action='store_true',
- help='Download AICP translations from Crowdin')
- parser.add_argument('--local-download', action='store_true',
- help='Locally download AICP translations from Crowdin')
- parser.add_argument('--submit', action='store_true',
- help='Auto-Merge open AICP translations on Gerrit')
- return parser.parse_args()
-
-# ################################# PREPARE ################################## #
-
-
-def check_dependencies():
- # Check for Java version of crowdin-cli
- cmd = ['find', '/usr/local/bin/crowdin-cli3.jar']
- if run_subprocess(cmd, silent=True)[1] != 0:
- print('You have not installed crowdin-cli.jar in its default location.', file=sys.stderr)
- return False
- return True
-
-
-def load_xml(x):
- try:
- return etree.parse(x)
- except etree.XMLSyntaxError:
- print(f'Malformed {x}', file=sys.stderr)
- return None
- except Exception:
- print(f'You have no {x}', file=sys.stderr)
- return None
-
-
-def check_files(files):
- for f in files:
- if not os.path.isfile(f):
- print(f'You have no {f}.', file=sys.stderr)
- return False
- return True
-
-# ################################### MAIN ################################### #
-
-
-def upload_sources_crowdin(project_id, branch, config):
- if config:
- print('\nUploading sources to Crowdin (custom config)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli3.jar',
- 'upload', 'sources',
- f'--project-id={project_id}',
- f'--branch={branch}',
- '--auto-update',
- f'--config={_DIR}/config/{config}'])
- else:
- print('\nUploading sources to Crowdin (AOSP supported languages)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli3.jar',
- 'upload', 'sources',
- f'--project-id={project_id}',
- f'--branch={branch}',
- '--auto-update',
- f'--config={_DIR}/config/{branch}.yml'])
-
-
-def upload_translations_crowdin(project_id, branch, config):
- if config:
- print('\nUploading translations to Crowdin (custom config)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli3.jar',
- 'upload', 'translations',
- f'--project-id={project_id}',
- f'--branch={branch}',
- '--import-eq-suggestions',
- '--auto-approve-imported',
- f'--config={_DIR}/config/{config}'])
- else:
- print('\nUploading translations to Crowdin '
- '(AOSP supported languages)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli3.jar',
- 'upload', 'translations',
- f'--project-id={project_id}',
- f'--branch={branch}',
- '--import-eq-suggestions',
- '--auto-approve-imported',
- f'--config={_DIR}/config/{branch}.yml'])
-
-
-def local_download(project_id, base_path, branch, xml, config):
- if config:
- print('\nDownloading translations from Crowdin (custom config)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli3.jar',
- 'download',
- f'--project-id={project_id}',
- f'--branch={branch}',
- '--skip-untranslated-strings',
- '--export-only-approved',
- f'--config={_DIR}/config/{config}'])
- else:
- print('\nDownloading translations from Crowdin '
- '(AOSP supported languages)')
- check_run(['java', '-jar', '/usr/local/bin/crowdin-cli3.jar',
- 'download',
- f'--project-id={project_id}',
- f'--branch={branch}',
- '--skip-untranslated-strings',
- '--export-only-approved',
- f'--config={_DIR}/config/{branch}.yml'])
-
- print('\nRemoving useless empty translation files (AOSP supported languages)')
- empty_contents = {
- '<resources/>',
- '<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>',
- ('<resources xmlns:android='
- '"http://schemas.android.com/apk/res/android"/>'),
- ('<resources xmlns:android="http://schemas.android.com/apk/res/android"'
- ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>'),
- ('<resources xmlns:tools="http://schemas.android.com/tools"'
- ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>'),
- ('<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\n</resources>'),
- ('<resources xmlns:android='
- '"http://schemas.android.com/apk/res/android">\n</resources>'),
- ('<resources xmlns:android="http://schemas.android.com/apk/res/android"'
- ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\n</resources>'),
- ('<resources xmlns:tools="http://schemas.android.com/tools"'
- ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\n</resources>'),
- ('<resources>\n</resources>')
- }
-
- xf = None
- cmd = ['java', '-jar', '/usr/local/bin/crowdin-cli3.jar',
- 'list', 'translations',
- f'--project-id={project_id}',
- '--plain',
- f'--config={_DIR}/config/{branch}.yml']
- comm, ret = run_subprocess(cmd)
- if ret != 0:
- sys.exit(ret)
- # Split in list and remove last empty entry
- xml_list=str(comm[0]).split("\n")[:-1]
- for xml_file in xml_list:
- try:
- print("Checking: " + base_path + '/' + xml_file)
- tree = etree.XML(open(base_path + '/' + xml_file).read().encode())
- etree.strip_tags(tree,etree.Comment)
- treestring = etree.tostring(tree,encoding='UTF-8')
- xf = "".join([s for s in treestring.decode().strip().splitlines(True) if s.strip()])
- for line in empty_contents:
- if line in xf:
- print("Removing: " + base_path + '/' + xml_file)
- os.remove(base_path + '/' + xml_file)
- break
- except IOError:
- print("File not found: " + xml_file)
- sys.exit(1)
- except etree.XMLSyntaxError:
- print("XML Syntax error in file: " + xml_file)
- sys.exit(1)
- del xf
-
-
-def download_crowdin(project_id, base_path, branch, xml, username, config):
- local_download(project_id, base_path, branch, xml, config)
-
- print('\nCreating a list of pushable translations')
- # Get all files that Crowdin pushed
- paths = []
- if config:
- files = [f'{_DIR}/config/{config}']
- else:
- files = [f'{_DIR}/config/{branch}.yml']
- for c in files:
- cmd = ['java', '-jar', '/usr/local/bin/crowdin-cli3.jar',
- 'list', 'sources', f'--branch={branch}', f'--config={c}', '--plain']
- comm, ret = run_subprocess(cmd)
- if ret != 0:
- sys.exit(ret)
- for p in str(comm[0]).split("\n"):
- paths.append(p.replace(f'/{branch}', ''))
-
- print('\nUploading translations to AICP Gerrit')
- items = [x for xmlfile in xml for x in xmlfile.findall("//project")]
- all_projects = []
-
- for path in paths:
- path = path.strip()
- if not path:
- continue
-
- if "/res" not in path:
- print(f'WARNING: Cannot determine project root dir of [{path}], skipping.')
- continue
-
- # Usually the project root is everything before /res
- # but there are special cases where /res is part of the repo name as well
- parts = path.split("/res")
- if len(parts) == 2:
- result = parts[0]
- elif len(parts) == 3:
- result = parts[0] + '/res' + parts[1]
- else:
- print(f'WARNING: Splitting the path not successful for [{path}], skipping')
- continue
-
- result = result.strip('/')
- if result == path.strip('/'):
- print(f'WARNING: Cannot determine project root dir of [{path}], skipping.')
- continue
-
- if result in all_projects:
- continue
-
- # When a project has multiple translatable files, Crowdin will
- # give duplicates.
- # We don't want that (useless empty commits), so we save each
- # project in all_projects and check if it's already in there.
- all_projects.append(result)
-
- # Search AICP/platform_manifest/*.xml or
- # config/%(branch)_extra_packages.xml for the project's name
- resultPath = None
- resultProject = None
- for project in items:
- path = project.get('path')
- if not (result + '/').startswith(path +'/'):
- continue
- # We want the longest match, so projects in subfolders of other projects are also
- # taken into account
- if resultPath is None or len(path) > len(resultPath):
- resultPath = path
- resultProject = project
-
- # Just in case no project was found
- if resultPath is None:
- continue
-
- if result != resultPath:
- if resultPath in all_projects:
- continue
- result = resultPath
- all_projects.append(result)
-
- br = resultProject.get('revision') or branch
-
- push_as_commit(files, base_path, result,
- resultProject.get('name'), br, username)
-
-
-def sig_handler(signal_received, frame):
- print('')
- print('SIGINT or CTRL-C detected. Exiting gracefully')
- exit(0)
-
-
-def main():
- signal(SIGINT, sig_handler)
- args = parse_args()
- default_branch = args.branch
-
- if args.submit:
- if args.username is None:
- print('Argument -u/--username is required for submitting!')
- sys.exit(1)
- submit_gerrit(default_branch, args.username)
- sys.exit(0)
-
- project_id_env = 'AICP_CROWDIN_PROJECT_ID'
- project_id = os.getenv(project_id_env)
-
- base_path_branch_suffix = default_branch.replace('.', '_')
- base_path_env = f'AICP_CROWDIN_BASE_PATH_{base_path_branch_suffix}'
- base_path = os.getenv(base_path_env)
- if base_path is None:
- cwd = os.getcwd()
- print(f'You have not set {base_path_env}. Defaulting to {cwd}')
- base_path = cwd
- if not os.path.isdir(base_path):
- print(f'{base_path_env} is not a real directory: {base_path}')
- sys.exit(1)
-
- if not check_dependencies():
- sys.exit(1)
-
- xml_default = load_xml(x=f'{base_path}/platform_manifest/default.xml')
- if xml_default is None:
- sys.exit(1)
-
- xml_extra = load_xml(x=f'{_DIR}/config/{default_branch}_extra_packages.xml')
- if xml_extra is not None:
- xml_files = (xml_default, xml_extra)
- else:
- xml_files = (xml_default)
-
- if args.config:
- files = [f'{_DIR}/config/{args.config}']
- else:
- files = [f'{_DIR}/config/{default_branch}.yml']
- if not check_files(files):
- sys.exit(1)
-
- if args.download and args.username is None:
- print('Argument --username is required to perform this action')
- sys.exit(1)
-
- if args.upload_sources:
- upload_sources_crowdin(project_id, default_branch, args.config)
-
- if args.upload_translations:
- upload_translations_crowdin(project_id, default_branch, args.config)
-
- if args.local_download:
- local_download(project_id, base_path, default_branch, xml_files, args.config)
-
- if args.download:
- download_crowdin(project_id, base_path, default_branch, xml_files, args.username, args.config)
-
- if _COMMITS_CREATED:
- print('\nDone!')
- sys.exit(0)
- else:
- print('\nFinished! Nothing to do or commit anymore.')
- sys.exit(-1)
-
-if __name__ == '__main__':
- main()