Data Providers
Lexis uses a provider system for loading translation data from various sources.
| Provider | ID | Capabilities | Use Case |
|---|---|---|---|
| JSON | json | Read, Write, Offline, Sync | Default, version control friendly |
| CSV | csv | Read, Write, Offline, Sync | Spreadsheet workflows |
| PO | po | Read, Write, Offline, Sync | GNU Gettext compatibility |
| Google Sheets | googlesheets | Read, Write* | Collaborative translation |
*Write capability requires API key; public sheets are read-only.
The Google Sheets provider enables collaborative translation workflows by loading translations directly from a Google Spreadsheet. There are two access modes with different requirements.
Access Modes
| Mode | Requirement | Best For |
|---|---|---|
| Public (Published to Web) | Sheet must be published to web | Quick setup, no API key needed |
| API Key | Google Cloud API key | Private sheets, full metadata access |
Important
"Shared with anyone with the link" is NOT the same as "Published to web". Public access without an API key requires the sheet to be published to the web.
Option 1: Public Access (No API Key)
For quick setup without API credentials, publish your sheet to the web:
How to Publish a Google Sheet:
- Open your Google Sheet in the browser
- Go to File → Share → Publish to web
- In the dialog:
- Select Entire Document or specific sheets
- Choose Web page or CSV format (either works)
- Click Publish
- Confirm when prompted
Limitations of Public Access:
- Sheet must remain published (anyone with URL can view)
- Cannot enumerate all sheet names automatically (uses common defaults: Sheet1, Translations, Localization, Strings)
- No push/write support - public access is read-only; use an API key for push operations
Inspector Setup:
- In LocalizationSettings, set Provider to Google Sheets
- Enter your Spreadsheet ID (from the URL)
- Check Is Public
- Click Fetch to load available sheets
- Select your sheet from the dropdown
Option 2: API Key Access
For private sheets or full functionality, use a Google Cloud API key:
How to Get an API Key:
- Go to Google Cloud Console
- Create a new project or select existing
- Enable the Google Sheets API:
- Go to APIs & Services → Library
- Search for "Google Sheets API"
- Click Enable
- Create an API key:
- Go to APIs & Services → Credentials
- Click Create Credentials → API Key
- (Recommended) Restrict the key to Google Sheets API only
- Copy the API key
Benefits of API Key:
- Works with private sheets (shared with specific users)
- Full sheet name enumeration in the Fetch dropdown
- Push/write support for syncing translations back to Google Sheets
Inspector Setup:
- In LocalizationSettings, set Provider to Google Sheets
- Enter your Spreadsheet ID (from the URL)
- Uncheck Is Public (or leave unchecked)
- Enter your API Key in the API Key field
- Click Fetch to load all available sheets
- Select your sheet from the dropdown
Security Warning Never commit API keys to version control. The API key field in the Inspector stores keys in EditorPrefs (not in the asset). For CI/CD pipelines, set the
LEXIS_GOOGLE_API_KEYenvironment variable.
Multi-Sheet Configuration
Lexis supports syncing multiple Google Sheets tabs, where each tab maps to a StringTable by its TableId.
How Sheet-to-Table Mapping Works:
- Google Sheet tab "UI" → StringTable with TableId "UI"
- Google Sheet tab "Dialogues" → StringTable with TableId "Dialogues"
- If no matching StringTable exists, one is created automatically during sync
Configuring Multiple Sheets:
API Key Mode (Recommended):
- Enter your Spreadsheet ID and API Key
- Click Fetch to load available sheets
- Check the boxes next to sheets you want to sync
Public Mode:
- Enter your Spreadsheet ID
- Use the + Add Sheet button to add sheet names
- Enter each sheet name manually (must match exactly)
Inspector UI:
Sheet Names (each sheet maps to a StringTable by TableId)
├── [x] UI
├── [x] Dialogues
├── [ ] Metadata (unchecked = not synced)
└── [x] TutorialsSync Operations
Pull Selected Table:
- Pulls from the sheet matching the selected StringTable's TableId
- Performs true sync: adds new entries, updates modified entries, and deletes entries that no longer exist in the source
- Shows a consent dialog if changes are detected
Pull All Sheets:
- Pulls from all configured sheets at once
- Creates new StringTables for sheets without matches
- Shows a consent dialog for each table with changes
- Options: Cancel, Skip, Overwrite, Overwrite All
Push Selected Table:
- Pushes the current StringTable to the configured provider
- For Google Sheets, requires an API key (not supported for public sheets)
Push All Tables:
- Pushes all StringTables to the configured provider
- Useful for backing up or exporting all translations
Important
Push operations to Google Sheets require an API key with write permissions. Pushing to public (published-to-web) sheets is not supported because public access is read-only.
Consent Dialog: Before applying changes, Lexis shows a diff preview:
- Number of new entries to add (green)
- Number of entries to modify with before/after preview (yellow)
- Number of entries to delete (red) - entries that exist locally but not in the source
Sync Menu (gear icon dropdown in String Table Browser toolbar):
- Pull Selected Table - Sync current table from its matching sheet
- Pull All Sheets - Sync all configured sheets
- Push Selected Table - Push current table to the provider
- Push All Tables - Push all tables to the provider
- Configure Provider... - Open provider settings
Expected Sheet Format
Your Google Sheet should follow this structure:
| Key | en | de | fr | Comment |
|---|---|---|---|---|
| ui.welcome | Welcome! | Willkommen! | Bienvenue! | Main menu greeting |
| ui.start | Start Game | Spiel starten | Démarrer | |
| ui.quit | Quit | Beenden | Quitter |
- First column: Translation keys
- Header row: Locale codes (en, de, fr, etc.)
- Optional: Comment column for translator notes
Troubleshooting Google Sheets
| Error | Cause | Solution |
|---|---|---|
| "Access denied" | Sheet not published or wrong sharing | Publish to web (File → Share → Publish to web) |
| "Sheet must be published to web" | Using public mode but sheet is only shared | Publish to web, or provide an API key |
| "Invalid API key" | API key is incorrect or expired | Verify key in Google Cloud Console |
| "Could not detect sheet names" | Sheet name doesn't match defaults | Enter sheet name manually, or use API key |
| Empty data returned | Sheet has no content | Add data to your spreadsheet |
| "No Sheets Configured" | SheetNames list is empty | Add at least one sheet name in provider settings |
| Pull creates wrong table | Sheet name doesn't match TableId | Rename sheet tab or StringTable.TableId to match |
| Consent dialog keeps appearing | Changes detected between sheet and local | Click "Overwrite All" to apply to all remaining |
| "Push not supported" | Trying to push to public sheet | Push requires an API key; configure API Key mode |
| Entries unexpectedly deleted | Pull performs true sync with deletions | This is expected behavior; entries not in source are removed |
| Flag | Description |
|---|---|
Read | Can load translations |
Write | Can save translations |
LazyLoad | Load individual keys on demand |
Watch | Detect external file changes |
Offline | Works without network |
Sync | Supports synchronous operations |
BatchWrite | Efficient bulk saves |
{
"localeCode": "en",
"entries": {
"ui.welcome": {
"value": "Welcome!",
"comment": "Main menu greeting",
"maxLength": 50,
"tags": ["ui", "menu"]
},
"ui.start": {
"value": "Start Game"
}
}
}Key,en,de,Comment,MaxLength
ui.welcome,Welcome!,Willkommen!,Main menu,50
ui.start,Start Game,Spiel starten,,# Translator comment
#: source/menu.cs:42
msgctxt "menu"
msgid "Start"
msgstr "Starten"
msgid "One item"
msgid_plural "%d items"
msgstr[0] "Ein Element"
msgstr[1] "%d Elemente"Implement ILocalizationProvider for custom data sources:
public interface ILocalizationProvider
{
string ProviderId { get; }
string DisplayName { get; }
ProviderCapabilities Capabilities { get; }
UniTask InitializeAsync(ProviderConfig config);
UniTask<LocaleData> LoadLocaleAsync(string localeCode);
UniTask<string> GetTranslationAsync(string key, string localeCode);
UniTask SaveLocaleAsync(string localeCode, LocaleData data);
UniTask<string[]> GetAvailableLocalesAsync();
}