Skip to Content
LearnCode Review5 WhysTranslation Keys Used as Data

Translation Keys Used as Data

This learning is based on a real production incident where user-facing translation values were mistakenly used as part of a data contract, leading to a silent failure.

Let’s investigate how coupling user-facing translation values with a backend data contract led to an incident, and how we can spot and prevent this category of bugs during code review.

The Scenario

  1. The feature displays a list of Circle products so users can select which ones they want to give feedback on.
  2. The implementation uses the resolved translation value (e.g., “Wallets”) for both the checkbox’s visible label and the name/value submitted in the form.
  3. Later, an engineer updated some of the translation values (e.g., “Programmable Wallets” → “Wallets”) without realising those same strings were also submitted as form values.
  4. The form posts to a Google Docs form that expects exact string matches; the updated values no longer matched the column options and the submissions were silently rejected.
  5. Unit tests for the GraphQL submission were missing so business logic changes were not caught when the translation key values changed.
  6. No integration tests were in place to validate the data contract with the Google Form, so the breaking change shipped unnoticed.
  7. A hot-fix introduced a stable enum (e.g., wallets, scp, gas, …) for the name/value while keeping translation keys—and their visible values—solely for UI labels.
FeatureRequestProducts.tsx
const products = [ t`products.wallets`, t`products.scp`, t`products.gas`, t`products.cctp`, t`products.usdc`, t`products.eurc`, ]; return ( <Checkbox.Group name="products"> {products.map((item) => ( <Checkbox key={item} label={item} name={item} /> ))} </Checkbox.Group> );

PR Comment

Choose the comment that you think is the most constructive and helpful.

Click here to learn more

Key Lessons

1. Separate Presentation from Data

  • Translation keys belong in the UI layer only
  • Submitted data should use stable, locale-agnostic values (e.g., enums, IDs)
  • Avoid hidden coupling between localisation files and business logic

2. Testing Strategy

  • Unit tests alone won’t catch changes in translation keys
  • Integration or contract tests should validate data sent to the backend
  • End-to-end tests can guard against regressions in critical flows

3. Code Review Best Practices

  • Question architectural choices that blend presentation and data layers
  • Ask how changes affect downstream systems and data contracts
  • Verify that tests cover contract boundaries, not just happy paths

Tips for Reviewers

1. Validate Data Contracts

  • Ask how data contracts are enforced and validated, especially for external integrations.
  • What happens if the value sent from the UI changes?

2. Verify Data Stability

  • Verify that user-facing changes, like editing translation values, cannot inadvertently alter submitted data.
  • Look for places where display text is used as a data identifier.

3. Promote Stable Identifiers

  • Encourage the use of enums or other canonical constants for values sent over the wire.
  • Advocate for decoupling the submitted ID from the displayed label (e.g., { id: 'wallets', label: t('wallets_label') }).

Common Pitfalls to Avoid

1. Coupling Display Text with API Values

  • <Checkbox name={t('product.name')} />
  • <Checkbox name="PRODUCT_ID" label={t('product.name')} />

2. Assuming External Contracts are Lenient

  • ❌ “It’s just a Google Form, we can send whatever we want.”
  • ✅ “This external service expects an exact string match. The contract is rigid and must be respected.”

3. Relying Only on Component-Level Tests

  • ❌ “All the component tests pass, so the feature works.”
  • ✅ “The component is correct, but we need an integration test to verify the submission to the external service.”

Remember: Clean separation between presentation and data layers prevents surprises when translations change.

Last updated on