@dcl/hooks 1.0.1-20251223125619.commit-1446f8b → 1.0.1-20251229200811.commit-7c01c67
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.
- package/README.md +214 -0
- package/esm/contexts/analytics/AnalyticsProvider.js +3 -2
- package/esm/contexts/analytics/AnalyticsProvider.js.map +1 -1
- package/esm/contexts/index.d.ts +3 -0
- package/esm/contexts/index.js +2 -0
- package/esm/contexts/index.js.map +1 -1
- package/esm/contexts/translation/TranslationContext.d.ts +3 -0
- package/esm/contexts/translation/TranslationContext.js +4 -0
- package/esm/contexts/translation/TranslationContext.js.map +1 -0
- package/esm/contexts/translation/TranslationProvider.d.ts +3 -0
- package/esm/contexts/translation/TranslationProvider.js +16 -0
- package/esm/contexts/translation/TranslationProvider.js.map +1 -0
- package/esm/contexts/translation/types.d.ts +16 -0
- package/esm/contexts/translation/types.js +2 -0
- package/esm/contexts/translation/types.js.map +1 -0
- package/esm/hooks/index.d.ts +2 -0
- package/esm/hooks/index.js +1 -0
- package/esm/hooks/index.js.map +1 -1
- package/esm/hooks/useTranslation/index.d.ts +2 -0
- package/esm/hooks/useTranslation/index.js +2 -0
- package/esm/hooks/useTranslation/index.js.map +1 -0
- package/esm/hooks/useTranslation/useTranslation.d.ts +77 -0
- package/esm/hooks/useTranslation/useTranslation.js +161 -0
- package/esm/hooks/useTranslation/useTranslation.js.map +1 -0
- package/esm/hooks/useTranslation/useTranslation.type.d.ts +21 -0
- package/esm/hooks/useTranslation/useTranslation.type.js +2 -0
- package/esm/hooks/useTranslation/useTranslation.type.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ npm install @dcl/hooks
|
|
|
20
20
|
- `useAsyncEffect`: Async version of useEffect
|
|
21
21
|
- `useAsyncMemo`: Async version of useMemo
|
|
22
22
|
- `useInfiniteScroll`: Infinite scroll functionality for loading more content
|
|
23
|
+
- `useTranslation`: Simple and lightweight translation management
|
|
23
24
|
|
|
24
25
|
## Examples
|
|
25
26
|
|
|
@@ -464,6 +465,219 @@ function App() {
|
|
|
464
465
|
}
|
|
465
466
|
```
|
|
466
467
|
|
|
468
|
+
### useTranslation
|
|
469
|
+
|
|
470
|
+
The `useTranslation` hook provides i18n capabilities powered by `@formatjs/intl`, giving you access to advanced formatting functions for numbers, dates, currencies, and more.
|
|
471
|
+
|
|
472
|
+
Basic usage:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import { useTranslation } from '@dcl/hooks'
|
|
476
|
+
|
|
477
|
+
const translations = {
|
|
478
|
+
en: {
|
|
479
|
+
greeting: 'Hello, {name}!',
|
|
480
|
+
welcome: 'Welcome to our app',
|
|
481
|
+
items: '{count, plural, =0 {No items} one {# item} other {# items}}'
|
|
482
|
+
},
|
|
483
|
+
es: {
|
|
484
|
+
greeting: 'Hola, {name}!',
|
|
485
|
+
welcome: 'Bienvenido a nuestra aplicación',
|
|
486
|
+
items: '{count, plural, =0 {Sin elementos} one {# elemento} other {# elementos}}'
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function MyComponent() {
|
|
491
|
+
const { t, intl, locale, setLocale } = useTranslation({
|
|
492
|
+
locale: 'en',
|
|
493
|
+
translations
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<div>
|
|
498
|
+
<p>{t('greeting', { name: 'John' })}</p>
|
|
499
|
+
<p>{t('items', { count: 5 })}</p>
|
|
500
|
+
<button onClick={() => setLocale('es')}>
|
|
501
|
+
Switch to Spanish
|
|
502
|
+
</button>
|
|
503
|
+
</div>
|
|
504
|
+
)
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Using the `intl` object for advanced formatting:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
function AdvancedFormattingExample() {
|
|
512
|
+
const { t, intl } = useTranslation({
|
|
513
|
+
locale: 'en',
|
|
514
|
+
translations: {
|
|
515
|
+
en: {
|
|
516
|
+
product_price: 'Price: {price}'
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
return (
|
|
522
|
+
<div>
|
|
523
|
+
{/* Format numbers */}
|
|
524
|
+
<p>Count: {intl.formatNumber(1000)}</p>
|
|
525
|
+
|
|
526
|
+
{/* Format dates */}
|
|
527
|
+
<p>Today: {intl.formatDate(new Date(), {
|
|
528
|
+
year: 'numeric',
|
|
529
|
+
month: 'long',
|
|
530
|
+
day: 'numeric'
|
|
531
|
+
})}</p>
|
|
532
|
+
|
|
533
|
+
{/* Format currency */}
|
|
534
|
+
<p>{intl.formatNumber(99.99, {
|
|
535
|
+
style: 'currency',
|
|
536
|
+
currency: 'USD'
|
|
537
|
+
})}</p>
|
|
538
|
+
|
|
539
|
+
{/* Format relative time */}
|
|
540
|
+
<p>{intl.formatRelativeTime(-1, 'day')}</p>
|
|
541
|
+
|
|
542
|
+
{/* Use formatMessage directly */}
|
|
543
|
+
<p>{intl.formatMessage({ id: 'product_price' }, { price: '$99' })}</p>
|
|
544
|
+
</div>
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
With fallback locale:
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
const translations = {
|
|
553
|
+
en: {
|
|
554
|
+
greeting: 'Hello!',
|
|
555
|
+
welcome: 'Welcome!'
|
|
556
|
+
},
|
|
557
|
+
es: {
|
|
558
|
+
greeting: 'Hola!'
|
|
559
|
+
// 'welcome' is missing in Spanish
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function MyComponent() {
|
|
564
|
+
const { t } = useTranslation({
|
|
565
|
+
locale: 'es',
|
|
566
|
+
translations,
|
|
567
|
+
fallbackLocale: 'en' // Will use English if translation is missing
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
return (
|
|
571
|
+
<div>
|
|
572
|
+
<p>{t('greeting')}</p> {/* Shows: "Hola!" */}
|
|
573
|
+
<p>{t('welcome')}</p> {/* Shows: "Welcome!" (from fallback) */}
|
|
574
|
+
</div>
|
|
575
|
+
)
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Using TranslationProvider for context-based translations:
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
import { TranslationProvider, useTranslation } from '@dcl/hooks'
|
|
583
|
+
|
|
584
|
+
const translations = {
|
|
585
|
+
en: {
|
|
586
|
+
greeting: 'Hello!',
|
|
587
|
+
welcome: 'Welcome to our app'
|
|
588
|
+
},
|
|
589
|
+
es: {
|
|
590
|
+
greeting: 'Hola!',
|
|
591
|
+
welcome: 'Bienvenido a nuestra aplicación'
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function App() {
|
|
596
|
+
return (
|
|
597
|
+
<TranslationProvider
|
|
598
|
+
locale="en"
|
|
599
|
+
translations={translations}
|
|
600
|
+
fallbackLocale="en"
|
|
601
|
+
>
|
|
602
|
+
<MyComponent />
|
|
603
|
+
</TranslationProvider>
|
|
604
|
+
)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function MyComponent() {
|
|
608
|
+
// Can be used without options when inside TranslationProvider
|
|
609
|
+
const { t, locale, setLocale } = useTranslation()
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
<div>
|
|
613
|
+
<p>{t('greeting')}</p>
|
|
614
|
+
<p>{t('welcome')}</p>
|
|
615
|
+
<button onClick={() => setLocale('es')}>
|
|
616
|
+
Switch to Spanish
|
|
617
|
+
</button>
|
|
618
|
+
</div>
|
|
619
|
+
)
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
Using ICU Message Syntax:
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
const translations = {
|
|
627
|
+
en: {
|
|
628
|
+
// Pluralization
|
|
629
|
+
items: '{count, plural, =0 {No items} one {# item} other {# items}}',
|
|
630
|
+
// Select syntax
|
|
631
|
+
gender: '{gender, select, male {He} female {She} other {They}}',
|
|
632
|
+
// Complex ICU
|
|
633
|
+
notification: '{count, plural, =0 {No notifications} =1 {You have one notification} other {You have # notifications}}'
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function MyComponent() {
|
|
638
|
+
const { t } = useTranslation({
|
|
639
|
+
locale: 'en',
|
|
640
|
+
translations
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
return (
|
|
644
|
+
<div>
|
|
645
|
+
<p>{t('items', { count: 0 })}</p> {/* "No items" */}
|
|
646
|
+
<p>{t('items', { count: 1 })}</p> {/* "1 item" */}
|
|
647
|
+
<p>{t('items', { count: 5 })}</p> {/* "5 items" */}
|
|
648
|
+
<p>{t('gender', { gender: 'male' })}</p> {/* "He" */}
|
|
649
|
+
<p>{t('notification', { count: 3 })}</p> {/* "You have 3 notifications" */}
|
|
650
|
+
</div>
|
|
651
|
+
)
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
Additional intl formatting functions:
|
|
656
|
+
|
|
657
|
+
```typescript
|
|
658
|
+
function MyComponent() {
|
|
659
|
+
const { intl } = useTranslation({
|
|
660
|
+
locale: 'en',
|
|
661
|
+
translations: { en: {} }
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
return (
|
|
665
|
+
<div>
|
|
666
|
+
{/* Format lists */}
|
|
667
|
+
<p>{intl.formatList(['apple', 'banana', 'orange'])}</p>
|
|
668
|
+
{/* "apple, banana, and orange" */}
|
|
669
|
+
|
|
670
|
+
{/* Format display names */}
|
|
671
|
+
<p>{intl.formatDisplayName('es', { type: 'language' })}</p>
|
|
672
|
+
{/* "Spanish" */}
|
|
673
|
+
|
|
674
|
+
<p>{intl.formatDisplayName('US', { type: 'region' })}</p>
|
|
675
|
+
{/* "United States" */}
|
|
676
|
+
</div>
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
467
681
|
## License
|
|
468
682
|
|
|
469
683
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useMemo, useRef, useState } from "react";
|
|
2
3
|
import { isbot } from "isbot";
|
|
3
4
|
import { useAsyncEffect } from "../../hooks/useAsyncEffect";
|
|
4
5
|
const AnalyticsContext = createContext(null);
|
|
@@ -53,7 +54,7 @@ const AnalyticsProvider = (props) => {
|
|
|
53
54
|
},
|
|
54
55
|
};
|
|
55
56
|
}, [isInitialized]);
|
|
56
|
-
return (
|
|
57
|
+
return (_jsx(AnalyticsContext.Provider, { value: contextValue, children: children }));
|
|
57
58
|
};
|
|
58
59
|
export { AnalyticsContext, AnalyticsProvider };
|
|
59
60
|
//# sourceMappingURL=AnalyticsProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsProvider.js","sourceRoot":"","sources":["../../../src/contexts/analytics/AnalyticsProvider.tsx"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"AnalyticsProvider.js","sourceRoot":"","sources":["../../../src/contexts/analytics/AnalyticsProvider.tsx"],"names":[],"mappings":"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"}
|
package/esm/contexts/index.d.ts
CHANGED
package/esm/contexts/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAA;AAE7C,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TranslationContext.js","sourceRoot":"","sources":["../../../src/contexts/translation/TranslationContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAGrC,MAAM,kBAAkB,GAAG,aAAa,CAAgC,IAAI,CAAC,CAAA;AAE7E,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { TranslationContext } from "./TranslationContext";
|
|
4
|
+
import { useTranslation } from "../../hooks/useTranslation";
|
|
5
|
+
const TranslationProvider = (props) => {
|
|
6
|
+
const { locale, translations, fallbackLocale, children } = props;
|
|
7
|
+
const translationResult = useTranslation({
|
|
8
|
+
locale,
|
|
9
|
+
translations,
|
|
10
|
+
fallbackLocale,
|
|
11
|
+
});
|
|
12
|
+
const contextValue = useMemo(() => translationResult, [translationResult]);
|
|
13
|
+
return (_jsx(TranslationContext.Provider, { value: contextValue, children: children }));
|
|
14
|
+
};
|
|
15
|
+
export { TranslationProvider };
|
|
16
|
+
//# sourceMappingURL=TranslationProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TranslationProvider.js","sourceRoot":"","sources":["../../../src/contexts/translation/TranslationProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAG3D,MAAM,mBAAmB,GAAG,CAC1B,KAAkC,EAClC,EAAE;IACF,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IAEhE,MAAM,iBAAiB,GAAG,cAAc,CAAC;QACvC,MAAM;QACN,YAAY;QACZ,cAAc;KACf,CAAC,CAAA;IAEF,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EAAE,CAAC,iBAAiB,EACvB,CAAC,iBAAiB,CAAC,CACpB,CAAA;IAED,OAAO,CACL,KAAC,kBAAkB,CAAC,QAAQ,IAAC,KAAK,EAAE,YAAY,YAC7C,QAAQ,GACmB,CAC/B,CAAA;AACH,CAAC,CAAA;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LanguageTranslations } from "../../hooks/useTranslation";
|
|
3
|
+
type TranslationProviderProps<L extends string = string> = {
|
|
4
|
+
locale: L;
|
|
5
|
+
translations: LanguageTranslations;
|
|
6
|
+
fallbackLocale?: L;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
type TranslationContextType = {
|
|
10
|
+
t: (key: string, values?: Record<string, string | number>) => string;
|
|
11
|
+
intl: import("@formatjs/intl").IntlShape;
|
|
12
|
+
locale: string;
|
|
13
|
+
setLocale: (locale: string) => void;
|
|
14
|
+
error: string | null;
|
|
15
|
+
};
|
|
16
|
+
export type { TranslationProviderProps, TranslationContextType };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/contexts/translation/types.ts"],"names":[],"mappings":""}
|
package/esm/hooks/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type { AdvancedNavigatorUAData } from "./useAdvancedUserAgentData";
|
|
|
2
2
|
export type { AsyncMemoResult, AsyncMemoResultState } from "./useAsyncMemo";
|
|
3
3
|
export type { AsyncStateResult, AsyncStateResultState } from "./useAsyncState";
|
|
4
4
|
export type { UseInfiniteScrollOptions } from "./useInfiniteScroll";
|
|
5
|
+
export type { Translations, LanguageTranslations, TranslationState, TranslationOptions, TranslationResult, } from "./useTranslation";
|
|
5
6
|
export { useAdvancedUserAgentData } from "./useAdvancedUserAgentData";
|
|
6
7
|
export { useAnalytics } from "./useAnalytics";
|
|
7
8
|
export { useAsyncEffect } from "./useAsyncEffect";
|
|
@@ -12,3 +13,4 @@ export { useAsyncTasks } from "./useAsyncTasks";
|
|
|
12
13
|
export { useInfiniteScroll } from "./useInfiniteScroll";
|
|
13
14
|
export { usePageTracking } from "./usePageTracking";
|
|
14
15
|
export { usePatchState } from "./usePatchState";
|
|
16
|
+
export { useTranslation } from "./useTranslation";
|
package/esm/hooks/index.js
CHANGED
|
@@ -9,4 +9,5 @@ export { useAsyncTasks } from "./useAsyncTasks";
|
|
|
9
9
|
export { useInfiniteScroll } from "./useInfiniteScroll";
|
|
10
10
|
export { usePageTracking } from "./usePageTracking";
|
|
11
11
|
export { usePatchState } from "./usePatchState";
|
|
12
|
+
export { useTranslation } from "./useTranslation";
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
package/esm/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAaA,QAAQ;AACR,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/useTranslation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { TranslationOptions, TranslationResult } from "./useTranslation.type";
|
|
2
|
+
/**
|
|
3
|
+
* Hook to manage translations in a React application using @formatjs/intl
|
|
4
|
+
*
|
|
5
|
+
* Can be used in two ways:
|
|
6
|
+
* 1. With options (standalone mode): Provides translations directly
|
|
7
|
+
* 2. Without options (context mode): Uses TranslationProvider context
|
|
8
|
+
*
|
|
9
|
+
* @param options - Optional configuration options for translations. If not provided, will use TranslationProvider context.
|
|
10
|
+
* @param options.locale - The initial locale to use (required in standalone mode)
|
|
11
|
+
* @param options.translations - An object containing all translations for all locales (required in standalone mode)
|
|
12
|
+
* @param options.fallbackLocale - Optional fallback locale if a translation is not found
|
|
13
|
+
*
|
|
14
|
+
* @returns An object with translation utilities including the full IntlShape instance
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* Standalone mode:
|
|
18
|
+
* ```tsx
|
|
19
|
+
* const translations = {
|
|
20
|
+
* en: {
|
|
21
|
+
* "greeting": "Hello, {name}!",
|
|
22
|
+
* "welcome": "Welcome to our app"
|
|
23
|
+
* },
|
|
24
|
+
* es: {
|
|
25
|
+
* "greeting": "Hola, {name}!",
|
|
26
|
+
* "welcome": "Bienvenido a nuestra aplicación"
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* function MyComponent() {
|
|
31
|
+
* const { t, intl, locale, setLocale } = useTranslation({
|
|
32
|
+
* locale: 'en',
|
|
33
|
+
* translations
|
|
34
|
+
* })
|
|
35
|
+
*
|
|
36
|
+
* return (
|
|
37
|
+
* <div>
|
|
38
|
+
* <p>{t('greeting', { name: 'John' })}</p>
|
|
39
|
+
* <p>{intl.formatNumber(1000)}</p>
|
|
40
|
+
* <p>{intl.formatDate(new Date())}</p>
|
|
41
|
+
* <button onClick={() => setLocale('es')}>Switch to Spanish</button>
|
|
42
|
+
* </div>
|
|
43
|
+
* )
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* Context mode:
|
|
49
|
+
* ```tsx
|
|
50
|
+
* function App() {
|
|
51
|
+
* return (
|
|
52
|
+
* <TranslationProvider
|
|
53
|
+
* locale="en"
|
|
54
|
+
* translations={{
|
|
55
|
+
* en: { greeting: "Hello!" },
|
|
56
|
+
* es: { greeting: "Hola!" }
|
|
57
|
+
* }}
|
|
58
|
+
* >
|
|
59
|
+
* <MyComponent />
|
|
60
|
+
* </TranslationProvider>
|
|
61
|
+
* )
|
|
62
|
+
* }
|
|
63
|
+
*
|
|
64
|
+
* function MyComponent() {
|
|
65
|
+
* const { t, locale, setLocale } = useTranslation()
|
|
66
|
+
*
|
|
67
|
+
* return (
|
|
68
|
+
* <div>
|
|
69
|
+
* <p>{t('greeting')}</p>
|
|
70
|
+
* <button onClick={() => setLocale('es')}>Switch to Spanish</button>
|
|
71
|
+
* </div>
|
|
72
|
+
* )
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
declare const useTranslation: <L extends string = string>(options?: Partial<TranslationOptions<L>>) => TranslationResult;
|
|
77
|
+
export { useTranslation };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useCallback, useContext, useMemo, useState } from "react";
|
|
2
|
+
import { createIntl, createIntlCache } from "@formatjs/intl";
|
|
3
|
+
import { TranslationContext } from "../../contexts/translation/TranslationContext";
|
|
4
|
+
// Create a cache for @formatjs/intl to improve performance
|
|
5
|
+
const cache = createIntlCache();
|
|
6
|
+
/**
|
|
7
|
+
* Hook to manage translations in a React application using @formatjs/intl
|
|
8
|
+
*
|
|
9
|
+
* Can be used in two ways:
|
|
10
|
+
* 1. With options (standalone mode): Provides translations directly
|
|
11
|
+
* 2. Without options (context mode): Uses TranslationProvider context
|
|
12
|
+
*
|
|
13
|
+
* @param options - Optional configuration options for translations. If not provided, will use TranslationProvider context.
|
|
14
|
+
* @param options.locale - The initial locale to use (required in standalone mode)
|
|
15
|
+
* @param options.translations - An object containing all translations for all locales (required in standalone mode)
|
|
16
|
+
* @param options.fallbackLocale - Optional fallback locale if a translation is not found
|
|
17
|
+
*
|
|
18
|
+
* @returns An object with translation utilities including the full IntlShape instance
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* Standalone mode:
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const translations = {
|
|
24
|
+
* en: {
|
|
25
|
+
* "greeting": "Hello, {name}!",
|
|
26
|
+
* "welcome": "Welcome to our app"
|
|
27
|
+
* },
|
|
28
|
+
* es: {
|
|
29
|
+
* "greeting": "Hola, {name}!",
|
|
30
|
+
* "welcome": "Bienvenido a nuestra aplicación"
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* function MyComponent() {
|
|
35
|
+
* const { t, intl, locale, setLocale } = useTranslation({
|
|
36
|
+
* locale: 'en',
|
|
37
|
+
* translations
|
|
38
|
+
* })
|
|
39
|
+
*
|
|
40
|
+
* return (
|
|
41
|
+
* <div>
|
|
42
|
+
* <p>{t('greeting', { name: 'John' })}</p>
|
|
43
|
+
* <p>{intl.formatNumber(1000)}</p>
|
|
44
|
+
* <p>{intl.formatDate(new Date())}</p>
|
|
45
|
+
* <button onClick={() => setLocale('es')}>Switch to Spanish</button>
|
|
46
|
+
* </div>
|
|
47
|
+
* )
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* Context mode:
|
|
53
|
+
* ```tsx
|
|
54
|
+
* function App() {
|
|
55
|
+
* return (
|
|
56
|
+
* <TranslationProvider
|
|
57
|
+
* locale="en"
|
|
58
|
+
* translations={{
|
|
59
|
+
* en: { greeting: "Hello!" },
|
|
60
|
+
* es: { greeting: "Hola!" }
|
|
61
|
+
* }}
|
|
62
|
+
* >
|
|
63
|
+
* <MyComponent />
|
|
64
|
+
* </TranslationProvider>
|
|
65
|
+
* )
|
|
66
|
+
* }
|
|
67
|
+
*
|
|
68
|
+
* function MyComponent() {
|
|
69
|
+
* const { t, locale, setLocale } = useTranslation()
|
|
70
|
+
*
|
|
71
|
+
* return (
|
|
72
|
+
* <div>
|
|
73
|
+
* <p>{t('greeting')}</p>
|
|
74
|
+
* <button onClick={() => setLocale('es')}>Switch to Spanish</button>
|
|
75
|
+
* </div>
|
|
76
|
+
* )
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
const useTranslation = (options) => {
|
|
81
|
+
const context = useContext(TranslationContext);
|
|
82
|
+
if (!options && !context) {
|
|
83
|
+
throw new Error("useTranslation must be used with either options or within a TranslationProvider");
|
|
84
|
+
}
|
|
85
|
+
if (!options && context) {
|
|
86
|
+
return context;
|
|
87
|
+
}
|
|
88
|
+
if (options && !options.locale) {
|
|
89
|
+
throw new Error("locale is required when using useTranslation with options");
|
|
90
|
+
}
|
|
91
|
+
if (options && !options.translations) {
|
|
92
|
+
throw new Error("translations is required when using useTranslation with options");
|
|
93
|
+
}
|
|
94
|
+
const validatedOptions = options;
|
|
95
|
+
const [state, setState] = useState({
|
|
96
|
+
locale: validatedOptions.locale,
|
|
97
|
+
translations: validatedOptions.translations,
|
|
98
|
+
error: null,
|
|
99
|
+
});
|
|
100
|
+
// Create intl instance with current locale and translations
|
|
101
|
+
const intl = useMemo(() => {
|
|
102
|
+
const currentTranslations = state.translations[state.locale];
|
|
103
|
+
const fallbackTranslations = validatedOptions.fallbackLocale &&
|
|
104
|
+
state.translations[validatedOptions.fallbackLocale];
|
|
105
|
+
if (!currentTranslations && fallbackTranslations) {
|
|
106
|
+
console.error(`No translations found for locale "${state.locale}". Using fallback locale "${validatedOptions.fallbackLocale}".`);
|
|
107
|
+
return createIntl({
|
|
108
|
+
locale: state.locale,
|
|
109
|
+
messages: fallbackTranslations,
|
|
110
|
+
}, cache);
|
|
111
|
+
}
|
|
112
|
+
if (!currentTranslations) {
|
|
113
|
+
console.error(`No translations found for locale "${state.locale}". Using empty translations.`);
|
|
114
|
+
return createIntl({
|
|
115
|
+
locale: state.locale,
|
|
116
|
+
messages: {},
|
|
117
|
+
}, cache);
|
|
118
|
+
}
|
|
119
|
+
const shouldMergeFallback = validatedOptions.fallbackLocale &&
|
|
120
|
+
state.locale !== validatedOptions.fallbackLocale &&
|
|
121
|
+
fallbackTranslations;
|
|
122
|
+
const messages = shouldMergeFallback
|
|
123
|
+
? {
|
|
124
|
+
...fallbackTranslations,
|
|
125
|
+
...currentTranslations,
|
|
126
|
+
}
|
|
127
|
+
: currentTranslations;
|
|
128
|
+
return createIntl({
|
|
129
|
+
locale: state.locale,
|
|
130
|
+
messages,
|
|
131
|
+
}, cache);
|
|
132
|
+
}, [state.locale, state.translations, validatedOptions.fallbackLocale]);
|
|
133
|
+
// Simplified t() function using intl.formatMessage
|
|
134
|
+
const t = useCallback((key, values) => {
|
|
135
|
+
return intl.formatMessage({ id: key }, values);
|
|
136
|
+
}, [intl]);
|
|
137
|
+
const setLocale = useCallback((newLocale) => {
|
|
138
|
+
if (!state.translations[newLocale]) {
|
|
139
|
+
console.error(`Locale "${newLocale}" not found in translations. Available locales: ${Object.keys(state.translations).join(", ")}`);
|
|
140
|
+
setState((current) => ({
|
|
141
|
+
...current,
|
|
142
|
+
error: `Locale "${newLocale}" not found`,
|
|
143
|
+
}));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
setState((current) => ({
|
|
147
|
+
...current,
|
|
148
|
+
locale: newLocale,
|
|
149
|
+
error: null,
|
|
150
|
+
}));
|
|
151
|
+
}, [state.translations]);
|
|
152
|
+
return {
|
|
153
|
+
t,
|
|
154
|
+
intl,
|
|
155
|
+
locale: state.locale,
|
|
156
|
+
setLocale,
|
|
157
|
+
error: state.error,
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
export { useTranslation };
|
|
161
|
+
//# sourceMappingURL=useTranslation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTranslation.js","sourceRoot":"","sources":["../../../src/hooks/useTranslation/useTranslation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IntlShape } from "@formatjs/intl";
|
|
2
|
+
type Translations = Record<string, string>;
|
|
3
|
+
type LanguageTranslations = Record<string, Translations>;
|
|
4
|
+
type TranslationState<L extends string = string> = {
|
|
5
|
+
locale: L;
|
|
6
|
+
translations: LanguageTranslations;
|
|
7
|
+
error: string | null;
|
|
8
|
+
};
|
|
9
|
+
type TranslationOptions<L extends string = string> = {
|
|
10
|
+
locale: L;
|
|
11
|
+
translations: LanguageTranslations;
|
|
12
|
+
fallbackLocale?: L;
|
|
13
|
+
};
|
|
14
|
+
type TranslationResult = {
|
|
15
|
+
t: (key: string, values?: Record<string, string | number>) => string;
|
|
16
|
+
intl: IntlShape;
|
|
17
|
+
locale: string;
|
|
18
|
+
setLocale: (locale: string) => void;
|
|
19
|
+
error: string | null;
|
|
20
|
+
};
|
|
21
|
+
export type { LanguageTranslations, TranslationOptions, TranslationResult, TranslationState, Translations, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTranslation.type.js","sourceRoot":"","sources":["../../../src/hooks/useTranslation/useTranslation.type.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcl/hooks",
|
|
3
|
-
"version": "1.0.1-
|
|
3
|
+
"version": "1.0.1-20251229200811.commit-7c01c67",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "esm/index.js",
|
|
6
6
|
"module": "esm/index.js",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"typescript-eslint": "^7.15.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
+
"@formatjs/intl": "^3.1.8",
|
|
51
52
|
"@segment/analytics-next": "^1.79.0",
|
|
52
53
|
"@sentry/browser": "^9.0.0",
|
|
53
54
|
"isbot": "^5.1.25",
|
|
@@ -77,5 +78,5 @@
|
|
|
77
78
|
"<rootDir>/test/setup.ts"
|
|
78
79
|
]
|
|
79
80
|
},
|
|
80
|
-
"commit": "
|
|
81
|
+
"commit": "7c01c67ea4f59e6cd131b191224dc86e24b568c5"
|
|
81
82
|
}
|