# Crowdin Synchronization Workflow for i18n in OpenLogi: Automated Translation Management

> Discover the Crowdin synchronization workflow for i18n in OpenLogi. Learn how GitHub Actions and a Python script maintain perfect locale key parity for automated translation management.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-09-05

---

**OpenLogi maintains perfect locale key parity through a GitHub Actions workflow that uploads English source strings to Crowdin, downloads sparse translations, and merges them back using a custom Python script to ensure every locale file contains the exact same key set as [`en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/en.toml).**

The AprilNEA/OpenLogi repository implements a fully automated **Crowdin synchronization workflow for i18n** that keeps UI translations synchronized while guaranteeing semantic consistency across all supported languages. This system ensures that [`crates/openlogi-ui/locales/en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/locales/en.toml) serves as the single source of truth, with automated processes verifying that every locale file maintains identical key structures regardless of translation completion status.

## How the Crowdin Synchronization Workflow Works

The automation is driven by [`.github/workflows/crowdin.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.github/workflows/crowdin.yml), which orchestrates a nine-step process to synchronize translations without human intervention.

### Trigger Conditions

The workflow initiates on a daily schedule or whenever critical files change. According to the `on` section of the workflow configuration, it monitors:

- [`crates/openlogi-ui/locales/en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/locales/en.toml) — the English source catalog
- [`.config/crowdin.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.config/crowdin.yml) — Crowdin configuration mappings
- `.github/scripts/i18n/**` — the merge script utilities

This ensures the pipeline executes immediately when developers modify the source of truth or the synchronization tooling itself.

### Snapshot and Authentication

Before contacting Crowdin, the workflow establishes secure credentials and baselines the current state:

1. **Authentication**: The action mints a GitHub App token to enable branch creation and pull request generation, bypassing the default `GITHUB_TOKEN` limitations.

2. **Credential Retrieval**: The Crowdin project ID and personal token are fetched from 1Password, ensuring secrets remain encrypted.

3. **Self-Test**: The helper script [`merge_crowdin_download.py`](https://github.com/AprilNEA/OpenLogi/blob/main/merge_crowdin_download.py) runs with `--self-test` to verify its internal logic before processing live data.

4. **Snapshot Creation**: All existing locale TOML files are copied to `/tmp/openlogi-locales-before`, creating a "merge base" that represents the complete key set before any Crowdin modifications.

### Upload, Download, and Merge

The core synchronization uses the official Crowdin GitHub Action with a specific sparse export strategy:

- **Upload**: Pushes the current [`en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/en.toml) to Crowdin as the source of truth
- **Seed**: Uploads existing translations from the repository so Crowdin learns current progress
- **Download**: Requests a sparse export with `skip_untranslated_strings: true`, which omits untranslated keys from the downloaded files

After download, the merge script restores key parity:

```bash
python3 .github/scripts/i18n/merge_crowdin_download.py \
    --before /tmp/openlogi-locales-before \
    --locales crates/openlogi-ui/locales \
    --en crates/openlogi-ui/locales/en.toml

```

This invocation preserves the snapshot directory as reference, applies downloaded translations only where values differ from English, and restores any keys omitted by the sparse export.

### Branch Creation and Pull Request

If `git diff` detects changes after the merge, the workflow:

1. Creates a branch named `crowdin/i18n`
2. Commits the updated locale files
3. Pushes the branch using the minted GitHub App token
4. Opens a pull request titled "chore(i18n): sync Crowdin translations"

The PR description explicitly notes that [`en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/en.toml) drives parity and that the merge guarantees semantic-key alignment across locales.

## Maintaining Locale Key Parity

OpenLogi employs a multi-layered defense against key drift, ensuring every locale file contains exactly the same keys as the English source.

### Source-of-Truth Principle

[`crates/openlogi-ui/locales/en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/locales/en.toml) serves as the **single source of truth** for all translation keys. Developers must add new keys to this file first; the synchronization pipeline propagates these keys to other locales automatically. This strict hierarchy prevents fragmented keysets across different language files.

### Automated Parity Testing

The crate includes [`crates/openlogi-ui/tests/catalog_parity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/tests/catalog_parity.rs), a dedicated CI test that loads every locale TOML file and asserts that the key set exactly matches [`en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/en.toml):

```rust
#[test]
fn catalog_parity() {
    let en = i18n::load("crates/openlogi-ui/locales/en.toml");
    for locale_path in glob::glob("crates/openlogi-ui/locales/*.toml").unwrap() {
        let locale = i18n::load(&locale_path.unwrap().to_string_lossy());
        assert_eq!(en.keys(), locale.keys(),
            "Locale {} does not have the same keys as en.toml", locale_path);
    }
}

```

This test executes on every CI run, catching accidental key mismatches before they reach production.

### The Merge Script Strategy

The [`merge_crowdin_download.py`](https://github.com/AprilNEA/OpenLogi/blob/main/merge_crowdin_download.py) script guarantees parity even when Crowdin's sparse export removes untranslated keys. By comparing the downloaded files against the pre-sync snapshot stored in `/tmp/openlogi-locales-before`, the script re-inserts any missing keys while preserving translated values. This ensures the repository never contains a locale with a divergent key set, regardless of translation completion rates.

## Running the Workflow Locally

Developers can debug the synchronization process or manually merge Crowdin exports using the following procedure.

First, create a snapshot of current catalogs:

```bash
mkdir -p /tmp/openlogi-locales-before
cp crates/openlogi-ui/locales/*.toml /tmp/openlogi-locales-before/

```

After downloading translations from Crowdin (via their web interface or CLI), run the merge script:

```bash
python3 .github/scripts/i18n/merge_crowdin_download.py \
    --before /tmp/openlogi-locales-before \
    --locales crates/openlogi-ui/locales \
    --en crates/openlogi-ui/locales/en.toml

```

This local execution follows the exact logic used in [`.github/workflows/crowdin.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.github/workflows/crowdin.yml), making it useful for troubleshooting merge conflicts or verifying sparse export handling.

## Summary

- **Automated Triggers**: The workflow runs daily or when [`en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/en.toml), [`.config/crowdin.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.config/crowdin.yml), or merge scripts change
- **Sparse Export Handling**: Uses `skip_untranslated_strings: true` to reduce download size, then restores missing keys via [`merge_crowdin_download.py`](https://github.com/AprilNEA/OpenLogi/blob/main/merge_crowdin_download.py)
- **Key Parity Guarantee**: The merge script compares against pre-sync snapshots to ensure every locale matches the English key set
- **CI Enforcement**: [`tests/catalog_parity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/tests/catalog_parity.rs) asserts that all locales contain identical keys to [`en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/en.toml)
- **Manual Debugging**: The merge script supports local execution with `--before`, `--locales`, and `--en` parameters

## Frequently Asked Questions

### What triggers the Crowdin synchronization workflow in OpenLogi?

The workflow triggers on a daily schedule and whenever files in three specific paths change: [`crates/openlogi-ui/locales/en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/locales/en.toml) (the English source), [`.config/crowdin.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.config/crowdin.yml) (Crowdin configuration), or any file in `.github/scripts/i18n/` (the merge utilities). This ensures immediate synchronization when developers modify the source catalog or when the automation tooling itself updates.

### How does OpenLogi prevent missing translation keys when using Crowdin's sparse export?

OpenLogi uses a snapshot-and-merge strategy. Before downloading from Crowdin, the workflow copies all existing locale files to `/tmp/openlogi-locales-before`. Even though Crowdin's `skip_untranslated_strings: true` setting omits untranslated keys from downloads, the [`merge_crowdin_download.py`](https://github.com/AprilNEA/OpenLogi/blob/main/merge_crowdin_download.py) script restores these missing entries from the snapshot while keeping newly translated values. This guarantees that no keys disappear from locale files, maintaining parity with the English source.

### What is the role of the [`merge_crowdin_download.py`](https://github.com/AprilNEA/OpenLogi/blob/main/merge_crowdin_download.py) script?

The script at [`.github/scripts/i18n/merge_crowdin_download.py`](https://github.com/AprilNEA/OpenLogi/blob/main/.github/scripts/i18n/merge_crowdin_download.py) serves as the parity enforcement engine. It accepts three arguments: `--before` (the snapshot directory), `--locales` (the target locale directory), and `--en` (the English source file). The script ensures that every locale file ends up with the exact same key set as the English catalog, inserting missing keys from the snapshot and only keeping translation values that differ from the English source. It also supports `--self-test` for validating its internal logic before network operations.

### How does OpenLogi verify that all locale files maintain key parity with the English source?

The repository includes a Rust integration test in [`crates/openlogi-ui/tests/catalog_parity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ui/tests/catalog_parity.rs) that executes during CI. This test loads [`en.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/en.toml) and compares its key set against every other locale file in the `crates/openlogi-ui/locales/` directory. If any locale contains extra or missing keys compared to the English source, the test fails immediately, preventing merges that would break translation consistency.