sheety_localization 0.6.0
sheety_localization: ^0.6.0 copied to clipboard
A package to generate localization files from Google Sheets. It uses the Google Sheets API to fetch translations and generates Dart localization files for use in Flutter applications.
Sheety Localization #
Sheety Localization is a Dart console script that generates ARB files (Application Resource Bundle) and corresponding Dart localization classes from a Google Sheets spreadsheet. It uses the Google Sheets API to fetch localization tables and produces everything you need to integrate internationalization into your Flutter project.
Features #
- Fetch data from Google Sheets via Service Account credentials.
- Generate ARB files for each language column (e.g.,
en,ru,fr). - Produce a "library barrel" file (e.g.,
localization.dart) that exports generated localization classes. - Generate Dart localization classes under a configurable folder (e.g.,
src/generated). - Flexible directory structure via command-line options:
- Specify credentials JSON path (
--credentials) - Provide the Google Spreadsheet ID (
--sheet) - Configure output directories for ARB files (
--arb) and generated Dart classes (--gen) - Customize ARB filename prefix (
--prefix) - Insert author, comments, last-modified timestamp, and context metadata into generated files
- Specify credentials JSON path (
- Automatic
dart formatof generated Dart files (enabled by default) - Default folder structure if you don't override:
credentials.jsonin the working directory- Library barrel file under
lib/localization.dart - ARB files under
lib/src/l10n/ - Generated Dart classes under
lib/src/generated/
TL;DR #
-
Create a properly formatted Google Sheet with headers:
label | description | meta | en | ru | ... (other locales). -
Generate a Service Account in Google Cloud, enable Sheets API, and share the sheet.
-
Activate Sheety Localization via
dart pub global activate sheety_localization. -
Run the generator:
dart pub global run sheety_localization:generate \ --credentials=credentials.json \ --sheet=<YOUR_SPREADSHEET_ID> \ --lib=lib \ --arb=src/l10n \ --gen=src/generated \ --prefix=app \ --format -
Import/Export the generated barrel file (
lib/localization.dart) in your Flutter project. -
Optionally automate translation formulas, conditional formatting, VS Code tasks, and CI pipelines.
Prerequisites #
-
Dart SDK >= 3.7.0 < 4.0.0
-
Service Account with Google Sheets API enabled:
- Create a new project in Google Cloud Console.
- Enable Google Sheets API for that project.
- Create a Service Account and download its credentials JSON (e.g.,
credentials.json). - Share your spreadsheet with the Service Account's email (visible in the credentials JSON).
-
Google Sheet laid out in the following format (each sheet/tab = one ARB file):
- First row (header) must contain:
label | description | meta | en | ru | es | de | ... (other locales) - Column definitions:
label- key (getter name) for your Dart codedescription- human-readable note (e.g., Button text on login screen)meta- JSON with metadata (e.g., plural rules, date formats, placeholders)en,ru,es,de- one column per locale
- First row (header) must contain:
Installation #
Activate the script globally so you can invoke the generate command from anywhere:
dart pub global activate sheety_localization
Note: If you already have
sheety_localizationactivated, rundart pub global activate sheety_localizationagain to upgrade to the latest version.
Usage #
After activation, run:
dart pub global run sheety_localization:generate --help
This will display the help message with all available options and their descriptions.
Basic Command #
dart pub global run sheety_localization:generate \
--credentials=credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--lib=lib \
--arb=src/l10n \
--gen=src/generated \
--prefix=app \
--format
Option Definitions
--credentials(or-c): Path to your Service Account JSON file. Defaults tocredentials.jsonin the current directory.--sheet(or-s) (required): The Google Spreadsheet ID (found in the sheet's URL, between/d/and/edit).--lib(or-o): Output root folder for the "library barrel" file (localization.dart). Defaults tolib.--arb(or-a): Relative path (under--lib) where ARB files will be written. Defaults tosrc/l10n.--gen(or-g): Relative path (under--lib) where generated Dart classes will be written. Defaults tosrc/generated.--author: String to insert as author in generated ARB files (e.g.,--author="Jane Doe <jane@example.com>").--comment: Description text to include in generated ARB files.--modified: Last modified timestamp in ISO 8601. Defaults to current UTC time.--last-modified/--no-last-modified: Toggle adding@@last_modifiedto ARB meta. Default: enabled. When disabled,--modifiedhas no effect.--context: Arbitrary string (e.g., version number) to embed in ARB files.--prefix: Prefix for ARB filenames (e.g.,appwill produceapp_en.arb,app_ru.arb, etc.). Defaults toapp.--header: Custom header comment for each generated Dart file. Defaults to// This file is generated, do not edit it manually!.--format/--no-format: Whether to rundart formaton generated Dart code. Enabled by default.--include-empty: Allow processing rows where Google Sheets API omits trailing empty locale cells. Missing translations are omitted per-locale (no key and no@metaentry). Default: disabled (rows with missing locale columns are skipped).
Integration Steps #
-
Prepare your Google Sheet
-
Create a new or reuse an existing Google Sheet.
-
For each sheet/tab within that spreadsheet, set up the header in the first row as follows:
label | description | meta | en | ru | es | de | ... (other locales). -
Each subsequent row represents one localized string:
label: Unique key (used as a Dart getter name).description: Short human-readable note (e.g.,"Login button text").meta: JSON string with any metadata (e.g.,{"plural":"one","other":"{count} items"}).en,ru,es,de, ...: One column per locale.
-
-
Create a Google Cloud project & Service Account
- Go to Google Cloud Console.
- Create a new project or select an existing one.
- Enable the Google Sheets API for that project.
- Create a Service Account under IAM -> Service Accounts.
- Grant it "Viewer" (or higher) access to the target spreadsheet.
- Download the JSON key file (e.g.,
credentials.json).
-
Share your spreadsheet with the Service Account
- In Google Sheets, click Share (top right).
- Add your Service Account's email (from the JSON file) with Viewer or Editor rights.
-
(Optional) Create a dedicated localization Dart package If you want to keep generated localization code in its own package, set up a new Flutter package, for example:
# packages/localization/pubspec.yaml name: localization description: "Localization" version: 0.0.1 environment: sdk: ">=3.7.0 <4.0.0" flutter: ">=3.29.3" dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter- Run
flutter pub get(ordart pub get). - Later, import this package as a dependency in your app, and export the generated
localization.dartbarrel file.
- Run
-
(Optional) Configure a VS Code task If you use VS Code, add a task to automatically generate localization on demand. Create (or edit)
.vscode/tasks.jsonin your package root:{ "version": "2.0.0", "tasks": [ { "label": "Generate Localization", "type": "shell", "command": [ "dart pub global activate sheety_localization", "&& dart pub global run sheety_localization:generate --credentials=credentials.json", "--sheet=1QgD5i0U-va3VrljXw8I3o8FxtMJAqrhk3ybbJ9O5mA4", "--lib=lib", "--arb=src/l10n", "--gen=src/generated", "--prefix=app", "--format" ], "options": { "cwd": "${workspaceFolder}/packages/localization" }, "problemMatcher": [] } ] }- Adjust
--sheet=<YOUR_SPREADSHEET_ID>to match your actual ID. - Change
cwdto point to your localization package location.
- Adjust
-
(Optional) Add automatic translations via Google Sheets formulas If you want to auto-fill missing translations, add a formula in your sheet cells. For example, in column
E2(assumingD2is the source text in English):=IF(ISBLANK(D2), "", GOOGLETRANSLATE(D2, "en", "ru"))This formula translates the English text to Russian whenever
E2is empty. Replace"ru"with any target locale code. -
(Optional) Conditional formatting to highlight missing or machine-translated cells
- Set up Conditional Formatting in Google Sheets:
- Select all cells in a language column (e.g., column
Efor Russian). - Use a rule like
=ISFORMULA(E2)to gray out any cell where a formula (i.e., machine translation) is used. - Use
=E2=""to highlight cells that are still empty (missing translation).
- Select all cells in a language column (e.g., column
- Set up Conditional Formatting in Google Sheets:
-
Run the generator In your terminal (inside the package folder containing
credentials.json), execute:dart pub global activate sheety_localization dart pub global run sheety_localization:generate \ --credentials=credentials.json \ --sheet=<YOUR_SPREADSHEET_ID> \ --lib=lib \ --arb=src/l10n \ --gen=src/generated \ --prefix=app \ --format \ --no-include-empty \ --no-last-modified- This will produce:
lib/localization.dart(exports all generated classes)- One ARB file per sheet (e.g.,
app_en.arb,app_ru.arb, etc.) underlib/src/l10n/ - One Dart file per locale under
lib/src/generated/(e.g.,app_localizations_en.dart,app_localizations_ru.dart, etc.)
- This will produce:
-
Add the generated localization package to your app
In your main Dart file (e.g.,
main.dart), import the generated localization and set up the localization delegates at MaterialApp:import 'package:localization/localization.dart'; MaterialApp( // Add generated localization delegate here: localizationsDelegates: const <LocalizationsDelegate<Object?>>[ AppLocalization.delegate, ErrorsLocalization.delegate, SignUpLocalization.delegate, ChatLocalization.delegate, SettingsLocalization.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: const <Locale>[ Locale('en'), Locale('ru'), // Add other supported locales here ], -
Use the generated localization classes in your app
You can now access localized strings from context under the MaterialApp, using the generated localization classes. For example:
String title = AppLocalization.of(context).title;
Directory Structure Example #
After you run the generator with default paths, your package might look like:
.vscode/
tasks.json
packages/
localization/
credentials.json
lib/
localization.dart <- Barrel file (exports generated classes)
src/
l10n/
app_en.arb
app_ru.arb
(other languages)
generated/
app_localizations_en.dart
app_localizations_ru.dart
(other languages)
pubspec.yaml
-
localization.dart:// GENERATED file: localization.dart // This file exports all generated localization classes. export 'src/generated/app_localizations_en.dart'; export 'src/generated/app_localizations_ru.dart'; // ... other exports -
app_en.arb(example):{ "@@locale": "en", "title": "Doctorina", "@title": { "description": "App title" }, "checkVersionUpdateNowButton": "Update Now", "@checkVersionUpdateNowButton": { "description": "Button text to update now" } // ... more key-value pairs for English } -
app_localizations_en.dart(example):// GENERATED CODE - DO NOT MODIFY BY HAND // Author: Jane Doe <jane@example.com> // Generated by sheety_localization on 2025-06-04T15:23:21.739113Z import 'package:flutter/widgets.dart'; import 'package:intl/intl.dart'; import 'package:intl/message_lookup_by_library.dart'; class AppLocalizationsEn { static Future<AppLocalizationsEn> load(Locale locale) { // load logic using ARB data } String get title => "Doctorina"; String get checkVersionUpdateNowButton => "Update Now"; // ... other getters }
(Actual generated Dart code may vary depending on your template and ARB contents.)
AI-Powered Localization #
In addition to generating ARB/Dart files, Sheety Localization can automatically translate missing cells in your Google Sheet using the OpenAI API.
Basic Command #
dart pub global run sheety_localization:localize \
--credentials=credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--token=<YOUR_OPENAI_API_KEY> \
--model=gpt-5-mini \
--batch=3 \
--workers=6
Localize Option Definitions #
--credentials(or-c): Path to your Service Account JSON file. Defaults tocredentials.json.--sheet(or-s) (required): The Google Spreadsheet ID.--token(or-t): OpenAI API key (e.g.sk-...).--token-file(or-f): Path to a file containing the OpenAI API key (alternative to--token).--model(or-m): OpenAI model to use. Defaults togpt-5-mini.--batch(or-b): Number of languages to translate per single API call. Defaults to3. Higher values are faster but may reduce quality for weaker models. Range: 1–20.--workers(or-w): Number of concurrent API requests. Defaults to6, max14.--timeout: Hard timeout of a single OpenAI request, in seconds. Defaults to120, range 10–900. A request that the model never finishes is aborted instead of stalling a worker forever.--ignore(or-i): Comma-separated list of regular expressions — not globs — matched against sheet titles; a sheet whose title matches any of them is skipped (e.g.^help$,^backend,^temp-). The match is unanchored, sobackendalso skipsbackend-monetization; anchor with^/$when you want an exact title. A glob-lookingtemp-*means "temp followed by any number of dashes" and will not do what you expect.--prompt(or-p): Path to a custom system prompt file for the AI model.
How Localization Failures Are Handled #
Only localization sheets are written to. A sheet is localized only when its header matches
label | description | meta | <source> | <locale> ...— that is, when the fourth column is a recognized source language. It does not have to be English:ru,de,pt_BRand any other known language code work just as well, and the rest of the row is translated from that language. Reference tables and notes kept in the same spreadsheet have ordinary data in the fourth column (e.g.Family,Region), which is not a language, so they are skipped with a warning rather than overwritten with translations.--ignoreremains available for sheets that do match the layout but should be left alone anyway.
Language models are unreliable on ambiguous or rare locale codes, so localize defends against that:
- Every locale code is spelled out for the model — name, native endonym and, for codes that are routinely misread, an explicit warning.
ukis sent asuk — Ukrainian (українська) — Ukrainian (Cyrillic script). NOT English and NOT "United Kingdom", so it can no longer come back as English. - A failed batch is split, not retried. If a request for a batch of languages fails — timeout, truncated or invalid JSON, garbage output — the batch is not re-sent as-is. Each language of that batch is retried on its own, so one problematic rare language cannot take its neighbours down with it.
- Every translation is validated before it is written: non-empty, ICU placeholders (
{name}) and markup tags preserved exactly, no leaked markdown fences, no runaway output. A translation that fails validation is retried alone; only that one language is affected. - Partial rows are still saved. Languages that succeeded are written to the sheet even if one of their neighbours never worked; the failed cell stays empty and is picked up on the next run.
Docker #
A minimal Docker image with AOT-compiled generate and localize binaries is available on DockerHub. No Dart SDK installation required.
Pull #
docker pull plugfox/sheety:latest
Generate localization files #
docker run --rm \
-v $(pwd)/credentials.json:/credentials.json:ro \
-v $(pwd)/lib:/output \
plugfox/sheety:latest \
--credentials=/credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--lib=/output \
--arb=src/l10n \
--gen=src/generated \
--prefix=app \
--no-format
Note:
--no-formatis recommended inside Docker sincedart formatis not available in the minimal image. Format the generated files on the host afterwards.
The default entrypoint is /bin/generate. Mount your credentials.json as read-only and the output directory as a volume.
AI-powered localization (translate missing cells) #
Override the entrypoint to use /bin/localize:
docker run --rm \
--entrypoint /bin/localize \
-v $(pwd)/credentials.json:/credentials.json:ro \
plugfox/sheety:latest \
--credentials=/credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--token=<YOUR_OPENAI_API_KEY> \
--model=gpt-5-mini \
--batch=3 \
--workers=6
Alternatively, pass the API key via a file:
docker run --rm \
--entrypoint /bin/localize \
-v $(pwd)/credentials.json:/credentials.json:ro \
-v $(pwd)/openai_token.txt:/token.txt:ro \
plugfox/sheety:latest \
--credentials=/credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--token-file=/token.txt
Custom system prompt for localization #
docker run --rm \
--entrypoint /bin/localize \
-v $(pwd)/credentials.json:/credentials.json:ro \
-v $(pwd)/my_prompt.txt:/prompt.txt:ro \
plugfox/sheety:latest \
--credentials=/credentials.json \
--sheet=<YOUR_SPREADSHEET_ID> \
--token=<YOUR_OPENAI_API_KEY> \
--prompt=/prompt.txt
Print help #
# generate help
docker run --rm plugfox/sheety --help
# localize help
docker run --rm --entrypoint /bin/localize plugfox/sheety:latest --help
Building the image locally #
# Clone the repository
git clone https://github.com/DoctorinaAI/sheety_localization.git
# Navigate into the project directory
cd sheety_localization
# Local build
docker build -t username/sheety:latest .
# Multi-platform build and push to DockerHub
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t username/sheety:latest \
-t username/sheety:0.4.2 \
--push .
Tips & Best Practices #
- Keep your ARB files under version control (for reference and manual tweaks).
- Do not commit service account JSON to public repos. Store
credentials.jsonsecurely, or inject it as CI secret. - Use
--author,--comment, and--modifiedflags to annotate generated files for auditing. - If you add or remove columns in the sheet (e.g., adding
frfor French), delete old ARB files or run the generator with--prefixset to a new value to force regeneration. - To regenerate only a subset of sheets, you can temporarily hide unwanted sheets in Google Sheets, or maintain separate spreadsheets per feature set.
- Use
--no-last-modifiedto keep ARBs stable in git diffs.
Note about missing cells in Google Sheets #
Google Sheets API may omit trailing empty cells in returned rows.
By default, such rows are skipped to avoid generating partially broken translations.
Use --include-empty to still generate available translations from these rows.
Sample VS Code Task #
Copy the following into .vscode/tasks.json to run generation from the command palette:
{
"version": "2.0.0",
"tasks": [
{
"label": "Generate Localization",
"type": "shell",
"command": [
"dart pub global activate sheety_localization",
"&& dart pub global run sheety_localization:generate --credentials=credentials.json",
"--sheet=1QgD5i0U-va3VrljXw8I3o8FxtMJAqrhk3ybbJ9O5mA4",
"--lib=lib",
"--arb=src/l10n",
"--gen=src/generated",
"--prefix=app",
"--format"
],
"options": {
"cwd": "${workspaceFolder}/packages/localization"
},
"problemMatcher": []
}
]
}
- Open Command Palette -> Tasks: Run Task -> Generate Localization.
Example: Automatic Google Translate Formula #
If you want to auto-fill missing translations from English to Russian:
=IF(ISBLANK(D2), "", GOOGLETRANSLATE(D2, "en", "ru"))
- Place this formula in cell
E2(under therucolumn). - Drag/fill down to apply to all rows.
- Cells with empty
enwill remain blank; otherwise, English text will be machine-translated to Russian.
Example: Conditional Formatting Rules #
-
Gray out machine translations
- Select entire column (e.g., column
Eforru). - Add a custom formula rule:
=ISFORMULA(E2). - Set fill color to light gray.
- Select entire column (e.g., column
-
Highlight empty cells (missing translations)
- Select entire column (e.g.,
E). - Add a custom formula rule:
=E2="". - Set fill color to red (or any noticeable color).
- Select entire column (e.g.,