diff --git a/Crowdin/Download.php b/Crowdin/Download.php index 49f56dc..ab62612 100644 --- a/Crowdin/Download.php +++ b/Crowdin/Download.php @@ -2,14 +2,6 @@ require(__DIR__ . '/Helpers.php'); -if ($useBundling === true) { - $projectPath = realpath(__DIR__ . '/../../../'); - echo 'Working on project bundle' . PHP_EOL; - executeDownload($projectPath, $branch); -} else { - foreach ($projects as $identifier => $projectData) { - $projectPath = realpath(__DIR__ . '/../../../' . $projectData['path']); - echo 'Working on ' . $projectPath . PHP_EOL; - executeDownload($projectPath, $branch); - } -} \ No newline at end of file +$projectPath = realpath(__DIR__ . '/../../../'); +echo 'Downloading…' . PHP_EOL; +executeDownload($projectPath, $branch); diff --git a/Crowdin/Helpers.php b/Crowdin/Helpers.php index 7e37e36..4079a3d 100644 --- a/Crowdin/Helpers.php +++ b/Crowdin/Helpers.php @@ -10,21 +10,22 @@ exit(1); } -/** @var array $projects */ -$projects = json_decode(file_get_contents($argv[1]), true); +/** @var array $configuration */ +$configuration = json_decode(file_get_contents($argv[1]), true); -if (array_search('--bundle', $argv) !== false) { - $useBundling = true; -} else { - $useBundling = false; - if (isset($projects['__bundle'])) { - unset($projects['__bundle']); - } +if (!isset($configuration['project'])) { + echo 'No project entry found in configuration file.' . PHP_EOL; + exit(1); } -$uploadTranslations = (array_search('--translations', $argv) !== false); -$branch = isset($projects['branch']) ? $projects['branch'] : null; -unset($projects['branch']); +if (!isset($configuration['project']['branch'])) { + echo 'No project.branch entry found in configuration file.' . PHP_EOL; + exit(1); +} +$branch = $configuration['project']['branch']; + +$verbose = (array_search('--verbose', $argv) !== false); +$uploadTranslations = (array_search('--translations', $argv) !== false); /** * Check if the given project exists on Crowdin. @@ -55,23 +56,25 @@ function projectExists($identifier, $apiKey) function executeUpload($projectPath, $uploadTranslations, $branch = null) { if (file_exists($projectPath . '/crowdin.yaml')) { - if ($branch === null) { - echo sprintf('cd %s; crowdin-cli upload sources', escapeshellarg($projectPath)) . PHP_EOL; - passthru(sprintf('cd %s; crowdin-cli upload sources', escapeshellarg($projectPath))); - if ($uploadTranslations === true) { - echo sprintf('cd %s; crowdin-cli upload translations', escapeshellarg($projectPath)) . PHP_EOL; - passthru(sprintf('cd %s; crowdin-cli upload translations', escapeshellarg($projectPath))); - } + if ($branch === '') { + $command = sprintf('cd %s; crowdin upload sources', escapeshellarg($projectPath)); } else { - echo sprintf('cd %s; crowdin-cli upload sources -b %s', escapeshellarg($projectPath), escapeshellarg($branch)) . PHP_EOL; - passthru(sprintf('cd %s; crowdin-cli upload sources -b %s', escapeshellarg($projectPath), escapeshellarg($branch))); - if ($uploadTranslations === true) { - echo sprintf('cd %s; crowdin-cli upload translations -b %s', escapeshellarg($projectPath), escapeshellarg($branch)) . PHP_EOL; - passthru(sprintf('cd %s; crowdin-cli upload translations -b %s', escapeshellarg($projectPath), escapeshellarg($branch))); + $command = sprintf('cd %s; crowdin upload sources --branch %s', escapeshellarg($projectPath), escapeshellarg($branch)); + } + echo $command . PHP_EOL; + passthru($command); + + if ($uploadTranslations === true) { + if ($branch === '') { + $command = sprintf('cd %s; crowdin upload translations', escapeshellarg($projectPath)); + } else { + $command = sprintf('cd %s; crowdin upload translations --branch %s', escapeshellarg($projectPath), escapeshellarg($branch)); } + echo $command . PHP_EOL; + passthru($command); } } else { - echo 'Project is not configured.' . PHP_EOL; + echo 'Project is not configured. Run setup first.' . PHP_EOL; } } @@ -84,14 +87,16 @@ function executeUpload($projectPath, $uploadTranslations, $branch = null) function executeDownload($projectPath, $branch = null) { if (file_exists($projectPath . '/crowdin.yaml')) { - if ($branch === null) { - echo sprintf('cd %s; crowdin-cli download', escapeshellarg($projectPath)) . PHP_EOL; - passthru(sprintf('cd %s; crowdin-cli download', escapeshellarg($projectPath))); + if ($branch === '') { + $command = sprintf('cd %s; crowdin download', escapeshellarg($projectPath)); + echo $command . PHP_EOL; + passthru($command); } else { - echo sprintf('cd %s; crowdin-cli download -b %s', escapeshellarg($projectPath), escapeshellarg($branch)) . PHP_EOL; - passthru(sprintf('cd %s; crowdin-cli download -b %s', escapeshellarg($projectPath), escapeshellarg($branch))); + $command = sprintf('cd %s; crowdin download --branch %s', escapeshellarg($projectPath), escapeshellarg($branch)); + echo $command . PHP_EOL; + passthru($command); } } else { - echo 'Project is not configured.' . PHP_EOL; + echo 'Project is not configured. Run setup first.' . PHP_EOL; } } diff --git a/Crowdin/README.rst b/Crowdin/README.rst index e292e12..8a34203 100644 --- a/Crowdin/README.rst +++ b/Crowdin/README.rst @@ -7,33 +7,33 @@ The tools in this folder allow to use it for Flow packages. Configuration ------------- -You need to have the crowdin-cli tool installed, see https://crowdin.net/page/cli-tool. +You need to have the crowdin-cli tool installed, see https://support.crowdin.com/cli-tool/. Now create a JSON file in your project root, e.g. named ``crowdin.json``:: { - "acme-foo": { - "name": "Acme Foo", - "path": "Packages/Application/Acme.Foo", - "apiKey": "" + "project": { + "branch": "", + "identifier": "neos", + "apiKey": "%CROWDIN_API_KEY%" }, - "acme-bar": { - "name": "Acme Bar", - "path": "Packages/Application/Acme.Bar", - "apiKey": "" - } - } -.. note:: You should never commit this to Git, as it contains your secret keys. - You can also use the option of specifying the key(s) via an environment variable. + "items": { + "neos": { + "path": "Packages/Neos/*" + }, -Now you can run:: + "framework": { + "path": "Packages/Framework/*" + }, - php Build/BuildEssentials/Crowdin/Setup.php `pwd`/crowdin.json - -This will create ``crowdin.yaml`` in every project configured in ``crowdin.json``. + "addons": { + "path": "Packages/Application/Neos.*" + } + } + } -.. note:: You should exclude ``crowdin.yaml`` from Git, so it cannot be committed by +.. note:: You should exclude ``crowdin.json`` from Git, so it cannot be committed by accident (as it contains your secret keys) or use the option of specifying the key(s) via an environment variable. @@ -45,42 +45,24 @@ variable in the ``crowdin.json``file, **wrapped in percent signs**. Then, when actually calling the scripts to setup, upload or download, define the variable as usual. -Bundling projects -^^^^^^^^^^^^^^^^^ - -Instead of using a single Crowdin project per package, you can also use bundling to have all -XLIFF files (sources and translations) be handled in a single Crowdin project. This allows -for better overview, uses a single translation memory and glossary and thus makes the life -of translators easier. - -To use bundling, set up ``crowdin.json`` like above but include a special ``__bundle`` entry:: - - { - "__bundle": { - "projectIdentifier": "", - "apiKey": "" - }, - "acme-foo": { - … - } - } +Usage +----- -The individual project entries can still have ``apiKey`` entries, they will be ignored. -Keeping them allows to reuse the same ``crowdin.json`` file for bundled and non-bundled -use (should that ever be needed). +Now you can run:: -Then call the setup script like this:: + php Build/BuildEssentials/Crowdin/Setup.php `pwd`/crowdin.json - php Build/BuildEssentials/Crowdin/Setup.php `pwd`/crowdin.json --bundle +This will create ``crowdin.yaml`` based on the configuration in ``crowdin.json``. -Usage ------ +.. note:: You should exclude ``crowdin.yaml`` from Git, so it cannot be committed by + accident (as it contains your secret keys) or use the option of specifying the + key(s) via an environment variable. -Now you can run:: +Then run:: php Build/BuildEssentials/Crowdin/Upload.php `pwd`/crowdin.json -This will upload the source XLIFF files to Crowdin. Using:: +to upload the source XLIFF files to Crowdin. Using:: php Build/BuildEssentials/Crowdin/Upload.php `pwd`/crowdin.json --translations @@ -93,13 +75,7 @@ Running this will download the translations from Crowdin:: This updates the XLIFF files with translations from Crowdin; review and commit as you like. -All of the above commands can be called with ``--bundle`` to use project bundling:: - - php Build/BuildEssentials/Crowdin/Upload.php `pwd`/crowdin.json --bundle - php Build/BuildEssentials/Crowdin/Upload.php `pwd`/crowdin.json --bundle --translations - php Build/BuildEssentials/Crowdin/Download.php `pwd`/crowdin.json --bundle - -To remove all generated ``crowdin.yaml`` files again use:: +To remove the generated ``crowdin.yaml`` file again use:: php Build/BuildEssentials/Crowdin/Teardown.php `pwd`/crowdin.json diff --git a/Crowdin/Setup.php b/Crowdin/Setup.php index 70859ab..8fd8935 100644 --- a/Crowdin/Setup.php +++ b/Crowdin/Setup.php @@ -5,61 +5,47 @@ $configurationFileTemplate = file_get_contents(__DIR__ . '/template-crowdin.yaml'); $filesEntryTemplate = file_get_contents(__DIR__ . '/template-filesentry.yaml'); -if ($useBundling === true) { - if (!isset($projects['__bundle'])) { - echo 'No __bundle configuration found in configuration file.' . PHP_EOL; - exit(1); - } - - $bundleProjectIdentifier = $projects['__bundle']['projectIdentifier']; - $apiKey = $projects['__bundle']['apiKey']; - $apiKeyEnvironmentVariable = false; - if (preg_match('/^%.+%$/', $apiKey) === 1) { - $apiKeyEnvironmentVariable = trim($apiKey, '%'); - $apiKey = getenv($apiKeyEnvironmentVariable); - } - unset($projects['__bundle']); - - if (projectExists($bundleProjectIdentifier, $apiKey) === false) { - echo 'Project ' . $bundleProjectIdentifier . ' does not exist, please create it in Crowdin.' . PHP_EOL; - exit(1); - } - - $filesEntryContent = ''; - foreach ($projects as $identifier => $projectData) { - $projectPath = '/' . $projectData['path']; - $filesEntryContent .= sprintf($filesEntryTemplate, $projectPath, $projectPath); - } - $configurationFileContent = sprintf($configurationFileTemplate, $bundleProjectIdentifier, $apiKeyEnvironmentVariable ? '_env' : '', $apiKeyEnvironmentVariable ?: $apiKey, $filesEntryContent); - - $configurationfilePathAndFilename = realpath(__DIR__ . '/../../../') . '/crowdin.yaml'; - file_put_contents($configurationfilePathAndFilename, $configurationFileContent); - echo 'Wrote ' . $configurationfilePathAndFilename . PHP_EOL; -} else { - foreach ($projects as $identifier => $projectData) { - if (!isset($projectData['apiKey'])) { - echo 'Project ' . $projectData['name'] . ' has no apiKey set.' . PHP_EOL; - continue; - } +$projectIdentifier = $configuration['project']['identifier']; +$apiKey = $configuration['project']['apiKey']; +$apiKeyEnvironmentVariable = false; +if (preg_match('/^%.+%$/', $apiKey) === 1) { + $apiKeyEnvironmentVariable = trim($apiKey, '%'); + $apiKey = getenv($apiKeyEnvironmentVariable); +} - $apiKey = $projectData['apiKey']; - $apiKeyEnvironmentVariable = false; - if (preg_match('/^%.+%$/', $apiKey) === 1) { - $apiKeyEnvironmentVariable = trim($apiKey, '%'); - $apiKey = getenv($apiKeyEnvironmentVariable); - } +if (projectExists($projectIdentifier, $apiKey) === false) { + echo 'Project ' . $projectIdentifier . ' does not exist, please create it in Crowdin.' . PHP_EOL; + exit(1); +} - if (projectExists($identifier, $apiKey) === false) { - echo 'Project ' . $projectData['name'] . ' does not exist, please create it in Crowdin.' . PHP_EOL; +$filesEntryContent = []; +foreach ($configuration['items'] as $itemKey => $itemConfiguration) { + foreach (glob($itemConfiguration['path'], GLOB_ONLYDIR) as $projectPath) { + $translationSourceDirectory = $projectPath . '/Resources/Private/Translations/en'; + if (!is_dir($translationSourceDirectory)) { + if ($verbose) { + echo sprintf( + 'Skipping "%s" since no XLIFF sources exist.' . PHP_EOL, + basename($projectPath), + $translationSourceDirectory + ); + } continue; } - $filesEntryContent = sprintf($filesEntryTemplate, '', ''); - $configurationFileContent = sprintf($configurationFileTemplate, $identifier, $apiKeyEnvironmentVariable ? '_env' : '', $apiKeyEnvironmentVariable ?: $apiKey, $filesEntryContent); - - $configurationfilePathAndFilename = realpath(__DIR__ . '/../../../' . $projectData['path']) . '/crowdin.yaml'; - file_put_contents($configurationfilePathAndFilename, $configurationFileContent); - echo 'Wrote ' . $configurationfilePathAndFilename . PHP_EOL; + $filesEntryContent[] .= rtrim(sprintf($filesEntryTemplate, $projectPath, $projectPath)); } } +$configurationFileContent = sprintf( + $configurationFileTemplate, + $projectIdentifier, + $apiKeyEnvironmentVariable ? '_env' : '', + $apiKeyEnvironmentVariable ?: $apiKey, + implode(',' . chr(10), $filesEntryContent) +); + +$configurationPathAndFilename = realpath(__DIR__ . '/../../../') . '/crowdin.yaml'; +file_put_contents($configurationPathAndFilename, $configurationFileContent); + +echo 'Wrote ' . $configurationPathAndFilename . PHP_EOL; diff --git a/Crowdin/Teardown.php b/Crowdin/Teardown.php index 75a10b8..4b54370 100644 --- a/Crowdin/Teardown.php +++ b/Crowdin/Teardown.php @@ -2,34 +2,8 @@ require(__DIR__ . '/Helpers.php'); -// remove top-level crowdin.yaml -$configurationfilePathAndFilename = realpath(__DIR__ . '/../../../' . '/crowdin.yaml'); -if (file_exists($configurationfilePathAndFilename)) { - unlink($configurationfilePathAndFilename); - echo 'Removed ' . $configurationfilePathAndFilename . PHP_EOL; -} - -if ($useBundling === true) { - if (!isset($projects['__bundle'])) { - echo 'No __bundle configuration found in configuration file.' . PHP_EOL; - exit(1); - } - unset($projects['__bundle']); - - foreach ($projects as $identifier => $projectData) { - $configurationfilePathAndFilename = realpath(__DIR__ . '/../../../' . $projectData['path'] . '/crowdin.yaml'); - if (file_exists($configurationfilePathAndFilename)) { - unlink($configurationfilePathAndFilename); - echo 'Removed ' . $configurationfilePathAndFilename . PHP_EOL; - } - } -} else { - foreach ($projects as $identifier => $projectData) { - // remove crowdin.yaml in packages - $configurationfilePathAndFilename = realpath(__DIR__ . '/../../../' . $projectData['path'] . '/crowdin.yaml'); - if (file_exists($configurationfilePathAndFilename)) { - unlink($configurationfilePathAndFilename); - echo 'Removed ' . $configurationfilePathAndFilename . PHP_EOL; - } - } +$configurationPathAndFilename = realpath(__DIR__ . '/../../../' . '/crowdin.yaml'); +if (file_exists($configurationPathAndFilename)) { + unlink($configurationPathAndFilename); + echo 'Removed ' . $configurationPathAndFilename . PHP_EOL; } diff --git a/Crowdin/Upload.php b/Crowdin/Upload.php index e318a3d..ad791e3 100644 --- a/Crowdin/Upload.php +++ b/Crowdin/Upload.php @@ -2,14 +2,6 @@ require(__DIR__ . '/Helpers.php'); -if ($useBundling === true) { - $projectPath = realpath(__DIR__ . '/../../../'); - echo 'Working on project bundle' . PHP_EOL; - executeUpload($projectPath, $uploadTranslations, $branch); -} else { - foreach ($projects as $identifier => $projectData) { - $projectPath = realpath(__DIR__ . '/../../../' . $projectData['path']); - echo 'Working on ' . $projectData['name'] . PHP_EOL; - executeUpload($projectPath, $uploadTranslations, $branch); - } -} +$projectPath = realpath(__DIR__ . '/../../../'); +echo 'Uploading…' . PHP_EOL; +executeUpload($projectPath, $uploadTranslations, $branch); diff --git a/Crowdin/template-crowdin.yaml b/Crowdin/template-crowdin.yaml index 67b8350..3674cb6 100644 --- a/Crowdin/template-crowdin.yaml +++ b/Crowdin/template-crowdin.yaml @@ -1,6 +1,19 @@ -project_identifier: '%s' -api_key%s: '%s' -base_path: '.' +# +# Your crowdin's credentials +# +"project_identifier": "%s" +"api_key%s": "%s" +"base_path": "." -files: -%s \ No newline at end of file +# +# Choose file structure in crowdin +# e.g. true or false +# +"preserve_hierarchy": true + +# +# Files configuration +# +files: [ +%s +] diff --git a/Crowdin/template-filesentry.yaml b/Crowdin/template-filesentry.yaml index 138bfc6..81e6192 100644 --- a/Crowdin/template-filesentry.yaml +++ b/Crowdin/template-filesentry.yaml @@ -1,43 +1,62 @@ - - - source: '%s/Resources/Private/Translations/en/**/*.xlf' - translation: '%s/Resources/Private/Translations/%%locale_with_underscore%%/**/%%original_file_name%%' - update_option: 'update_as_unapproved' - languages_mapping: - locale_with_underscore: - 'af': 'af' - 'ar': 'ar' - 'ca': 'ca' - 'cs': 'cs' - 'da': 'da' - 'de': 'de' - 'el': 'el' - 'en': 'en' - 'es-ES': 'es' - 'fi': 'fi' - 'fr': 'fr' - 'he': 'he' - 'hu': 'hu' - 'id-ID': 'id_ID' - 'it': 'it' - 'ja': 'ja' - 'kk-KZ': 'kk_KZ' - 'km': 'km' - 'ko': 'ko' - 'la-LA': 'la' - 'lv': 'lv' - 'mr': 'mr' - 'nl': 'nl' - 'no': 'no' - 'pl': 'pl' - 'ps': 'ps' - 'pt-PT': 'pt' - 'pt-BR': 'pt_BR' - 'ro': 'ro' - 'ru': 'ru' - 'sr': 'sr' - 'sv-SE': 'sv' - 'tr': 'tr' - 'uk': 'uk' - 'vi': 'vi' - 'zh-CN': 'zh' + { + # + # Source files filter + # + 'source': '/%s/Resources/Private/Translations/en/**/*.xlf', + + # + # where translations live + # + 'translation': '/%s/Resources/Private/Translations/%%locale_with_underscore%%/**/%%original_file_name%%', + + # + # The parameter 'update_option' is optional. If it is not set, translations for changed strings will be lost. Useful for typo fixes and minor changes in source strings. + # e.g. 'update_as_unapproved' or 'update_without_changes' + # + 'update_option': 'update_as_unapproved', + + # + # Often software projects have custom names for locale directories. crowdin-cli allows you to map your own languages to be understandable by Crowdin. + # + 'languages_mapping': { + 'locale_with_underscore': { + 'af': 'af', + 'ar': 'ar', + 'ca': 'ca', + 'cs': 'cs', + 'da': 'da', + 'de': 'de', + 'el': 'el', + 'en': 'en', + 'es-ES': 'es', + 'fi': 'fi', + 'fr': 'fr', + 'he': 'he', + 'hu': 'hu', + 'id-ID': 'id_ID', + 'it': 'it', + 'ja': 'ja', + 'kk-KZ': 'kk_KZ', + 'km': 'km', + 'ko': 'ko', + 'la-LA': 'la', + 'lv': 'lv', + 'mr': 'mr', + 'nl': 'nl', + 'no': 'no', + 'pl': 'pl', + 'ps': 'ps', + 'pt-PT': 'pt', + 'pt-BR': 'pt_BR', + 'ro': 'ro', + 'ru': 'ru', + 'sr': 'sr', + 'sv-SE': 'sv', + 'tr': 'tr', + 'uk': 'uk', + 'vi': 'vi', + 'zh-CN': 'zh', 'zh-TW': 'zh_TW' + } + } + }