diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 84c9069c7..88d8089f5 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -11,20 +11,48 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - - name: crowdin action + - name: push source files uses: crowdin/github-action@v2 with: - crowdin_branch_name: main - dryrun_action: true - upload_sources: true - download_translations: true - export_only_approved: true - push_translations: true - localization_branch_name: main - create_pull_request: false - commit_message: 'chore(i18n): update translations via Crowdin' + command: 'push' env: - GITHUB_TOKEN: ${{ secrets.CROWDIN_GH_TOKEN }} CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} \ No newline at end of file + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + - name: pull translations + uses: crowdin/github-action@v2 + with: + command: 'download' + command_args: '--export-only-approved --skip-untranslated-strings' + env: + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Ensure file permissions + run: | + find pkg/i18n/lang frontend/src/i18n/lang -type f -name "*.json" -exec sudo chmod 666 {} \; + - name: Fix exported files + run: | + node contrib/clean-translations.js + - name: Check for changes + id: check_changes + run: | + if git diff --quiet; then + echo "changes_exist=0" >> "$GITHUB_OUTPUT" + else + echo "changes_exist=1" >> "$GITHUB_OUTPUT" + fi + - name: Commit files + if: steps.check_changes.outputs.changes_exist != '0' + run: | + git config --local user.email "frederik@vikunja.io" + git config --local user.name "Frederick [Bot]" + git commit -am "chore(i18n): update translations via Crowdin" + - name: Push changes + if: steps.check_changes.outputs.changes_exist != '0' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5209ab203..193a75ed8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -296,8 +296,11 @@ jobs: - name: Check for changes id: check_changes run: | - git diff --quiet - echo "changes_exist=$?" >> "$GITHUB_OUTPUT" + if git diff --quiet; then + echo "changes_exist=0" >> "$GITHUB_OUTPUT" + else + echo "changes_exist=1" >> "$GITHUB_OUTPUT" + fi - name: Commit files if: steps.check_changes.outputs.changes_exist != '0' run: | diff --git a/contrib/clean-translations.js b/contrib/clean-translations.js new file mode 100755 index 000000000..fc6364a6a --- /dev/null +++ b/contrib/clean-translations.js @@ -0,0 +1,124 @@ +#!/usr/bin/env node + +/** + * Script to remove empty JSON keys from translation files + * + * This script traverses through the specified directories and removes all + * empty string values from JSON files recursively. + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// Get the root directory (where the script is run from) +const rootDir = process.cwd(); + +// Define directories to process (relative to root) +const directories = [ + path.join(rootDir, 'pkg/i18n/lang'), + path.join(rootDir, 'frontend/src/i18n/lang') +]; + +/** + * Recursively removes empty string values from an object + * @param {Object} obj - The object to clean + * @returns {Object} - The cleaned object with empty strings removed + */ +function removeEmptyStrings(obj) { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + + // Handle arrays + if (Array.isArray(obj)) { + return obj.map(item => removeEmptyStrings(item)) + .filter(item => item !== ''); + } + + // Handle objects + const result = {}; + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const value = obj[key]; + + if (value === '') { + // Skip empty strings + continue; + } else if (typeof value === 'object' && value !== null) { + // Recursively clean nested objects + const cleanedValue = removeEmptyStrings(value); + + // Only add non-empty objects + if (typeof cleanedValue === 'object' && + !Array.isArray(cleanedValue) && + Object.keys(cleanedValue).length === 0) { + continue; + } + + result[key] = cleanedValue; + } else { + // Keep non-empty values + result[key] = value; + } + } + } + + return result; +} + +/** + * Process a single JSON file to remove empty strings + * @param {string} filePath - Path to the JSON file + */ +function processFile(filePath) { + try { + console.log(`Processing ${filePath}`); + + // Read and parse the JSON file + const data = fs.readFileSync(filePath, 'utf8'); + const json = JSON.parse(data); + + // Clean the JSON data + const cleanedJson = removeEmptyStrings(json); + + // Write the cleaned JSON back to the file + fs.writeFileSync( + filePath, + JSON.stringify(cleanedJson, null, '\t'), + 'utf8' + ); + + console.log(`Successfully cleaned ${filePath}`); + } catch (error) { + console.error(`Error processing ${filePath}:`, error); + } +} + +/** + * Process all JSON files in the specified directories + */ +function main() { + directories.forEach(dir => { + if (!fs.existsSync(dir)) { + console.warn(`Directory ${dir} does not exist. Skipping.`); + return; + } + + const files = fs.readdirSync(dir); + + files.forEach(file => { + const filePath = path.join(dir, file); + + if (file.endsWith('.json') && file !== 'en.json') { + processFile(filePath); + } + }); + }); + + console.log('All translation files have been processed successfully!'); +} + +// Run the script +main(); \ No newline at end of file