@sheet-i18n/react
v1.6.1
Published
i18n client logic based on react
Downloads
2,132
Readme
@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'License 📜
This project is licensed under the ISC License. See the LICENSE file for details.
Author ✍️
Created by devAnderson.
