Environment-specific translations
Not all translations should be the same across every environment. Development environments benefit from placeholder text that signals when a key is not yet translated. Staging environments need realistic copy for QA and stakeholder review. Production environments must carry the exact signed-off content. Running these as separate files in separate repositories, or trying to manage environment switches through runtime flags in application code, creates drift and uncertainty.
Messagevisor solves this with sets: parallel, fully independent authoring trees living in one repository, connected by a promotion workflow that moves content from one environment to the next.
What sets give you#
When sets are enabled, your repository contains multiple complete definition trees:
sets/├── dev/│ ├── locales/│ ├── messages/│ ├── segments/│ ├── targets/│ └── tests/├── staging/│ ├── locales/│ ├── messages/│ ├── segments/│ ├── targets/│ └── tests/└── production/ ├── locales/ ├── messages/ ├── segments/ ├── targets/ └── tests/Each set is completely independent. A message in sets/dev/ does not automatically appear in sets/production/. Content flows between sets through explicit promotion commands, not silent inheritance.
Enabling sets#
module.exports = { sets: true, promotionFlows: [ { from: "dev", to: "staging" }, { from: "staging", to: "production" }, ],};The promotionFlows constraint enforces your intended direction. Attempting promote --from=production --to=dev will fail clearly rather than silently doing something unexpected.
Authoring environment-specific content#
Some messages have genuinely different content per environment. A banner that reads "You are viewing a staging environment" should only exist in the staging set. A skeleton placeholder like "[TODO: nav.home]" in dev should never reach production.
description: Home navigation labeltranslations: en: "[TODO: nav.home]" nl: "[TODO: nav.home nl]"description: Home navigation labeltranslations: en: Home nl: Homedescription: Home navigation labeltranslations: en: Home nl: HomeFor messages that should stay environment-specific and not be overwritten when content is promoted from dev, set promotable: false:
description: Banner shown on non-production environmentspromotable: falsetranslations: en: You are viewing the staging environment. Content here is not final.With promotable: false, running promote --from=staging --to=production will not touch this message in the production set.
Environment-specific segments#
Segments can also be environment-specific. A segment that matches internal users by email domain might be appropriate in staging for QA review but inappropriate in production where it could expose unreleased content to people it should not:
description: Internal QA userspromotable: falseconditions: attribute: email operator: endsWith value: "@yourcompany.com"Setting promotable: false on the segment keeps it from leaking into production when you run a promotion.
Promoting content between environments#
Once content in dev has been reviewed and is ready for staging:
$ npx messagevisor promote --from=dev --to=stagingReview the preview output first. Then apply the promotion:
$ npx messagevisor promote --from=dev --to=staging --applyAfter promotion, validate the destination set:
$ npx messagevisor lint --set=staging$ npx messagevisor test --set=staging$ npx messagevisor build --set=stagingThis sequence ensures that promoted content is valid and tests pass before the staging datafiles are published.
Building environment-specific datafiles#
Each set produces its own datafiles:
$ npx messagevisor build --set=productionThis writes:
datafiles/├── messagevisor-web-en-US.json (from production set)├── messagevisor-web-nl-NL.json└── messagevisor-mobile-en-US.jsonBuild output from sets/production/ is completely separate from output from sets/staging/. Applications running against staging point at staging datafiles; production applications point at production datafiles.
CI workflows per environment#
A complete CI setup validates and publishes each environment independently:
name: Messagevisoron: pull_request: push: branches: [main]jobs: validate-all: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx messagevisor lint - run: npx messagevisor test build-staging: runs-on: ubuntu-latest needs: validate-all if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx messagevisor build --set=staging - run: aws s3 sync datafiles/ s3://your-cdn/staging/messagevisor/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} build-production: runs-on: ubuntu-latest needs: validate-all if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx messagevisor build --set=production - run: aws s3 sync datafiles/ s3://your-cdn/production/messagevisor/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}Testing a specific environment#
When debugging a promotion or investigating a difference between environments, you can scope your test run to a specific set:
$ npx messagevisor test --set=staging$ npx messagevisor test --set=production --keyPattern=checkoutThis is faster than testing all sets and gives you targeted confidence before a deploy.
Checking divergence between environments#
To understand what content differs between staging and production before a promotion:
$ npx messagevisor promote --from=staging --to=production --showUnchangedThe --showUnchanged flag includes messages that would not be affected, giving you the full picture of what is and is not in sync.
Audit trail for promotions#
When you want a durable record of what moved between environments - useful for compliance reviews or change approval workflows - use the --audit flag:
$ npx messagevisor promote --from=staging --to=production --apply --audit=markdownThe generated audit file lists every message and override that was added, updated, or unchanged in the destination set. Commit it to your repository or attach it to your change management ticket.

