Google Sheets
Google Sheets has a built-in GOOGLETRANSLATE formula that talks to Google Translate. Combined with Messagevisor's CSV export and import, it gives you a quick way to seed a new locale from an existing one without leaving the spreadsheet.
This guide walks through the full round trip: export a CSV with a source locale, open it in Google Sheets, fill a new column with GOOGLETRANSLATE, and import the result back into the project.
The output of GOOGLETRANSLATE is machine translation. Treat it as a first draft for a translator to review, not as production copy. The workflow still benefits from the usual Messagevisor safety net: pull request review, lint, tests, and the catalog before build.
When to use this#
This workflow is useful when you want to:
- bootstrap a new locale from an existing one to give translators something to start from
- get a rough draft for an internal preview before commissioning real translation work
- translate a small batch of new strings between scheduled translator handoffs
- experiment with how a UI looks in another language before committing budget to it
It is not a replacement for human translation. ICU plural and select syntax, brand names, and product-specific terms all need a human pass.
Step 1: Export a CSV with the source locale#
Export the messages you want to translate, including the source locale you will translate from:
$ npx messagevisor export \ --locale=en \ --target=web \ --output=exports/en-web.csvThe resulting CSV looks like:
messageKey,messageDescription,en,enStatusauth.signin,Sign in button label,Sign in,directauth.signout,Sign out button label,Sign out,directdashboard.welcome,Dashboard welcome heading,"Welcome back, {name}",directIf you already have some translations for the target locale and only want to fill the gaps, include the target locale and limit to untranslated rows:
$ npx messagevisor export \ --locale=en \ --locale=nl-NL \ --target=web \ --onlyUntranslated \ --output=exports/nl-NL-web.csvSee Export and import for the full flag reference, and the translator handoff advice.
Step 2: Open the CSV in Google Sheets#
In Google Drive, choose File ▸ Import ▸ Upload, pick the exported CSV, and select Replace spreadsheet. Make sure the separator type is set to Comma (or Detect automatically) so each CSV column lands in its own sheet column.
If you exported with --delimiter=";" or --bom, match those settings when importing into Sheets.
You should see something like this:
| A | B | C | D |
|---|---|---|---|
messageKey | messageDescription | en | enStatus |
auth.signin | Sign in button label | Sign in | direct |
auth.signout | Sign out button label | Sign out | direct |
dashboard.welcome | Dashboard welcome heading | Welcome back, {name} | direct |
Step 3: Add a target locale column#
Add a new column to the right for the locale you want to translate into. The column header must exactly match the locale key you want to import, for example nl-NL, de, or fr.
| A | B | C | D | E |
|---|---|---|---|---|
messageKey | messageDescription | en | enStatus | nl-NL |
The locale key in the column header is what Messagevisor's importer reads. It must match a locale file under locales/.
You can also add a nl-NLStatus column if you want to mirror the export shape, but the importer only needs the locale value column. Status columns are informational and are ignored on import.
Step 4: Translate the column with GOOGLETRANSLATE#
In the first body row of the new column (row 2 in the example above), enter:
=GOOGLETRANSLATE(C2, "en", "nl")The signature is GOOGLETRANSLATE(text, source_language, target_language). The language arguments use ISO 639-1 codes, not Messagevisor locale keys. So nl-NL becomes "nl", pt-BR becomes "pt", zh-Hant becomes "zh-TW", and so on.
Drag the fill handle down to apply the formula to every row, or copy the cell and paste it across the rest of the new column.
To make the source and target language explicit and easy to change, store them in their own cells and reference them:
F1: enG1: nlE2: =GOOGLETRANSLATE(C2, $F$1, $G$1)To let Sheets auto-detect the source language, pass "auto":
=GOOGLETRANSLATE(C2, "auto", "nl")For an empty source cell (which can happen on --onlyUntranslated exports for an existing target locale that also has gaps in the source), wrap the formula in IF so it leaves the cell empty rather than producing #VALUE!:
=IF(C2="", "", GOOGLETRANSLATE(C2, "en", "nl"))Step 5: Handle ICU and interpolation placeholders#
GOOGLETRANSLATE does not understand Messagevisor's message syntax. It will sometimes:
- translate placeholder names inside
{}(turning{name}into a translated word) - reorder or drop ICU
plural/selectkeywords - mangle nested ICU expressions like
{count, plural, one {# item} other {# items}}
Before importing, scan the new column for rows where placeholders look different from the source. The simplest checks:
- Count braces. Source
{and}counts should match the target. - Confirm placeholder names are unchanged.
{name}should still be{name}, not a translated word. - Look for ICU keywords like
plural,select,selectordinal,number,date,time. These should appear unchanged in the target string.
For ICU-heavy projects, consider exporting only simple messages for this workflow and handing ICU messages to a human translator. You can split exports by message namespace with --includeMessages:
$ npx messagevisor export --includeMessages="marketing*" --locale=en --output=exports/marketing-en.csvStep 6: Export the sheet back to CSV#
Once the new column is filled in, choose File ▸ Download ▸ Comma-separated values (.csv). Save the file somewhere you can run the CLI from, for example exports/nl-NL-web.csv.
Step 7: Preview the import#
Always preview before applying:
$ npx messagevisor import exports/nl-NL-web.csv --locale=nl-NLWithout --apply, the CLI reports what would change: changed messages, changed overrides, skipped rows, skipped cells, and warnings. The --locale=nl-NL flag tells the importer to only read the nl-NL column and ignore the source en column.
Read the summary carefully. If you see unexpected Skipped rows or large numbers of warnings, open the CSV in a text editor or in Sheets and look for the problem before applying.
Step 8: Apply the import#
When the preview looks right:
$ npx messagevisor import exports/nl-NL-web.csv --locale=nl-NL --applyThe importer writes translations into the existing messages/ files. Each touched message file gains a nl-NL entry in its translations map (and possibly under matching overrides).
Step 9: Validate and review#
Run the rest of the authoring loop:
$ npx messagevisor lint$ npx messagevisor test$ npx messagevisor catalogThe catalog is the easiest way to spot wrong-tone translations, ICU placeholder damage, or strings that no longer fit their UI slot. For RTL targets, see RTL language support.
Then commit the changes and open a pull request. The diff will show the new locale entries inside each message file, which makes review focused on the translations themselves.
Tips and variations#
Use one sheet per locale#
For multiple target locales, create one tab per locale in the same workbook and a separate CSV per import. Keeping each target locale in its own file means you can --apply them independently and revert one without touching the others.
Add a reviewer notes column#
Add a notes column to the right of the locale columns to capture reviewer comments. Messagevisor's importer ignores unknown columns, so notes live in the CSV without polluting the project.
Combine with --onlyDirectlyUntranslated#
For regional locales that inherit from a parent (en-GB from en, nl-BE from nl), export with --onlyDirectlyUntranslated so the sheet only contains rows where a direct regional translation is missing. Use --prune on import so values that match the inherited parent are dropped instead of being written as direct duplicates.
Use the catalog to verify ICU output#
After import, open the catalog and look at the message under the target locale. The catalog renders ICU messages with example values, so plural and select bugs caused by machine translation show up quickly.
Re-translate selected rows#
To re-translate just a few rows after a copy change, export with --includeMessages scoped to those keys, follow the same flow, and import only that subset. The importer leaves untouched messages alone.
What about quality?#
GOOGLETRANSLATE produces machine translation. It is fast and free, but it is not native-speaker copy. For anything user-facing in production, treat the imported translations as a first draft and route them through a translator or a reviewer who reads the target language fluently.
For a workflow that involves an LLM agent handling the export, translation, and import end-to-end, see AI translations.

