Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
775e50e
translate primevue components
latin-panda Mar 12, 2026
6b82dfd
Merge branch 'main' of https://github.com/getodk/web-forms into add-t…
latin-panda Mar 19, 2026
7622e56
add translation logic
latin-panda Mar 23, 2026
30da347
Merge branch 'main' of https://github.com/getodk/web-forms into add-t…
latin-panda Mar 23, 2026
b309dac
fixes lint
latin-panda Mar 23, 2026
dfab101
Allows locale on form load error
latin-panda Mar 23, 2026
77e442e
add translations
latin-panda Mar 23, 2026
d480139
add translations
latin-panda Mar 23, 2026
2e50a81
unit tests
latin-panda Mar 23, 2026
a1a43f6
changeset
latin-panda Mar 23, 2026
cc4b018
updates comments key
latin-panda Mar 24, 2026
56f65f5
Adds spanish translation from transifex
latin-panda Mar 24, 2026
1fb49b7
fix unit tests
latin-panda Mar 24, 2026
c4e56f5
updates release step
latin-panda Mar 24, 2026
0668877
Updates translation docs
latin-panda Mar 24, 2026
0008abb
Move documentation
latin-panda Mar 24, 2026
9c7213b
Merge branch 'main' of https://github.com/getodk/web-forms into add-t…
latin-panda Mar 25, 2026
cfc3de8
polishing code
latin-panda Mar 25, 2026
4c0a828
improve spanish
latin-panda Mar 25, 2026
4334a2f
Merge branch 'main' of https://github.com/getodk/web-forms into add-t…
latin-panda Mar 26, 2026
d1c9325
feedback part 1: translation keys
latin-panda Mar 26, 2026
a9f9dca
feedback part 2: common translation keys
latin-panda Mar 26, 2026
2f72ed4
feedback part 3: adds readme file and warning when using cli tool wrong
latin-panda Mar 26, 2026
25a33e1
feedback part 4: transifex cli config
latin-panda Mar 26, 2026
2c6dcc7
feedback part 5: rename translate function
latin-panda Mar 26, 2026
8d68187
fixes unit tests
latin-panda Mar 26, 2026
c49a97f
feedback part 6: regional locales
latin-panda Mar 27, 2026
29a6a19
updates documentation
latin-panda Mar 27, 2026
2b29025
updates spanish translation
latin-panda Mar 27, 2026
63e28e4
Merge branch 'main' of https://github.com/getodk/web-forms into add-t…
latin-panda Mar 30, 2026
60852f7
feedback
latin-panda Mar 30, 2026
e6da4c8
Merge branch 'main' of https://github.com/getodk/web-forms into add-t…
latin-panda Mar 31, 2026
2a50d0e
fixes merge conflict
latin-panda Mar 31, 2026
a2ef3a7
Merge branch 'main' of https://github.com/getodk/web-forms into add-t…
latin-panda Apr 7, 2026
6899aaa
updates the Transifex project name
latin-panda Apr 7, 2026
e813a7a
fix merge conflict
latin-panda Apr 7, 2026
2f6014c
feat(#332): Prioritize form designer's default language (#763)
latin-panda Apr 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/solid-keys-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@getodk/xforms-engine': minor
'@getodk/web-forms': minor
---

Adds translation support.
9 changes: 9 additions & 0 deletions .tx/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[main]
host = https://app.transifex.com

[o:getodk:p:web_forms:r:strings]
file_filter = packages/web-forms/locales/strings_<lang>.json
source_file = packages/web-forms/locales/strings_en.json
source_lang = en
type = STRUCTURED_JSON
minimum_perc = 0
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ We will be adding color and more styling soon. We intend to expose a way to do b

Thank you for contributing! Follow these guidelines for smooth collaboration.

### Translations

Translations are managed on Transifex. Translators can contribute at: https://app.transifex.com/getodk/web_forms

For developers, see [TRANSLATIONS.md](./packages/web-forms/TRANSLATIONS.md) for details on how UI strings are managed and how to add or update translations.

### Requirements

- [Volta](https://volta.sh/) to ensure consistent `node` and `yarn` versions.
Expand Down Expand Up @@ -510,9 +516,10 @@ If you'd like to try the functionality available on `main`, see the preview [on

1. Run `yarn changeset version` to generate changelog files and version bumps from the changeset files.
2. Run `yarn install` to update `yarn.lock` with the new versions.
3. Verify that the changelogs look good, commit changes, open a PR, and merge the PR.
4. Push tags for each package in the format `@getodk/<package>@x.x.x`. A GitHub action will publish the packages on NPM.
5. Update dependencies to kick off the new release cycle.
3. Update translations by running `yarn translations:pull` in the root directory.
4. Verify that the changelogs look good, commit changes, open a PR, and merge the PR.
5. Push tags for each package in the format `@getodk/<package>@x.x.x`. A GitHub action will publish the packages on NPM.
6. Update dependencies to kick off the new release cycle.

### Patch release process

Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default defineConfig(
'packages/tree-sitter-xpath/bindings/**/*',
'packages/tree-sitter-xpath/types/**/*',
'packages/web-forms/dist-demo/**/*',
'packages/web-forms/scripts/**/*',
'packages/xforms-engine/api-docs/**/*',
'**/vendor',
],
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"format:readme-only": "prettier -w README.md",
"format:checkonly": "prettier -c \"**/*\" --ignore-unknown",
"lint": "eslint . --report-unused-disable-directives --cache",
"feature-matrix": "node scripts/feature-matrix/render.js"
"feature-matrix": "node scripts/feature-matrix/render.js",
"translations:pull": "tx pull -a -f --mode translator"
},
"dependenciesMeta": {
"tree-sitter": {
Expand Down Expand Up @@ -81,5 +82,8 @@
"resolutions": {
"tree-sitter": "0.22.1",
"@asgerf/dts-tree-sitter/tree-sitter": "0.22.1"
},
"devDependencies": {
"@transifex/cli": "^7.1.5"
}
}
17 changes: 17 additions & 0 deletions packages/common/src/fixtures/date-and-time/date-and-time.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@
<value>À quelle heure prends-tu ton premier repas ?</value>
</text>
</translation>
<translation lang="Spanish (es)" default="true()">
<text id="/data/dates/survey_date:label">
<value>¿Cuando está completando este formulario?</value>
</text>
<text id="/data/dates/date_of_birth:label">
<value>¿Cuál es su fecha de nacimiento?</value>
</text>
<text id="/data/dates/fruits_date:label">
<value>¿Cuando fué la ultima vez que comió frutas?</value>
</text>
<text id="/data/dates/vegetables_date:label">
<value>¿Cuando fué la ultima vez que comió verduras?</value>
</text>
<text id="/data/dates/time:label">
<value>¿A que hora es su primera comida?</value>
</text>
</translation>
</itext>
<instance>
<data id="date" version="2025020401">
Expand Down
14 changes: 3 additions & 11 deletions packages/scenario/src/assertion/extensions/answers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UnreachableError } from '@getodk/common/lib/error/UnreachableError.ts';
import { getBlobText } from '@getodk/common/lib/web-compat/blob.ts';
import { constants, type ValidationCondition } from '@getodk/xforms-engine';
import { type ValidationCondition } from '@getodk/xforms-engine';
import { assert, expect } from 'vitest';
import { ComparableAnswer } from '../../answer/ComparableAnswer.ts';
import { ExpectedApproximateUOMAnswer } from '../../answer/ExpectedApproximateUOMAnswer.ts';
Expand Down Expand Up @@ -40,24 +40,16 @@ const assertAnswerResult: AssertAnswerResult = (value) => {
};

const matchDefaultMessage = (condition: ValidationCondition) => {
const expectedMessage = constants.VALIDATION_TEXT[`${condition}Msg`];

return {
node: {
validationState: {
[condition]: {
valid: false,
message: {
origin: 'engine',
asString: expectedMessage,
},
message: null,
},
violation: {
condition,
message: {
origin: 'engine',
asString: expectedMessage,
},
message: null,
},
},
},
Expand Down
58 changes: 58 additions & 0 deletions packages/web-forms/TRANSLATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Translations

## How it works

Translation strings are defined in `.i18n.json` files co-located with their components. At build time, these files are merged into `locales/strings_en.json`, which serves as the English baseline and is eagerly loaded at runtime. Translations for other locales are loaded lazily when the user switches language.

Transifex automatically pulls the source strings from `locales/strings_en.json` daily. Translation files are synced back into the project just before each release.

## Key naming convention

Keys follow a 3-part dot-separated pattern:

```
component.feature.type
```

- **component**: camelCase name of the Vue component that owns the string (e.g. `odk_web_forms`)
- **feature**: the feature or section within that component (e.g. `submit`, `validation`)
- **type**: what kind of string it is. Use one of:
- `label`: button or field label
- `title`: heading or title
- `placeholder`: input placeholder
- `message`: informational message
- `error`: error message

An easy way to remember: **who → where → kind** (which component? which feature? what type of string?).

### Examples

| Key (snake_case) | Description |
| -------------------------------- | ----------------------- |
| `odk_web_forms.submit.label` | Submit button label |
| `odk_web_forms.validation.error` | Validation error banner |

## Adding a string

1. Create or open the `.i18n.json` file next to the component.
2. Add an entry using the `component.feature.type` key convention (snake_case):

```json
{
"my_component.some_feature.label": {
"string": "English text here",
"developer_comment": "Context for translators: when and where this string appears."
}
}
```

3. Run `build:translations` to regenerate `locales/strings_en.json`. This also runs automatically as part of the build.
4. Use the string in the component via `t('my_component.some_feature.label')`.

## Developer comments

The `developer_comment` optional field is for translators. Explain:

- Where the string appears in the UI
- Any placeholders (e.g. `{count}` is the number of violations)
- Any constraints (e.g. keep it short, it appears on a button)
4 changes: 4 additions & 0 deletions packages/web-forms/locales/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Locales

**Do not edit these files directly.** The `strings_en.json` file is auto-generated by `scripts/merge-translations.js`, and the other `strings_[locale].json` files are pulled from the Transifex project.
The source strings are defined in `.i18n.json` files.
Loading