sheet-i18n
v1.10.2
Published
All-in-one i18n toolchain: seamlessly integrate Google Sheets, CLI, and react-i18n for easy translation workflows.
Maintainers
Readme
sheet-i18n 
sheet-i18n is a powerful and flexible library designed to streamline the process of managing and utilizing translations stored in Google Sheets (planned to support other spreadsheets soon).
It serves as a bridge between your translation data and your application, offering an end-to-end solution for exporting, managing, and integrating translations into your projects.
The sheet-i18n ecosystem is divided into three main packages:
sheet-i18n/react@sheet-i18n/clisheet-i18n/importer
Installation
npm install sheet-i18n
npm install @sheet-i18n/cli -D@sheet-i18n/react
@sheet-i18n/react ✨
The client-side i18n library subpackage of sheet-i18n.
This package provides tools to handle translations in React applications using context and hooks. It simplifies internationalization workflows by offering functions and components to manage, access, and use locale-specific translation data.
✨ Package Introduction
I18nStore: Core Store Creation Class for managing translation data.createI18nContext: React Context to generate providers and hooks for translation.IntlProvider: React Translation Provider for managing current locale.useTranslation: Client Side Translation Hook for easy access to translation messages on the client side.getTranslation: Static Translation Function for easy access to translation messages on Static module files.ruleFactory: Rule Factory Function for creating custom translation rules with conditional logic.
🚀 Getting Started(Manually)
⚡ Strongly recommended to use the init CLI for setup 👉 Please follow the Init CLI
If you don't want to use the CLI, you can follow the
Manual Setupbelow.
step 1. Define Translation Data
Prepare locale JSON files:
// en.json
{
"header": {
"login": "Login",
"logout": "Logout"
}
}
// ko.json
{
"header": {
"login": "로그인",
"logout": "로그아웃"
}
}step 2. Initialize i18nStore
this store will be used as a core translations module.
import en from './en.json';
import ko from './ko.json';
import { I18nStore } from '@sheet-i18n/react';
export const i18nStore = new I18nStore({
supportedLocales: ['ko', 'en'],
defaultLocale: 'ko',
/** if you want to load translation data statically */
localeSet: {
ko,
en,
},
/** if you want to load translation data dynamically */
// dynamicLoaders: {
// ko: () => import('./ko.json'),
// en: () => import('./en.json'),
// jp: () => import('./jp.json'),
// cn: () => import('./cn.json'),
// },
});step 3. Create i18n Context
import { i18nStore } from './file-path-of-i18nStore-initiated';
import { createI18nContext } from '@sheet-i18n/react';
export const { IntlProvider, useTranslation, getTranslation } =
createI18nContext(i18nStore);step 4. Mount Intl Context Provider in your App
import React from 'react';
import { IntlProvider } from './i18nContext';
const App = () => {
const [locale, setLocale] = useState('en');
return (
<IntlProvider currentLocale={locale}>
<YourComponent />
</IntlProvider>
);
};step 5. Use Translations
import React from 'react';
import { useTranslation } from './i18nContext';
const YourComponent = () => {
const { t } = useTranslation('header');
return (
<div>
<button>{t('login')}</button>
<button>{t('logout')}</button>
</div>
);
};📦 Base API Reference
I18nStore(core)
The I18nStore manages translation states, ensuring consistency across locales.
Parameters:
🧠 I18nStore Configuration Options
| Option | Type | Required | Description |
| ------------------ | ---------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| supportedLocales | string[] | ✅ | List of supported locale codes. |
| defaultLocale | string | ✅ | Default locale to use when no match is found. Must be included in supportedLocales. |
| localeSet | Record<string, object> | | (Static Loading Option) Preload all translation data for each locale in memory. Keys must match supportedLocales. |
| dynamicLoaders | Record<string, () => Promise> | | (Recommended for large locale sets) Dynamically load translation data on demand, reducing initial bundle size. |
| typeSafe | boolean | | Enable strict key checking and autocompletion (default: false). |
| |
💡 typeSafe? I18nStore doesn't enforce adherence to your locale JSON definitions by default. This means that you can add translation data even if it isn’t pre-defined in your locale JSON files. However, if you prefer to enforce strict type-safety, you can manually enable the typeSafe option which allows you to notice the auto-completed list in translation data.
But to enable correct type checking,
the full localeSet must be defined according to the supportedLocales in i18nStoreAs a result, type auto-completion relies on having the complete localeSet defined at initialization. This means that using dynamicLoaders to fetch locales conditionally at runtime can be limiting when strict type-safety is enabled.
// typeSafe: true const YourComponent = () => { // "useTranslation" shows the autocompletion suggestions const { t } = useTranslation('header'); return ( <div> {/* "t" function shows the autocompletion suggestions */} <button>{t('login')}</button> </div> ); };⚠️ Caveats:
supportedLocalesmust be an array of locale strings.defaultLocalemust exist insupportedLocales.
🚀 Static vs Dynamic Loading
You can configure how translations are loaded:
Static (
localeSet)
Load all translations at once. Ideal for small projects or limited locale sets.Dynamic (
dynamicLoaders) ✅ Recommended
Load only the locale needed, reducing the bundle size dramatically.
Useful when supporting many languages or large translation files.
export const i18nStore = new I18nStore({
supportedLocales: ['en', 'fr', 'ko'],
defaultLocale: 'en',
dynamicLoaders: {
en: () => import('./en.json'),
fr: () => import('./fr.json'),
ko: () => import('./ko.json'),
},
});💡 When both localeSet and dynamicLoaders are defined, sheet-i18n automatically merges both sources — static data loads immediately, while dynamic data supplements on-demand.
🎯 Why Dynamic Loaders?
For projects with many locale datasets, it's often preferable to load translation data on demand rather than all at once. It is better using dynamicLoaders to enable this conditional loading strategy.
- ✅ Reduces the initial JavaScript bundle size.
- ✅ Keeps only required locales in memory.
- ✅ Works well with code-splitting and SSR/CSR.
- ✅ Enables lazy-loading for translations.
Example:
export const i18nStore = new I18nStore({
supportedLocales: ['ko', 'en'],
defaultLocale: 'ko',
/** if you want to load translation data statically */
localeSet: {
ko,
en,
},
/** if you want to load translation data dynamically */
// dynamicLoaders: {
// ko: () => import('./ko.json'),
// en: () => import('./en.json'),
// jp: () => import('./jp.json'),
// cn: () => import('./cn.json'),
// },
});createI18nContext
Generates React context, including the IntlProvider and useTranslation.
Parameters:
i18nStore: Instance ofI18nStore.plugins(optional): Plugin configuration object.rules(optional): Custom rules object for conditional translations. See Plugins section for details.
⚠️ Caveats:
i18nStorepassed to createI18nContext must be an instance of I18nStore.- custom object is not allowed to be passed to createI18nContext.
Example:
const { IntlProvider, useTranslation } = createI18nContext(i18nStore);IntlProvider
A Context provider to provide translations to child components.
Properties:
currentLocale(optional):- A key representing the current locale to use for translations.
- If not provided, the user's preferred language is automatically detected based on the browser setting.
- The fallback is the default locale in i18nStore if the detected user-preferred language is not unsupported in the
supportedLocales.
children: React children.
Example:
// Add currentLocale if you want to manually handle the locale
// This example is Next.js app routing
interface LayoutProps {
params: {
locale: Locale;
};
}
export default function Layout({
children,
params,
}: PropsWithChildren<PageProps>) {
return <IntlProvider currentLocale={params.locale}>{children}</IntlProvider>;
}useTranslation
A hook to access translations in your components.
Parameters:
sheetTitle: The sheet title of the translation group.
Example:
const { t } = useTranslation('header');
const translatedMessage = t('login');getTranslation
A function to access translations in the environment where cannot use react context and hooks.
Parameters:
sheetTitle: The sheet title of the translation group.
t Function
The t function is used to retrieve translation strings based on keys and optionally interpolate variables. It provides flexibility to include dynamic values, such as plain strings or React components, for enhanced localization capabilities.
Overview: t(), t.rule(), and t.dynamic()
| Method | Use Case | Key Type | When to Use |
| ----------------------------------- | ------------------------------------------ | ------------------------------------ | ------------------------------------------------------------------- |
| t(key, values?) | Standard translation with explicit keys | Static (known at build time) | When you know the translation key beforehand |
| t.rule(ruleKey, values?) | Conditional translation using custom rules | Determined by rule function | When you need conditional logic (pluralization, gender-based, etc.) |
| t.dynamic(id, values?, opts?) | Translation with runtime keys | Dynamic (from API, user input, etc.) | When the translation key comes from runtime data |
t() - Standard Translation
Retrieve translations for explicit keys that are known at build time.
Parameters:
key(string): The translation key to retrieve the localized string.values(object, optional): An object containing key-value pairs to interpolate into the translation string.
Examples:
const { t } = useTranslation('header');
// Simple translation
const loginText = t('login'); // "Login" or "로그인"
// Translation with interpolation
const welcomeMessage = t('{username} shown', { username: 'John Doe' });
// Result: "John Doe shown"
// Interpolation with React components
const message = t('{username} shown', { username: <Username /> });💡 Note: The values object can contain any type of data, including React components.
t.rule() - Conditional Translation with Custom Rules
Use custom rules for conditional translations based on dynamic values. This method is available when custom rules are registered via the createI18nContext plugins option.
Parameters:
ruleKey(string): The key of the registered custom rule.values(object, optional): An object containing values to be passed to the rule function and used for interpolation.
Examples:
// Assuming custom rules are registered
const { t } = useTranslation('landing');
// Call rule with values
const message = t.rule('plural', { age: 21 });
// The rule function determines which translation key to use based on age💡 Note: t.rule() works exactly like t() in terms of interpolation. If the translation key returned by the rule function contains {slot} placeholders, they will be automatically replaced using the values object passed to t.rule().
For more details, see the Plugins section.
t.dynamic() - Runtime Key Translation
Resolve translation keys that are not known at build time (e.g., values coming from API responses, user input, or other runtime sources).
Parameters:
id(string): A runtime string to match against the current locale's translation keys.values(object, optional): Interpolation values, identical tot().opts(object, optional): Formatter options.
Examples:
const { t } = useTranslation('header');
const { data } = useQuery(...queryOptions);
// Runtime key from API response
const deviceName = data?.device?.name; // e.g., "smartphone"
// Will translate the runtime key if it exists in the current locale
const label = t.dynamic(deviceName);
// If "smartphone" exists in locale JSON, returns translated value
// Otherwise, returns "smartphone" as fallback💡 Tip: Ensure your locale JSON contains possible runtime keys; otherwise t.dynamic will fall back to the provided id string.
📄 Best Practices of Translation utilities
A robust and flexible translation library designed for efficient handling of translations in React applications.
Features
- Client-Side Translation (Explicit Strings): Translate explicit string keys directly in your components.
- Client-Side Translation (Dynamic Strings): Dynamically match translations from JSON for unknown variables.
- Module Translation: Use translations in the place where the react context or hooks cannot be used.
Usage
1. Client-Side Translation (Explicit Strings)
Retrieve translations for explicit keys in your React components using useTranslation.
import React from 'react';
import { useTranslation } from './i18nContext';
const YourComponent = () => {
const { t } = useTranslation('header');
return (
<div>
<button>{t('login')}</button>
<button>{t('logout')}</button>
</div>
);
};2. Client-Side Translation (Dynamic Strings)
For scenarios where the string key is not explicitly known (e.g., coming from a server), use t.dynamic. Ensure that your translation JSON and sheet are updated to include the variable values.
The t.dynamic function will automatically match the values from the JSON with the provided arguments and display the result.
// App.tsx
import { useTranslation } from './i18nContext';
const { t } = useTranslation('header');
const { data } = useQuery(...queryOptions);
const deviceName = data?.device?.name;
return <div>{t.dynamic(deviceName)}</div>;3. Module Translation
The getTranslation function allows you to use translations outside of React components, such as in static configuration files, constants, or utility modules.
✅ Usage Scenarios
[Scenario 1]: Context where the call expression of t function is evaluated at runtime
- For the common example, t call expression in a function module
- This behaves the same as
useTranslation'stfunction.
Example
// module.ts
import { getTranslation } from './i18nContext';
const { t } = getTranslation('header');
export function getStatusCategory {
return [t('Total Energy Usage'), t('Total Waste Usage')];
};
// App.tsx
// the "t" call expression is evaluated at function call timing
import { getStatusCategory } from './module';
export default function App() {
return getStatusCategory().map((item) => <div>{item}</div>);
}Result: The translation is resolved immediately during runtime.
[Scenario 2]: Context where the translation value is evaluated immediately
- If the module is imported dynamically in a client-side component, the evaluation order of
getTranslationandtcall expression may not be guaranteed. - Since JavaScript evaluates modules at import time, it may attempt to access translation values before
IntlProviderhas fully initialized the locale. - In this case, use
getTranslationandwatchCLI for easy translation workflow.
Example
// module.ts
// the "t" function from "getTranslation" is used as the "marker" of "watch" CLI
import { getTranslation } from './i18nContext';
const { t } = getTranslation('header');
export const STATUS_CATEGORY = [
t('Total Energy Usage'), // Marked
t('Total Waste Usage'), // Marked
];
// Pre-requisite: install @sheet-i18n/cli and run `npx sheet-i18n watch`
// You can register your translation data with ease
Detected Translations:
📄 [Sheet Title]: header
- Total Energy Usage
- Total Waste Usage
# You can proceed using the shortcuts below:
- Press <Shift + R> to register the detected changes. // <-- you can register your translation data
- Press <Ctrl + C> to cancel the watch command.
// App.tsx
// After update of translation data, t.dynamic will resolve the translations
// If you want to translate the text with type safety, use "t" function which works like t.dynamic after register and import your translation data.
import { STATUS_CATEGORY } from './module.ts';
export default function App() {
const { t } = useTranslation('header');
return (
<div>
{STATUS_CATEGORY.map((item, index) => {
return <div key={index}>{t.dynamic(item)}</div>;
})}
</div>
);
}🌐 Advanced Interpolation Strategies
sheet-i18n provides advanced interpolation strategies for different rendering environments. These strategies ensure optimal performance and user experience across various deployment scenarios.
🖥️ Client-Side Rendering (CSR) Interpolation
For client-side applications, sheet-i18n offers StorageBasedIntlProvider with LocaleStorageManager to handle persistent locale settings and interpolation seamlessly.
What is Persistent Locale Settings?
The StorageBasedIntlProvider provides persistent translation settings that automatically remember and restore the user's language preference across browser sessions. This means:
- Session Persistence: User's language choice persists even after closing and reopening the browser
- Cross-Tab Synchronization: Language changes are synchronized across all open tabs/windows
- Automatic Restoration: When users return to your app, their preferred language is automatically restored
- Fallback Handling: If stored preferences are invalid, the system gracefully falls back to default settings
Setup for CSR Interpolation
// i18nContext.ts
import { createI18nContext } from '@sheet-i18n/react';
import { i18nStore } from './i18nStore';
export const {
StorageBasedIntlProvider,
useTranslation,
getLocaleStorageManager,
useLocaleStorage,
} = createI18nContext(i18nStore);
// Create a storage manager
// if you don't pass the storage service as an argument,
// it automatically utilize "LocalStorage" as the storage service
export const localeStorageManager = getLocaleStorageManager();Implementation Example
// App.tsx
import React from 'react';
import { StorageBasedIntlProvider, localeStorageManager } from './i18nContext';
const App = () => {
return (
// StorageBasedIntlProvider automatically re-render
// if storage local data is changed
<StorageBasedIntlProvider
storageManager={localeStorageManager}
fallbackUI={<div>Loading...</div>} // optional
>
<YourApp />
</StorageBasedIntlProvider>
);
};
// Component with persistent locale switching
const WelcomeComponent = () => {
const { t } = useTranslation('welcome');
const { locale } = useLocaleStorage(localeStorageManager);
return (
<div>
<h1>{t('welcome_message')}</h1>
<p>Current language: {locale}</p>
{/* Language change handler */}
<button onClick={() => localeStorageManager.setLocale('ko')}>
한국어
</button>
<button onClick={() => localeStorageManager.setLocale('en')}>
English
</button>
</div>
);
};How Persistence Works
- Initial Load: When the app starts,
StorageBasedIntlProviderautomatically checks for previously saved locale preferences - Automatic Restoration: If a saved preference exists, it's automatically applied; otherwise, the default locale is used
- Real-time Updates: When users change the language, the new preference is immediately saved to storage and re-render the page
Custom Storage Service
For advanced use cases, you can implement custom storage services to control how locale preferences are persisted:
// customStorageService.ts
import { IStorageService } from '@sheet-i18n/react';
export class CustomStorageService implements IStorageService<string> {
private storage = new Map<string, string>();
getItem(key: string): string {
// Retrieve persisted locale preference
return this.storage.get(key) || 'en';
}
setItem(key: string, value: string): boolean {
// Persist locale preference for cross-session memory
this.storage.set(key, value);
return true;
}
removeItem(key: string): boolean {
// Clear persisted locale preference
return this.storage.delete(key);
}
clear(): boolean {
// Clear all persisted preferences
this.storage.clear();
return true;
}
}
// Usage with custom persistence strategy
const customStorageManager = getLocaleStorageManager(
new CustomStorageService()
);Storage Options
- LocalStorage (Default): Persists across browser sessions and device restarts
- SessionStorage: Persists only for the current browser session
- Custom Storage: Implement your own persistence strategy (e.g., server-side storage, encrypted storage)
- Memory Storage: No persistence (for testing or temporary use cases)
🔌 Plugins
sheet-i18n provides an extensible plugin system that allows you to customize translation logic and easily implement conditional translations or complex translation rules.
🎯 Custom Rules Plugin
Custom Rules is a plugin that enables dynamic selection of different translation keys based on conditions. This allows you to implement various scenarios such as pluralization, gender-based translations, age-based message changes, and more.
Key Features
- ✅ Conditional Translation: Automatically select different translation keys based on values
- ✅ Interpolation Support: Same
{slot}replacement functionality as thetfunction - ✅ Extensible: Combine multiple rules to implement complex logic
Key Points
import { ruleFactory } from '@sheet-i18n/react';
import { i18nStore } from './i18nStore';
// Step 1: Create factory service
// ruleFactory takes your i18nStore and returns an object with createRule method
const { createRule } = ruleFactory(i18nStore);
// Step 2: Use createRule to define custom rules
// `createRule` can take a rule function and support the current localeSet data use registered in the localeSet of `i18nStore`
// In the body of the rule function, you can defined the translation logic as you want
// 💡 Note: Highly recommended to return the translation key from the localeSet
// When you use `t.rule()` from the component, the translation key will be resolved and the translated text will be returned
// If you return the normal string, that string will be used as the translation key
// and If no matched translation key is found, the string will be used rendered
const myRule = createRule<{ count: number }>((values, localeSet) => {
if (values.count > 10) {
return localeSet.sheetTitle['many_items'];
}
return localeSet.sheetTitle['few_items'];
});Complete Flow Example
// Translation data example from your sheet
// en.json
{
...
"userSheet": {
"adult_message": "You are an adult. Your age is {age}",
"youth_message": "You are a youth. Your age is {age}"
}
}
// 1. Initialize i18nStore
const i18nStore = new I18nStore({
/* ... */
});
// 2. Create factory service (binds createRule to i18nStore)
const { createRule } = ruleFactory(i18nStore);
// 3. Define custom rules using createRule
export const customRules = {
ageRule: createRule<{ age: number }>((values, localeSet) => {
if (values.age > 20) {
return localeSet.userSheet['adult_message'];
}
return localeSet.userSheet['youth_message'];
}),
};
// 4. Register rules in context
const { useTranslation } = createI18nContext(i18nStore, { rules: customRules });
// 5. Use in components
const { t } = useTranslation('userSheet');
const translatedText = t.rule('ageRule', { age: 25 }); // 'You are an adult. Your age is 25'💡 Core Concept:
localeSetcontains the complete translation data for the current locale, accessible vialocaleSet.landing['key_name'].- The returned translation key is processed the same way as the
t()function to convert it to the final translated value. - If the returned translation key contains
{slot}placeholders, they will be automatically replaced using thevaluesobject passed tot.rule().
💡 Important: If you want to use auto-replaced interpolation, you should use the same sheetTitle in the useTranslation hook and the localeSet in the t.rule function
// Example
// you return the translation key from 'userSheet' in the localeSet
const customRules = {
ageRule: createRule<{ age: number }>((values, localeSet) => {
if (values.age > 20) {
return localeSet.userSheet['adult_message'];
}
return localeSet.userSheet['youth_message'];
}),
};
// you should use the 't.rule' function from the same sheetTitle for the argument of `useTranslation`
const { t } = useTranslation('userSheet');
const translatedText = t.rule('ageRule', { age: 25 }); // 'You are an adult. Your age is 25'@sheet-i18n/cli
A CLI tool for efficient translation management using Google Sheets, with a focus on developer experience (DX).
Features
- ✨ Initialize CLI configuration for translation workflows.
- 👀 Watch files or directories for changes.
- 📤 Register translation data to Google Sheets.
- 🛠️ Built with TypeScript for type safety.
Core Concepts 🌟
😭 General Translation Workflow
The traditional process of managing translations with Google Spreadsheets often includes these steps:
- Translator identifies the text to be translated from the page (without developer involvement).
- Translator adds the translation data to a Google Spreadsheet.
- Developer creates a script to locally download the translation data from the sheet.
- Developer inspects the translation data, matches it with the code, and finds the corresponding values in the source code (this can be time-consuming).
- If discrepancies exist between the spreadsheet and the code, developers request changes from the translator.
- Once resolved, the developer applies the translations using an internationalization (intl) library.
- For additional translations, developers manually import and apply them in the code.
- Results are checked on the page, and any errors prompt a repeat of the cycle.
This process is lengthy and redundant. Translators often lack visibility into how text is structured in code, especially when developers split strings, need to insert variables on the text, or add line breaks (<br/>). As a result, the translation burden falls back to the developers.
☺️ Developer-Centric Translation Workflow
This CLI streamlines the process by shifting the focus to developers:
- Developers identify and mark variables or text for translation directly in the code.
- Run the CLI to register changes (it automatically adds translations using Google Translate with support for Google Spreadsheets).
- Translators inspect and refine the translations for improved fluency and meaning on the Google Sheet.
- Developers import the confirmed translation data, completing the process.
With this approach, unnecessary back-and-forth is eliminated, and translations align seamlessly with code.
🚀 Initial Setup with CLI
@sheet-i18n/cli provides a fully automated setup for React applications.
Once installed, you can bootstrap the required i18n context, store, and folder structure by simply running:
npx sheet-i18n initThis will automatically generate:
- A preconfigured
i18nStoreusing@sheet-i18n/react - A ready-to-use
i18nContext.tsthat exportsIntlProvider,useTranslation, andgetTranslation - Example locale JSON files (e.g.,
en.json,ko.json) - The
sheet.config.tsfile for managing your CLI behavior and Google Sheets credentials
💡 All files will be created in the recommended structure (e.g.
src/i18n,src/locales), and safely skipped if they already exist.
✅ What You No Longer Have to Do Manually
- You don’t need to manually initiate
I18nStoreor set up the React context. - You don’t need to manually prepare locale JSON templates.
- You don’t need to manually configure
sheet.config.ts.
Just run the init command and you’re ready to use useTranslation() in your components.
🗂️ Example Output:
📦 your-project/
├── 📄 .gitignore # Automatically appends 'sheet.config.ts' to ignore list
├── 📄 sheet.config.ts # CLI configuration file (contains Google Sheet credentials & options)
└── 📁 src/
└── 📁 i18n/
├── 📄 i18nContext.ts # React context for IntlProvider, useTranslation, and getTranslation
├── 📄 i18nStore.ts # Initializes I18nStore with supported locales and translation data
├── 📄 en.json # Placeholder for English translation strings
└── 📄 ko.json # Placeholder for Korean translation strings✅ The CLI automatically creates this structure after running npx sheet-i18n init.
🛡️ If sheet.config.ts or src/i18n already exist, the CLI will skip the creation of these files.
after running npx sheet-i18n init, you can mount the IntlProvider in your app.
Mount Intl Context Provider in your App
import React from 'react';
import I18nContextProvider from './i18n/i18nContext';
const App = () => {
const [locale, setLocale] = useState('en');
return (
<I18nContextProvider currentLocale={locale}>
<YourComponent />
</I18nContextProvider>
);
};Use Translations
// YourComponent.tsx
import React from 'react';
import { useTranslation } from './i18n/i18nContext';
const YourComponent = () => {
const { t } = useTranslation('header');
return (
<div>
<button>{t('login')}</button>
<button>{t('logout')}</button>
</div>
);
};That's it!
your translations are now ready to use in your application.
⚙️ sheet.config.ts
The CLI requires a sheet.config.ts file in the root of your project. Run sheet-i18n init to generate this file automatically.
Example sheet.config.ts:
{
"gitRootPath": ".",
"watchEntry": "src",
"detectExtensions": ["tsx", "ts", "jsx", "js"],
"googleSheetConfig": {
"credentials": {
"sheetId": "YOUR_GOOGLE_SHEET_ID",
"clientEmail": "YOUR_CLIENT_EMAIL",
"privateKey": "YOUR_PRIVATE_KEY"
},
"defaultLocale": "en",
"supportedLocales": ["en", "fr", "es"],
"ignoredSheets": ["BASE"],
"importPath": "./src/locales",
"autoAppendFilePathColumn": {
"enabled": true,
"columnName": "__filePath",
"fullFilePath": false
}
}
}1. File change monitoring configuration
| Option | Type | Required | Default | Description |
| ------------------ | -------- | -------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| gitRootPath | string | | "." (CWD) | Git repository root path. Used as the base directory for all git operations (especially useful in monorepo environments) |
| watchEntry | string | | "src" | Entry path to monitor file changes, relative to the current working directory. |
| detectExtensions | string[] | | ["jsx", "js", "tsx", "ts"] | List of file extensions to watch. |
2. Register command configuration
📄 googleSheetConfig Options
| Option | Type | Required | Default | Description |
| -------------------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| credentials.sheetId | string | ✅ | | The same ID as spreadsheetId (for API auth). |
| credentials.clientEmail | string | ✅ | | Client email from your Google Service Account credentials. |
| credentials.privateKey | string | ✅ | | Private key from your Google Service Account credentials. |
| supportedLocales | string[] | ✅ | | Array of all supported locale codes in ISO 639-1 format (e.g., ["en", "ko"]). Recommended to use ISO 639-1 standard. |
| defaultLocale | string | ✅ | | The default language/locale that serves as the base language for translations. This will be the language of the original text in your source code. Must be one of the values defined in supportedLocales |
| headerStartRowNumber | number | | 1 | Row number where sheet headers begin. |
| ignoredSheets | string[] | | | Titles of sheets to ignore during register/import (e.g., ["BASE"]). |
| importPath | string | | current working directory | Directory path to export translation JSON files. |
| autoAppendFilePathColumn | object | | enabled: truecolumnName: "__filePath"fullFilePath:false | Configuration for appending reference file path to each translation row. This indicates where the translation key is located in your source code.- enabled: Whether to append the file path column- columnName: Column name to use for file path- fullFilePath: Show full path or mask intermediate folders (e.g., "*******/folder1/folder2/file.ts") |
Commands
🎬 init
npx sheet-i18n initSets up the sheet.config.ts file in your project. This configuration file is required for all other commands.
👀 watch
npx sheet-i18n watchMonitors files or directories for changes and logs relevant updates. When watch mode is started, the CLI will detect changes of "useTranslation" and "t" calls of "@sheet-i18n/react" package.
//translationContext.tsx
import { createI18nContext } from 'sheet-i18n/react';
export const { IntlProvider, useTranslation } = createI18nContext(i18nStore);
// Component.tsx
import { useTranslation } from '../translationContext'
...
function Component (){
// The arguments of useTranslation function = sheetTitle
const { t } = useTranslation('environment');
...
// The arguments of t function = translation key
return (
<>
<SettingItem label={t('over')}>
<SettingItem label={t('under')}>
</>
)
}The watched result is
Detected Translations:
📄 [Sheet Title]: environment
- over
- under
# You can proceed using the shortcuts below:
- Press <Shift + R> to register the detected changes.
- Press <Ctrl + C> to cancel the watch command.📤 register
npx sheet-i18n register [options]Registers translation data to Google Sheets.
Remarkable features:
The
registercommand collects the current sheets in Google Spreadsheets. If there are sheets in your local changes that do not exist in the current list, it prompts you to create the missing sheets.It updates rows and adds "translation" entries using the
GOOGLETRANSLATEfunction supported by Google Spreadsheets for automated translations.After registering, it asks whether you want to update the locale JSON data locally. It then exports the JSON data to the
importPathspecified insheet.config.ts(default is the current working directory).
Options:
-s, --scope <scope>
Define the scope of the registration process:diff: Only scans translation keys that have been modified in the Git history (based ongit diff).total: Scans the entire directory for all translation keys and updates the spreadsheet, regardless of Git changes.select: User selection mode which allows you to choose the sheets to register.
Default:
diff
--sync
Define whether to synchronize remote sheet data source with the local translation code dataThis flag only takes effect when the scope is
totalorselectDefault:
false# synchronize the total sheet data with your local translation code data npx sheet-i18n register -s total --sync # synchronize the selected sheet data with your local translation code data npx sheet-i18n register -s select --sync
📄 Detailed description of the scope option:
Scope: diff
- The CLI identifies changes in translation keys by comparing the current state of the codebase with the latest Git commit.
- It only processes translation keys added, removed, or modified since the last commit.
- This is useful for incremental updates, ensuring only new or updated keys are registered.
Example:
# diff is the default scope. So you can omit the --scope option.
npx sheet-i18n registerScope: total
- The CLI scans the entire specified directory for all translation keys from the directory path you provide.
- This is useful for full synchronization, ensuring all keys are registered in the spreadsheet.
- However, it may take longer to process large directories. So, use with caution.
Example:
npx sheet-i18n register --scope totalScope: select
The CLI prompts you to manually select which sheets you want to update.
Once the selection is made, only the chosen sheets will have their translation data updated in the Google Spreadsheet.
This mode is especially useful for partial updates, allowing you to skip scanning the entire directory and focus on specific sheets.
It’s also helpful in cases where you’ve committed changes without registering translation data during development — instead of syncing all translations, you can update only the relevant sheets as needed.
Example:
npx sheet-i18n register --scope select📄 import
npx sheet-i18n importExports translation data from Google Sheets to local export directory.
The configuration of export command is based on sheet.config.ts on your root.
{
"googleSheetConfig": {
"credentials": {
"sheetId": "YOUR_GOOGLE_SHEET_ID",
"clientEmail": "YOUR_CLIENT_EMAIL",
"privateKey": "YOUR_PRIVATE_KEY"
},
"defaultLocale": "The base language of your application in sheet header",
"ignoredSheets": ["BASE"],
"importPath": "The path of export directory of translation data. (default: current working directory)"
}
}sheet-i18n/importer
The server-side importer subpackage allows you to interact with Google Sheets and export translations directly into your project. This is primarily used in server-side environments, such as Next.js API routes or other backend frameworks, where you want to fetch and store translations from a Google Spreadsheet to be served to clients or used within your server application.
import { googleSheetImporter } from 'sheet-i18n/importer';
const importer = await googleSheetImporter({
credentials: {
sheetId: 'your-google-sheet-id',
clientEmail: 'your-client-email',
privateKey: 'your-private-key',
},
defaultLocale: 'default-language-in-sheet-header',
});
await importer.importTranslations();Configuration ⚙️
The configuration object required for using the importer is as follows:
Required 📝
credentials: Google Sheets API credentials:sheetId: The ID of your Google Spreadsheet (extracted from the URL).clientEmail: The email of the Google Sheets API client.privateKey: The private key associated with the client.
defaultLocale: The default locale/language specified in your Google Sheet header.
Optional 🔧
headerStartRowNumber: Specifies the row number where the headers begin (if not at the top).ignoredSheets: A list of sheets to exclude by title. By default, sheets without thedefaultLocalein headers will be ignored.importPath: Path to save exported translations from your sheet. This is the location where the exported translations will be saved. By default, it will use the current working directory (cwd). This option is only used when calling theimportTranslationsmethod.
Importer Methods 🛠️
getTranslations 📝
Description: This function retrieves the translation data, which is structured by locale keys (such as "ko", "en", etc.). It collects all translations from the specified sheet, replaces any placeholders, and prepares them to be sent to the client in the response body. Each key corresponds to a language, and the value for each key is an object containing the translated text for that locale.
Type: Function
Parameters: None
Returns: An object where each key is a locale (e.g., "ko", "en"), and the value is the respective translation data for that locale.
Example:
If the headers in your Google Sheets are ["ko", "en", ...], the result returned by getTranslations will look like this:
{
"ko": {
"greeting": "안녕하세요",
"farewell": "안녕히 가세요"
},
"en": {
"greeting": "Hello",
"farewell": "Goodbye"
}
...
}In this example:
"ko"contains the translations for Korean."en"contains the translations for English. Each locale’s object contains key-value pairs for individual translations.
importTranslations 📤
Description: This asynchronous function is used in a Node.js environment to export translations directly into your project. It is especially useful if your project includes server-side APIs (like Next.js API routes) and you need to update the locale JSON data in your project. It can be invoked to update or create the locale files within your project, ensuring that the translation data is synced with the local environment.
Type: Function
Parameters: None
Returns:
void
Example:
If you call the importTranslations function, it will update or create JSON files in your project for each locale. The result could look like this:
// ko.json
{
"greeting": "안녕하세요",
"farewell": "안녕히 가세요"
}
// en.json
{
"greeting": "Hello",
"farewell": "Goodbye"
}API Documentation 📚
This package provides a streamlined way to export data using sheets API. It is designed to simplify the integration of spreadsheet data into your applications, particularly for translation workflows and internationalization (i18n).
Google Sheets API Documentation Google Auth Library Google Spreadsheet
Exported Data Format 🗂️
The exported translations will be saved in a format that is easy to use for localization purposes. Each translation is stored in its respective locale folder, with a structured format.
License 📜
This project is licensed under the ISC License. See the LICENSE file for details.
Author ✍️
Created by devAnderson.
