@vocab/react 0.0.0-intl-dep-bump-202301323944

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/CHANGELOG.md ADDED
@@ -0,0 +1,176 @@
1
+ # @vocab/react
2
+
3
+ ## 0.0.0-intl-dep-bump-202301323944
4
+
5
+ ### Patch Changes
6
+
7
+ - [`b63496a`](https://github.com/seek-oss/vocab/commit/b63496a4e25c1537e9b10057749e6660b24c777b) Thanks [@askoufis](https://github.com/askoufis)! - Update intl dependencies
8
+
9
+ - Updated dependencies [[`b63496a`](https://github.com/seek-oss/vocab/commit/b63496a4e25c1537e9b10057749e6660b24c777b)]:
10
+ - @vocab/types@0.0.0-intl-dep-bump-202301323944
11
+
12
+ ## 1.1.2
13
+
14
+ ### Patch Changes
15
+
16
+ - [`240d6ad`](https://github.com/seek-oss/vocab/commit/240d6ad7e0cf43fed92655a2f95fb463bd7b6644) [#85](https://github.com/seek-oss/vocab/pull/85) Thanks [@askoufis](https://github.com/askoufis)! - The `t` function returned from `useTranslations` is now memoized. `t` should now only change after the initial loading of translations, and when the language changes, making it more useful inside a hook's dependency array.
17
+
18
+ ## 1.1.1
19
+
20
+ ### Patch Changes
21
+
22
+ - [`e9c7067`](https://github.com/seek-oss/vocab/commit/e9c7067b31215a176e70ac1e73f2c878107f328f) [#83](https://github.com/seek-oss/vocab/pull/83) Thanks [@michaeltaranto](https://github.com/michaeltaranto)! - Add React 18 support
23
+
24
+ ## 1.1.0
25
+
26
+ ### Minor Changes
27
+
28
+ - [`6de02b3`](https://github.com/seek-oss/vocab/commit/6de02b35839e8ecdd9016fec49a95e17d3696f87) [#69](https://github.com/seek-oss/vocab/pull/69) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Automatically assign keys to React elements
29
+
30
+ Previously, when using React elements within translation templates, React would warn about missing keys as the return type is an array. This meant you needed to supply a key manually. Vocab can now automatically assign a key to React elements. Keys that are passed explicitly will remain untouched.
31
+
32
+ ## 1.0.2
33
+
34
+ ### Patch Changes
35
+
36
+ - [`3ec6dba`](https://github.com/seek-oss/vocab/commit/3ec6dbaad590299cc33e2d9d4a877576eb05853a) [#63](https://github.com/seek-oss/vocab/pull/63) Thanks [@jahredhope](https://github.com/jahredhope)! - Migrate to new @formatjs/icu-messageformat-parser as intl-messageformat-parser has been deprecated
37
+
38
+ - Updated dependencies [[`3ec6dba`](https://github.com/seek-oss/vocab/commit/3ec6dbaad590299cc33e2d9d4a877576eb05853a)]:
39
+ - @vocab/types@1.0.1
40
+
41
+ ## 1.0.1
42
+
43
+ ### Patch Changes
44
+
45
+ - [`c9a38dd`](https://github.com/seek-oss/vocab/commit/c9a38dd15e2c2a47fc4d5eb2348fdd08a6982768) [#54](https://github.com/seek-oss/vocab/pull/54) Thanks [@jahredhope](https://github.com/jahredhope)! - Allow special characters within translation keys and messages
46
+
47
+ * [`c9a38dd`](https://github.com/seek-oss/vocab/commit/c9a38dd15e2c2a47fc4d5eb2348fdd08a6982768) [#54](https://github.com/seek-oss/vocab/pull/54) Thanks [@jahredhope](https://github.com/jahredhope)! - Add a warning when accessing a key that doesn't exist
48
+
49
+ ## 1.0.0
50
+
51
+ ### Major Changes
52
+
53
+ - [`3031054`](https://github.com/seek-oss/vocab/commit/303105440851db6126f0606e1607745b27dd981c) [#51](https://github.com/seek-oss/vocab/pull/51) Thanks [@jahredhope](https://github.com/jahredhope)! - Release v1.0.0
54
+
55
+ Release Vocab as v1.0.0 to signify a stable API and support future [semver versioning](https://semver.org/) releases.
56
+
57
+ Vocab has seen a lot of iteration and changes since it was first published on 20 November 2020. We are now confident with the API and believe Vocab is ready for common use.
58
+
59
+ ### Patch Changes
60
+
61
+ - [`0074382`](https://github.com/seek-oss/vocab/commit/007438273ef70f5d5ded45777933651ad8df36f6) [#52](https://github.com/seek-oss/vocab/pull/52) Thanks [@jahredhope](https://github.com/jahredhope)! - Remove React dependency on core types.
62
+
63
+ Direct use of tags in Translations now have stricter type definitions.
64
+
65
+ - Updated dependencies [[`0074382`](https://github.com/seek-oss/vocab/commit/007438273ef70f5d5ded45777933651ad8df36f6), [`3031054`](https://github.com/seek-oss/vocab/commit/303105440851db6126f0606e1607745b27dd981c)]:
66
+ - @vocab/types@1.0.0
67
+
68
+ ## 0.0.12
69
+
70
+ ### Patch Changes
71
+
72
+ - [`5b1fdc0`](https://github.com/seek-oss/vocab/commit/5b1fdc019522b12e7ef94b2fec57b54a9310d41c) [#46](https://github.com/seek-oss/vocab/pull/46) Thanks [@jahredhope](https://github.com/jahredhope)! - Enable the use of translation files directly with 3 new documented methods for working with translations.
73
+
74
+ ```typescript
75
+ /**
76
+ * Retrieve messages. If not available, will attempt to load messages and resolve once complete.
77
+ */
78
+ translations.getMessages(language);
79
+ /**
80
+ * Retrieve already loaded messages. Will return null if messages haven't been loaded.
81
+ */
82
+ translations.getLoadedMessages(language);
83
+ /**
84
+ * Load messages for the given language. Resolving once complete.
85
+ */
86
+ translations.load(language);
87
+ ```
88
+
89
+ - Updated dependencies [[`5b1fdc0`](https://github.com/seek-oss/vocab/commit/5b1fdc019522b12e7ef94b2fec57b54a9310d41c)]:
90
+ - @vocab/types@0.0.9
91
+
92
+ ## 0.0.11
93
+
94
+ ### Patch Changes
95
+
96
+ - [`f2fca67`](https://github.com/seek-oss/vocab/commit/f2fca679c66ae65405a0aa24f0a0e472026aad0d) [#36](https://github.com/seek-oss/vocab/pull/36) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Support custom locales for ICU message parsing
97
+
98
+ * [`6c725f4`](https://github.com/seek-oss/vocab/commit/6c725f43c5eaed9b248c8452ff6f83cef1b2f61c) [#35](https://github.com/seek-oss/vocab/pull/35) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Rename `useTranslation` to `useTranslations`
99
+
100
+ * Updated dependencies [[`f2fca67`](https://github.com/seek-oss/vocab/commit/f2fca679c66ae65405a0aa24f0a0e472026aad0d)]:
101
+ - @vocab/types@0.0.8
102
+
103
+ ## 0.0.10
104
+
105
+ ### Patch Changes
106
+
107
+ - Updated dependencies [[`283bcad`](https://github.com/seek-oss/vocab/commit/283bcada06e622ab14ed891743ed3f55cf09e245), [`f3992ef`](https://github.com/seek-oss/vocab/commit/f3992efbf08939ebf853fac650a49cc46dc51dfb)]:
108
+ - @vocab/types@0.0.7
109
+
110
+ ## 0.0.9
111
+
112
+ ### Patch Changes
113
+
114
+ - Updated dependencies [[`80a46c0`](https://github.com/seek-oss/vocab/commit/80a46c01a55408675f5822c3618519f80136c3ab)]:
115
+ - @vocab/types@0.0.6
116
+
117
+ ## 0.0.8
118
+
119
+ ### Patch Changes
120
+
121
+ - [`b51db12`](https://github.com/seek-oss/vocab/commit/b51db125b6d5e29feb77eac20a45b410e79be9b2) [#21](https://github.com/seek-oss/vocab/pull/21) Thanks [@jahredhope](https://github.com/jahredhope)! - Rename TranslationsProvider to VocabProvider
122
+
123
+ ## 0.0.7
124
+
125
+ ### Patch Changes
126
+
127
+ - [`5f5c581`](https://github.com/seek-oss/vocab/commit/5f5c581a65bff28729ee19e1ec0bdea488a9d6c2) [#19](https://github.com/seek-oss/vocab/pull/19) Thanks [@jahredhope](https://github.com/jahredhope)! - Compile useable TypeScript importable files with `vocab compile`.
128
+
129
+ The new `vocab compile` step replaces `vocab generate-types` in creating a fully functional **translations.ts** file.
130
+
131
+ This allows vocab to be used **without the Webpack Plugin**, however use of the plugin is still heavily advised to ensure optimal loading of translation content on the web.
132
+
133
+ Support for unit testing is now better than ever! The newly created **translations.ts** means your unit test code will see the same code as available while rendering.
134
+
135
+ See the [documentation](https://github.com/seek-oss/vocab) for further usage details.
136
+
137
+ - Updated dependencies [[`5f5c581`](https://github.com/seek-oss/vocab/commit/5f5c581a65bff28729ee19e1ec0bdea488a9d6c2)]:
138
+ - @vocab/types@0.0.5
139
+
140
+ ## 0.0.6
141
+
142
+ ### Patch Changes
143
+
144
+ - [`26b52f4`](https://github.com/seek-oss/vocab/commit/26b52f4878ded440841e08c858bdc9e685500c2a) [#16](https://github.com/seek-oss/vocab/pull/16) Thanks [@jahredhope](https://github.com/jahredhope)! - Enable debugging with DEBUG environment variable
145
+
146
+ - Updated dependencies [[`08de30d`](https://github.com/seek-oss/vocab/commit/08de30d338c2a5ebdcf14da7c736dddf22e7ca9e)]:
147
+ - @vocab/types@0.0.4
148
+
149
+ ## 0.0.5
150
+
151
+ ### Patch Changes
152
+
153
+ - [`4710f34`](https://github.com/seek-oss/vocab/commit/4710f341f2827643e3eff69ef7e26d44ec6e8a2b) [#8](https://github.com/seek-oss/vocab/pull/8) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Infer `t` return type more intelligently
154
+
155
+ The translate key function (`t`) will now infer the return type as ReactNode only when the tag syntax is used.
156
+
157
+ - Updated dependencies [[`4710f34`](https://github.com/seek-oss/vocab/commit/4710f341f2827643e3eff69ef7e26d44ec6e8a2b)]:
158
+ - @vocab/types@0.0.3
159
+
160
+ ## 0.0.4
161
+
162
+ ### Patch Changes
163
+
164
+ - [`45c4fe2`](https://github.com/seek-oss/vocab/commit/45c4fe273c5157475cb03ca57db662956ad5cbc9) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Improved type definitions for `t` function
165
+
166
+ ## 0.0.3
167
+
168
+ ### Patch Changes
169
+
170
+ - [`f79c85e`](https://github.com/seek-oss/vocab/commit/f79c85e37c5b927306866961cf6cb3c541d0d6cf) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Add @vocab/types dep
171
+
172
+ ## 0.0.2
173
+
174
+ ### Patch Changes
175
+
176
+ - [`9f99ea7`](https://github.com/seek-oss/vocab/commit/9f99ea7c827ec4d7c21a485e17e3adbbd1c49319) Thanks [@jahredhope](https://github.com/jahredhope)! - Remove React as dependency and target node
@@ -0,0 +1,25 @@
1
+ import { TranslationFile, LanguageName, ParsedFormatFnByKey, ParsedFormatFn } from '@vocab/types';
2
+ import { ReactNode } from 'react';
3
+ declare type Locale = string;
4
+ interface TranslationsValue {
5
+ language: LanguageName;
6
+ locale?: Locale;
7
+ }
8
+ interface VocabProviderProps extends TranslationsValue {
9
+ children: ReactNode;
10
+ }
11
+ export declare const VocabProvider: ({ children, language, locale, }: VocabProviderProps) => JSX.Element;
12
+ export declare const useLanguage: () => TranslationsValue;
13
+ declare type FormatXMLElementReactNodeFn = (parts: ReactNode[]) => ReactNode;
14
+ declare type MapToReactNodeFunction<Params extends Record<string, any>> = {
15
+ [key in keyof Params]: Params[key] extends ParsedFormatFn ? FormatXMLElementReactNodeFn : Params[key];
16
+ };
17
+ declare type TranslateFn<FormatFnByKey extends ParsedFormatFnByKey> = {
18
+ <TranslationKey extends keyof FormatFnByKey>(key: TranslationKey, params: MapToReactNodeFunction<Parameters<FormatFnByKey[TranslationKey]>[0]>): ReturnType<FormatFnByKey[TranslationKey]> extends string ? string : ReactNode | string | Array<ReactNode | string>;
19
+ <TranslationKey extends keyof FormatFnByKey>(key: Parameters<FormatFnByKey[TranslationKey]>[0] extends Record<string, any> ? never : TranslationKey): string;
20
+ };
21
+ export declare function useTranslations<Language extends string, FormatFnByKey extends ParsedFormatFnByKey>(translations: TranslationFile<Language, FormatFnByKey>): {
22
+ ready: boolean;
23
+ t: TranslateFn<FormatFnByKey>;
24
+ };
25
+ export {};
@@ -0,0 +1 @@
1
+ export * from "./declarations/src/index";
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
+
11
+ const TranslationsContext = /*#__PURE__*/React__default['default'].createContext(undefined);
12
+ const VocabProvider = ({
13
+ children,
14
+ language,
15
+ locale
16
+ }) => {
17
+ const value = React.useMemo(() => ({
18
+ language,
19
+ locale
20
+ }), [language, locale]);
21
+ return /*#__PURE__*/React__default['default'].createElement(TranslationsContext.Provider, {
22
+ value: value
23
+ }, children);
24
+ };
25
+ const useLanguage = () => {
26
+ const context = React.useContext(TranslationsContext);
27
+
28
+ if (!context) {
29
+ throw new Error('Attempted to access translation without Vocab context set. Did you forget to render VocabProvider?');
30
+ }
31
+
32
+ if (!context.language) {
33
+ throw new Error('Attempted to access translation without language set. Did you forget to pass language to VocabProvider?');
34
+ }
35
+
36
+ return context;
37
+ };
38
+ const SERVER_RENDERING = typeof window === 'undefined';
39
+ function useTranslations(translations) {
40
+ const {
41
+ language,
42
+ locale
43
+ } = useLanguage();
44
+ const [, forceRender] = React.useReducer(s => s + 1, 0);
45
+ const translationsObject = translations.getLoadedMessages(language, locale || language);
46
+ let ready = true;
47
+
48
+ if (!translationsObject) {
49
+ if (SERVER_RENDERING) {
50
+ throw new Error(`Translations not synchronously available on server render. Applying translations dynamically server-side is not supported.`);
51
+ }
52
+
53
+ translations.load(language).then(() => {
54
+ forceRender();
55
+ });
56
+ ready = false;
57
+ }
58
+
59
+ const t = React.useCallback((key, params) => {
60
+ if (!translationsObject) {
61
+ return ' ';
62
+ }
63
+
64
+ const message = translationsObject === null || translationsObject === void 0 ? void 0 : translationsObject[key];
65
+
66
+ if (!message) {
67
+ // eslint-disable-next-line no-console
68
+ console.error(`Unable to find translation for key "${key}". Possible keys are ${Object.keys(translationsObject).map(v => `"${v}"`).join(', ')}`);
69
+ return '';
70
+ }
71
+
72
+ const result = message.format(params);
73
+
74
+ if (Array.isArray(result)) {
75
+ for (let i = 0; i < result.length; i++) {
76
+ const item = result[i];
77
+
78
+ if (typeof item === 'object' && item && !item.key && /*#__PURE__*/React.isValidElement(item)) {
79
+ result[i] = /*#__PURE__*/React.cloneElement(item, {
80
+ key: `_vocab-${i}`
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ return result;
87
+ }, [translationsObject]);
88
+ return {
89
+ ready,
90
+ t
91
+ };
92
+ }
93
+
94
+ exports.VocabProvider = VocabProvider;
95
+ exports.useLanguage = useLanguage;
96
+ exports.useTranslations = useTranslations;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ if (process.env.NODE_ENV === "production") {
4
+ module.exports = require("./vocab-react.cjs.prod.js");
5
+ } else {
6
+ module.exports = require("./vocab-react.cjs.dev.js");
7
+ }
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
+
11
+ const TranslationsContext = /*#__PURE__*/React__default['default'].createContext(undefined);
12
+ const VocabProvider = ({
13
+ children,
14
+ language,
15
+ locale
16
+ }) => {
17
+ const value = React.useMemo(() => ({
18
+ language,
19
+ locale
20
+ }), [language, locale]);
21
+ return /*#__PURE__*/React__default['default'].createElement(TranslationsContext.Provider, {
22
+ value: value
23
+ }, children);
24
+ };
25
+ const useLanguage = () => {
26
+ const context = React.useContext(TranslationsContext);
27
+
28
+ if (!context) {
29
+ throw new Error('Attempted to access translation without Vocab context set. Did you forget to render VocabProvider?');
30
+ }
31
+
32
+ if (!context.language) {
33
+ throw new Error('Attempted to access translation without language set. Did you forget to pass language to VocabProvider?');
34
+ }
35
+
36
+ return context;
37
+ };
38
+ const SERVER_RENDERING = typeof window === 'undefined';
39
+ function useTranslations(translations) {
40
+ const {
41
+ language,
42
+ locale
43
+ } = useLanguage();
44
+ const [, forceRender] = React.useReducer(s => s + 1, 0);
45
+ const translationsObject = translations.getLoadedMessages(language, locale || language);
46
+ let ready = true;
47
+
48
+ if (!translationsObject) {
49
+ if (SERVER_RENDERING) {
50
+ throw new Error(`Translations not synchronously available on server render. Applying translations dynamically server-side is not supported.`);
51
+ }
52
+
53
+ translations.load(language).then(() => {
54
+ forceRender();
55
+ });
56
+ ready = false;
57
+ }
58
+
59
+ const t = React.useCallback((key, params) => {
60
+ if (!translationsObject) {
61
+ return ' ';
62
+ }
63
+
64
+ const message = translationsObject === null || translationsObject === void 0 ? void 0 : translationsObject[key];
65
+
66
+ if (!message) {
67
+ // eslint-disable-next-line no-console
68
+ console.error(`Unable to find translation for key "${key}". Possible keys are ${Object.keys(translationsObject).map(v => `"${v}"`).join(', ')}`);
69
+ return '';
70
+ }
71
+
72
+ const result = message.format(params);
73
+
74
+ if (Array.isArray(result)) {
75
+ for (let i = 0; i < result.length; i++) {
76
+ const item = result[i];
77
+
78
+ if (typeof item === 'object' && item && !item.key && /*#__PURE__*/React.isValidElement(item)) {
79
+ result[i] = /*#__PURE__*/React.cloneElement(item, {
80
+ key: `_vocab-${i}`
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ return result;
87
+ }, [translationsObject]);
88
+ return {
89
+ ready,
90
+ t
91
+ };
92
+ }
93
+
94
+ exports.VocabProvider = VocabProvider;
95
+ exports.useLanguage = useLanguage;
96
+ exports.useTranslations = useTranslations;
@@ -0,0 +1,86 @@
1
+ import React, { useMemo, useContext, useReducer, useCallback, isValidElement, cloneElement } from 'react';
2
+
3
+ const TranslationsContext = /*#__PURE__*/React.createContext(undefined);
4
+ const VocabProvider = ({
5
+ children,
6
+ language,
7
+ locale
8
+ }) => {
9
+ const value = useMemo(() => ({
10
+ language,
11
+ locale
12
+ }), [language, locale]);
13
+ return /*#__PURE__*/React.createElement(TranslationsContext.Provider, {
14
+ value: value
15
+ }, children);
16
+ };
17
+ const useLanguage = () => {
18
+ const context = useContext(TranslationsContext);
19
+
20
+ if (!context) {
21
+ throw new Error('Attempted to access translation without Vocab context set. Did you forget to render VocabProvider?');
22
+ }
23
+
24
+ if (!context.language) {
25
+ throw new Error('Attempted to access translation without language set. Did you forget to pass language to VocabProvider?');
26
+ }
27
+
28
+ return context;
29
+ };
30
+ const SERVER_RENDERING = typeof window === 'undefined';
31
+ function useTranslations(translations) {
32
+ const {
33
+ language,
34
+ locale
35
+ } = useLanguage();
36
+ const [, forceRender] = useReducer(s => s + 1, 0);
37
+ const translationsObject = translations.getLoadedMessages(language, locale || language);
38
+ let ready = true;
39
+
40
+ if (!translationsObject) {
41
+ if (SERVER_RENDERING) {
42
+ throw new Error(`Translations not synchronously available on server render. Applying translations dynamically server-side is not supported.`);
43
+ }
44
+
45
+ translations.load(language).then(() => {
46
+ forceRender();
47
+ });
48
+ ready = false;
49
+ }
50
+
51
+ const t = useCallback((key, params) => {
52
+ if (!translationsObject) {
53
+ return ' ';
54
+ }
55
+
56
+ const message = translationsObject === null || translationsObject === void 0 ? void 0 : translationsObject[key];
57
+
58
+ if (!message) {
59
+ // eslint-disable-next-line no-console
60
+ console.error(`Unable to find translation for key "${key}". Possible keys are ${Object.keys(translationsObject).map(v => `"${v}"`).join(', ')}`);
61
+ return '';
62
+ }
63
+
64
+ const result = message.format(params);
65
+
66
+ if (Array.isArray(result)) {
67
+ for (let i = 0; i < result.length; i++) {
68
+ const item = result[i];
69
+
70
+ if (typeof item === 'object' && item && !item.key && /*#__PURE__*/isValidElement(item)) {
71
+ result[i] = /*#__PURE__*/cloneElement(item, {
72
+ key: `_vocab-${i}`
73
+ });
74
+ }
75
+ }
76
+ }
77
+
78
+ return result;
79
+ }, [translationsObject]);
80
+ return {
81
+ ready,
82
+ t
83
+ };
84
+ }
85
+
86
+ export { VocabProvider, useLanguage, useTranslations };
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@vocab/react",
3
+ "version": "0.0.0-intl-dep-bump-202301323944",
4
+ "main": "dist/vocab-react.cjs.js",
5
+ "module": "dist/vocab-react.esm.js",
6
+ "author": "SEEK",
7
+ "license": "MIT",
8
+ "peerDependencies": {
9
+ "react": ">=16.3.0"
10
+ },
11
+ "dependencies": {
12
+ "@vocab/types": "^0.0.0-intl-dep-bump-202301323944",
13
+ "intl-messageformat": "^10.0.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/react": "^18.0.9",
17
+ "react": "^18.1.0"
18
+ }
19
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,167 @@
1
+ import {
2
+ TranslationFile,
3
+ LanguageName,
4
+ ParsedFormatFnByKey,
5
+ ParsedFormatFn,
6
+ } from '@vocab/types';
7
+ import React, {
8
+ ReactNode,
9
+ useContext,
10
+ useMemo,
11
+ useReducer,
12
+ isValidElement,
13
+ cloneElement,
14
+ useCallback,
15
+ } from 'react';
16
+
17
+ type Locale = string;
18
+
19
+ interface TranslationsValue {
20
+ language: LanguageName;
21
+ locale?: Locale;
22
+ }
23
+
24
+ const TranslationsContext = React.createContext<TranslationsValue | undefined>(
25
+ undefined,
26
+ );
27
+
28
+ interface VocabProviderProps extends TranslationsValue {
29
+ children: ReactNode;
30
+ }
31
+ export const VocabProvider = ({
32
+ children,
33
+ language,
34
+ locale,
35
+ }: VocabProviderProps) => {
36
+ const value = useMemo(() => ({ language, locale }), [language, locale]);
37
+
38
+ return (
39
+ <TranslationsContext.Provider value={value}>
40
+ {children}
41
+ </TranslationsContext.Provider>
42
+ );
43
+ };
44
+
45
+ export const useLanguage = (): TranslationsValue => {
46
+ const context = useContext(TranslationsContext);
47
+ if (!context) {
48
+ throw new Error(
49
+ 'Attempted to access translation without Vocab context set. Did you forget to render VocabProvider?',
50
+ );
51
+ }
52
+ if (!context.language) {
53
+ throw new Error(
54
+ 'Attempted to access translation without language set. Did you forget to pass language to VocabProvider?',
55
+ );
56
+ }
57
+
58
+ return context;
59
+ };
60
+
61
+ const SERVER_RENDERING = typeof window === 'undefined';
62
+
63
+ type FormatXMLElementReactNodeFn = (parts: ReactNode[]) => ReactNode;
64
+
65
+ type MapToReactNodeFunction<Params extends Record<string, any>> = {
66
+ [key in keyof Params]: Params[key] extends ParsedFormatFn
67
+ ? FormatXMLElementReactNodeFn
68
+ : Params[key];
69
+ };
70
+
71
+ type TranslateFn<FormatFnByKey extends ParsedFormatFnByKey> = {
72
+ <TranslationKey extends keyof FormatFnByKey>(
73
+ key: TranslationKey,
74
+ params: MapToReactNodeFunction<
75
+ Parameters<FormatFnByKey[TranslationKey]>[0]
76
+ >,
77
+ ): ReturnType<FormatFnByKey[TranslationKey]> extends string
78
+ ? string
79
+ : ReactNode | string | Array<ReactNode | string>;
80
+ <TranslationKey extends keyof FormatFnByKey>(
81
+ key: Parameters<FormatFnByKey[TranslationKey]>[0] extends Record<
82
+ string,
83
+ any
84
+ >
85
+ ? never
86
+ : TranslationKey,
87
+ ): string;
88
+ };
89
+
90
+ export function useTranslations<
91
+ Language extends string,
92
+ FormatFnByKey extends ParsedFormatFnByKey,
93
+ >(
94
+ translations: TranslationFile<Language, FormatFnByKey>,
95
+ ): {
96
+ ready: boolean;
97
+ t: TranslateFn<FormatFnByKey>;
98
+ } {
99
+ const { language, locale } = useLanguage();
100
+ const [, forceRender] = useReducer((s: number) => s + 1, 0);
101
+
102
+ const translationsObject = translations.getLoadedMessages(
103
+ language as any,
104
+ locale || language,
105
+ );
106
+
107
+ let ready = true;
108
+
109
+ if (!translationsObject) {
110
+ if (SERVER_RENDERING) {
111
+ throw new Error(
112
+ `Translations not synchronously available on server render. Applying translations dynamically server-side is not supported.`,
113
+ );
114
+ }
115
+
116
+ translations.load(language as any).then(() => {
117
+ forceRender();
118
+ });
119
+ ready = false;
120
+ }
121
+
122
+ const t = useCallback(
123
+ (key: string, params?: any) => {
124
+ if (!translationsObject) {
125
+ return ' ';
126
+ }
127
+
128
+ const message = translationsObject?.[key];
129
+
130
+ if (!message) {
131
+ // eslint-disable-next-line no-console
132
+ console.error(
133
+ `Unable to find translation for key "${key}". Possible keys are ${Object.keys(
134
+ translationsObject,
135
+ )
136
+ .map((v) => `"${v}"`)
137
+ .join(', ')}`,
138
+ );
139
+ return '';
140
+ }
141
+
142
+ const result = message.format(params);
143
+
144
+ if (Array.isArray(result)) {
145
+ for (let i = 0; i < result.length; i++) {
146
+ const item = result[i];
147
+ if (
148
+ typeof item === 'object' &&
149
+ item &&
150
+ !item.key &&
151
+ isValidElement(item)
152
+ ) {
153
+ result[i] = cloneElement(item, { key: `_vocab-${i}` });
154
+ }
155
+ }
156
+ }
157
+
158
+ return result;
159
+ },
160
+ [translationsObject],
161
+ );
162
+
163
+ return {
164
+ ready,
165
+ t,
166
+ };
167
+ }