sheet-i18n 1.9.6 β†’ 1.10.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +220 -29
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -31,6 +31,10 @@ npm install @sheet-i18n/cli -D
31
31
 
32
32
  ### `@sheet-i18n/react`
33
33
 
34
+ # @sheet-i18n/react ✨
35
+
36
+ [![npm package](https://img.shields.io/npm/v/@sheet-i18n/react)](https://npmjs.com/package/@sheet-i18n/react)
37
+
34
38
  The **client-side i18n library** subpackage of sheet-i18n.
35
39
 
36
40
  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.
@@ -42,11 +46,12 @@ This package provides tools to handle translations in React applications using c
42
46
  - **`IntlProvider`**: _React Translation Provider_ for managing current locale.
43
47
  - **`useTranslation`**: _Client Side Translation Hook_ for easy access to translation messages on the client side.
44
48
  - **`getTranslation`**: _Static Translation Function_ for easy access to translation messages on Static module files.
49
+ - **`ruleFactory`**: _Rule Factory Function_ for creating custom translation rules with conditional logic.
45
50
 
46
51
  ## πŸš€ Getting Started(Manually)
47
52
 
48
53
  > ⚑ **Strongly recommended to use the init CLI for setup**
49
- > πŸ‘‰ [Please follow the Init CLI section](#initial-setup-anchor)
54
+ > πŸ‘‰ [Please follow the Init CLI](https://www.npmjs.com/package/@sheet-i18n/cli)
50
55
  >
51
56
  > If you don't want to use the CLI, you can follow the `Manual Setup` below.
52
57
 
@@ -77,8 +82,8 @@ Prepare locale JSON files:
77
82
  this store will be used as a core translations module.
78
83
 
79
84
  ```tsx
80
- import ko from './ko.json';
81
85
  import en from './en.json';
86
+ import ko from './ko.json';
82
87
 
83
88
  import { I18nStore } from '@sheet-i18n/react';
84
89
 
@@ -166,6 +171,7 @@ The `I18nStore` manages translation states, ensuring consistency across locales.
166
171
  | localeSet | Record<string, object> | | _(Static Loading Option)_ Preload all translation data for each locale in memory. Keys must match `supportedLocales`. |
167
172
  | dynamicLoaders | Record<string, () => Promise<any>> | | _(Recommended for large locale sets)_ Dynamically load translation data on demand, reducing initial bundle size. |
168
173
  | typeSafe | boolean | | Enable strict key checking and autocompletion (default: `false`). |
174
+ | |
169
175
 
170
176
  > πŸ’‘ **typeSafe?** <br/>
171
177
  > 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.
@@ -174,7 +180,7 @@ The `I18nStore` manages translation states, ensuring consistency across locales.
174
180
  supportedLocales in i18nStore`
175
181
  >
176
182
  > As 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.
177
- >
183
+
178
184
  > ```tsx
179
185
  > // typeSafe: true
180
186
  > const YourComponent = () => {
@@ -234,9 +240,6 @@ For projects with many locale datasets, it's often preferable to load translatio
234
240
  #### Example:
235
241
 
236
242
  ```tsx
237
- import ko from './locales/ko.json';
238
- import en from './locales/en.json';
239
-
240
243
  export const i18nStore = new I18nStore({
241
244
  supportedLocales: ['ko', 'en'],
242
245
  defaultLocale: 'ko',
@@ -263,6 +266,8 @@ Generates React context, including the `IntlProvider` and `useTranslation`.
263
266
  #### Parameters:
264
267
 
265
268
  - **`i18nStore`**: Instance of `I18nStore`.
269
+ - **`plugins`** (optional): Plugin configuration object.
270
+ - **`rules`** (optional): Custom rules object for conditional translations. See [Plugins](#-plugins) section for details.
266
271
 
267
272
  > ⚠️ Caveats:
268
273
  >
@@ -334,31 +339,93 @@ A function to access translations in the environment where cannot use react cont
334
339
 
335
340
  ### `t Function`
336
341
 
337
- The t function is used to retrieve a translation string based on a key and optionally interpolate variables. It provides flexibility to include dynamic values, such as plain strings or React components, for enhanced localization capabilities.
342
+ 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.
338
343
 
339
- #### Parameters:
344
+ #### **Overview: `t()`, `t.rule()`, and `t.dynamic()`**
345
+
346
+ | Method | Use Case | Key Type | When to Use |
347
+ | ----------------------------------- | ------------------------------------------ | ------------------------------------ | ------------------------------------------------------------------- |
348
+ | **`t(key, values?)`** | Standard translation with explicit keys | Static (known at build time) | When you know the translation key beforehand |
349
+ | **`t.rule(ruleKey, values?)`** | Conditional translation using custom rules | Determined by rule function | When you need conditional logic (pluralization, gender-based, etc.) |
350
+ | **`t.dynamic(id, values?, opts?)`** | Translation with runtime keys | Dynamic (from API, user input, etc.) | When the translation key comes from runtime data |
351
+
352
+ #### **`t()` - Standard Translation**
353
+
354
+ Retrieve translations for explicit keys that are known at build time.
340
355
 
341
- - key (string): The translation key to retrieve the localized string.
356
+ ##### Parameters:
357
+
358
+ - **`key`** (string): The translation key to retrieve the localized string.
359
+ - **`values`** (object, optional): An object containing key-value pairs to interpolate into the translation string.
360
+
361
+ ##### Examples:
342
362
 
343
363
  ```tsx
344
- const translatedMessage = t('login'); // login is key
364
+ const { t } = useTranslation('header');
365
+
366
+ // Simple translation
367
+ const loginText = t('login'); // "Login" or "둜그인"
368
+
369
+ // Translation with interpolation
370
+ const welcomeMessage = t('{username} shown', { username: 'John Doe' });
371
+ // Result: "John Doe shown"
372
+
373
+ // Interpolation with React components
374
+ const message = t('{username} shown', { username: <Username /> });
345
375
  ```
346
376
 
347
- - values (object, optional): An object containing key-value pairs to interpolate into the translation string.
377
+ πŸ’‘ **Note**: The values object can contain any type of data, including React components.
378
+
379
+ #### **`t.rule()` - Conditional Translation with Custom Rules**
380
+
381
+ Use custom rules for conditional translations based on dynamic values. This method is available when custom rules are registered via the `createI18nContext` plugins option.
382
+
383
+ ##### Parameters:
384
+
385
+ - **`ruleKey`** (string): The key of the registered custom rule.
386
+ - **`values`** (object, optional): An object containing values to be passed to the rule function and used for interpolation.
387
+
388
+ ##### Examples:
348
389
 
349
390
  ```tsx
350
- // John Doe shown
351
- const translatedMessage = t('{username} shown', { username: 'John Doe' });
391
+ // Assuming custom rules are registered
392
+ const { t } = useTranslation('landing');
393
+
394
+ // Call rule with values
395
+ const message = t.rule('plural', { age: 21 });
396
+ // The rule function determines which translation key to use based on age
352
397
  ```
353
398
 
354
- πŸ’‘ Note: The values object can contain any type of data, including React components.
399
+ πŸ’‘ **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()`.
400
+
401
+ For more details, see the [Plugins](#-plugins) section.
402
+
403
+ #### **`t.dynamic()` - Runtime Key Translation**
404
+
405
+ Resolve translation keys that are not known at build time (e.g., values coming from API responses, user input, or other runtime sources).
406
+
407
+ ##### Parameters:
408
+
409
+ - **`id`** (string): A runtime string to match against the current locale's translation keys.
410
+ - **`values`** (object, optional): Interpolation values, identical to `t()`.
411
+ - **`opts`** (object, optional): Formatter options.
412
+
413
+ ##### Examples:
355
414
 
356
415
  ```tsx
357
- // <Username /> shown
358
- const translatedMessage = t('{username} shown', { username: <Username /> });
416
+ const { t } = useTranslation('header');
417
+ const { data } = useQuery(...queryOptions);
418
+
419
+ // Runtime key from API response
420
+ const deviceName = data?.device?.name; // e.g., "smartphone"
421
+
422
+ // Will translate the runtime key if it exists in the current locale
423
+ const label = t.dynamic(deviceName);
424
+ // If "smartphone" exists in locale JSON, returns translated value
425
+ // Otherwise, returns "smartphone" as fallback
359
426
  ```
360
427
 
361
- <br/>
428
+ πŸ’‘ **Tip**: Ensure your locale JSON contains possible runtime keys; otherwise `t.dynamic` will fall back to the provided id string.
362
429
 
363
430
  ## πŸ“„ Best Practices of Translation utilities
364
431
 
@@ -414,11 +481,6 @@ return <div>{t.dynamic(deviceName)}</div>;
414
481
 
415
482
  The `getTranslation` function allows you to use translations outside of React components, such as in static configuration files, constants, or utility modules.
416
483
 
417
- It provide two possible supports
418
-
419
- 1. **`t` (Synchronous Translation)** – Use when translation values are available **at runtime**.
420
- 2. **`t.promise` (Asynchronous Translation)** – Use when the module’s evaluation order is uncertain.
421
-
422
484
  #### **βœ… Usage Scenarios**
423
485
 
424
486
  #### **[Scenario 1]: Context where the call expression of `t` function is evaluated at runtime**
@@ -453,32 +515,46 @@ export default function App() {
453
515
 
454
516
  - If the module is **imported dynamically in a client-side component**, the evaluation order of `getTranslation` and `t` call expression may not be guaranteed.
455
517
  - Since JavaScript **evaluates modules at import time**, it may attempt to access translation values before `IntlProvider` has fully initialized the locale.
456
- - In this case, use **`t.promise`** to ensure the translation resolves asynchronously.
518
+ - In this case, use **`getTranslation`** and **`watch`** CLI for easy translation workflow.
457
519
 
458
520
  #### **Example**
459
521
 
460
522
  ```tsx
461
523
  // module.ts
462
- // The "t" result in the array will be resolved asynchronously
524
+ // the "t" function from "getTranslation" is used as the "marker" of "watch" CLI
463
525
  import { getTranslation } from './i18nContext';
464
526
 
465
527
  const { t } = getTranslation('header');
466
528
 
467
529
  export const STATUS_CATEGORY = [
468
- t.promise('Total Energy Usage'),
469
- t.promise('Total Waste Usage'),
530
+ t('Total Energy Usage'), // Marked
531
+ t('Total Waste Usage'), // Marked
470
532
  ];
471
533
 
534
+ // Pre-requisite: install @sheet-i18n/cli and run `npx sheet-i18n watch`
535
+ // You can register your translation data with ease
536
+ Detected Translations:
537
+
538
+ πŸ“„ [Sheet Title]: header
539
+ - Total Energy Usage
540
+ - Total Waste Usage
541
+
542
+ # You can proceed using the shortcuts below:
543
+ - Press <Shift + R> to register the detected changes. // <-- you can register your translation data
544
+ - Press <Ctrl + C> to cancel the watch command.
545
+
472
546
  // App.tsx
473
- // So, t.promise ensure the current client-side locale data
474
- // is fully initialized before the translations are resolved
547
+ // After update of translation data, t.dynamic will resolve the translations
548
+ // 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.
475
549
  import { STATUS_CATEGORY } from './module.ts';
476
550
 
477
551
  export default function App() {
552
+ const { t } = useTranslation('header');
553
+
478
554
  return (
479
555
  <div>
480
556
  {STATUS_CATEGORY.map((item, index) => {
481
- return <div key={index}>{item}</div>;
557
+ return <div key={index}>{t.dynamic(item)}</div>;
482
558
  })}
483
559
  </div>
484
560
  );
@@ -610,6 +686,117 @@ const customStorageManager = getLocaleStorageManager(
610
686
  );
611
687
  ```
612
688
 
689
+ #### **Storage Options**
690
+
691
+ - **LocalStorage (Default)**: Persists across browser sessions and device restarts
692
+ - **SessionStorage**: Persists only for the current browser session
693
+ - **Custom Storage**: Implement your own persistence strategy (e.g., server-side storage, encrypted storage)
694
+ - **Memory Storage**: No persistence (for testing or temporary use cases)
695
+
696
+ ## πŸ”Œ Plugins
697
+
698
+ sheet-i18n provides an extensible plugin system that allows you to customize translation logic and easily implement conditional translations or complex translation rules.
699
+
700
+ ### 🎯 Custom Rules Plugin
701
+
702
+ **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.
703
+
704
+ #### **Key Features**
705
+
706
+ - βœ… **Conditional Translation**: Automatically select different translation keys based on values
707
+ - βœ… **Interpolation Support**: Same `{slot}` replacement functionality as the `t` function
708
+ - βœ… **Extensible**: Combine multiple rules to implement complex logic
709
+
710
+ ##### **Key Points**
711
+
712
+ ```tsx
713
+ import { ruleFactory } from '@sheet-i18n/react';
714
+ import { i18nStore } from './i18nStore';
715
+
716
+ // Step 1: Create factory service
717
+ // ruleFactory takes your i18nStore and returns an object with createRule method
718
+ const { createRule } = ruleFactory(i18nStore);
719
+
720
+ // Step 2: Use createRule to define custom rules
721
+ // `createRule` can take a rule function and support the current localeSet data use registered in the localeSet of `i18nStore`
722
+ // In the body of the rule function, you can defined the translation logic as you want
723
+
724
+ // πŸ’‘ Note: Highly recommended to return the translation key from the localeSet
725
+ // When you use `t.rule()` from the component, the translation key will be resolved and the translated text will be returned
726
+ // If you return the normal string, that string will be used as the translation key
727
+ // and If no matched translation key is found, the string will be used rendered
728
+ const myRule = createRule<{ count: number }>((values, localeSet) => {
729
+ if (values.count > 10) {
730
+ return localeSet.sheetTitle['many_items'];
731
+ }
732
+ return localeSet.sheetTitle['few_items'];
733
+ });
734
+ ```
735
+
736
+ ##### **Complete Flow Example**
737
+
738
+ ```tsx
739
+ // Translation data example from your sheet
740
+ // en.json
741
+ {
742
+ ...
743
+ "userSheet": {
744
+ "adult_message": "You are an adult. Your age is {age}",
745
+ "youth_message": "You are a youth. Your age is {age}"
746
+ }
747
+ }
748
+
749
+ // 1. Initialize i18nStore
750
+ const i18nStore = new I18nStore({
751
+ /* ... */
752
+ });
753
+
754
+ // 2. Create factory service (binds createRule to i18nStore)
755
+ const { createRule } = ruleFactory(i18nStore);
756
+
757
+ // 3. Define custom rules using createRule
758
+ export const customRules = {
759
+ ageRule: createRule<{ age: number }>((values, localeSet) => {
760
+ if (values.age > 20) {
761
+ return localeSet.userSheet['adult_message'];
762
+ }
763
+ return localeSet.userSheet['youth_message'];
764
+ }),
765
+ };
766
+
767
+ // 4. Register rules in context
768
+ const { useTranslation } = createI18nContext(i18nStore, { rules: customRules });
769
+
770
+ // 5. Use in components
771
+ const { t } = useTranslation('userSheet');
772
+ const translatedText = t.rule('ageRule', { age: 25 }); // 'You are an adult. Your age is 25'
773
+ ```
774
+
775
+ πŸ’‘ **Core Concept**:
776
+
777
+ - `localeSet` contains the complete translation data for the current locale, accessible via `localeSet.landing['key_name']`.
778
+ - The returned translation key is processed the same way as the `t()` function to convert it to the final translated value.
779
+ - If the returned translation key contains `{slot}` placeholders, they will be automatically replaced using the `values` object passed to `t.rule()`.
780
+
781
+ πŸ’‘ **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
782
+
783
+ ```ts
784
+ // Example
785
+ // you return the translation key from 'userSheet' in the localeSet
786
+ const customRules = {
787
+ ageRule: createRule<{ age: number }>((values, localeSet) => {
788
+ if (values.age > 20) {
789
+ return localeSet.userSheet['adult_message'];
790
+ }
791
+ return localeSet.userSheet['youth_message'];
792
+ }),
793
+ };
794
+
795
+ // you should use the 't.rule' function from the same sheetTitle for the argument of `useTranslation`
796
+ const { t } = useTranslation('userSheet');
797
+ const translatedText = t.rule('ageRule', { age: 25 }); // 'You are an adult. Your age is 25'
798
+ ```
799
+
613
800
  </details>
614
801
 
615
802
  ---
@@ -620,6 +807,8 @@ const customStorageManager = getLocaleStorageManager(
620
807
 
621
808
  ### `@sheet-i18n/cli`
622
809
 
810
+ [![npm package](https://img.shields.io/npm/v/@sheet-i18n/cli)](https://npmjs.com/package/@sheet-i18n/cli)
811
+
623
812
  A CLI tool for efficient translation management using Google Sheets, with a focus on developer experience (DX).
624
813
 
625
814
  ## Features
@@ -991,6 +1180,8 @@ The configuration of export command is based on **`sheet.config.ts`** on your ro
991
1180
 
992
1181
  ### `sheet-i18n/importer`
993
1182
 
1183
+ [![npm package](https://img.shields.io/npm/v/@sheet-i18n/importer)](https://npmjs.com/package/@sheet-i18n/importer)
1184
+
994
1185
  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.
995
1186
 
996
1187
  ```jsx
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sheet-i18n",
3
3
  "description": "All-in-one i18n toolchain: seamlessly integrate Google Sheets, CLI, and react-i18n for easy translation workflows.",
4
- "version": "1.9.6",
4
+ "version": "1.10.0-canary.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/chltjdrhd777/sheet-i18n"
@@ -50,9 +50,9 @@
50
50
  "license": "ISC",
51
51
  "homepage": "https://github.com/chltjdrhd777/sheet-i18n",
52
52
  "dependencies": {
53
- "@sheet-i18n/importer": "1.9.4",
54
- "@sheet-i18n/react": "1.5.3",
55
- "@sheet-i18n/typescript": "0.2.2"
53
+ "@sheet-i18n/react": "1.6.0-canary.0",
54
+ "@sheet-i18n/typescript": "0.2.2",
55
+ "@sheet-i18n/importer": "1.9.5"
56
56
  },
57
57
  "devDependencies": {
58
58
  "tsup": "^6.0.0",