cloudflare-next-intl 0.7.0 → 0.7.1
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
CHANGED
|
@@ -96,6 +96,14 @@ export default async function Page() {
|
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
`t(key)` always returns a `string`. For a message whose value is an array
|
|
100
|
+
or nested object (e.g. a list), use `t.raw(key)` to get it back as-is:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
const t = await getTranslations("Index");
|
|
104
|
+
const items = t.raw("items") as string[]; // messages.Index.items
|
|
105
|
+
```
|
|
106
|
+
|
|
99
107
|
### Client Components
|
|
100
108
|
|
|
101
109
|
```tsx
|
|
@@ -13,7 +13,9 @@ describe('cache_variables', () => {
|
|
|
13
13
|
getMessageCache(undefined);
|
|
14
14
|
});
|
|
15
15
|
bench('setTranslationCache + getTranslationCache hit', () => {
|
|
16
|
-
|
|
16
|
+
const fn = (k) => k;
|
|
17
|
+
fn.raw = (k) => k;
|
|
18
|
+
setTranslationCache('en-common', fn);
|
|
17
19
|
getTranslationCache('en-common');
|
|
18
20
|
});
|
|
19
21
|
});
|
|
@@ -18,6 +18,7 @@ const errorAndReturnFallback = (message, cacheKey, locale, namespace, key) => {
|
|
|
18
18
|
].filter(Boolean); // Filter out empty parts
|
|
19
19
|
console.error(parts.join(' | '));
|
|
20
20
|
const fallbackFn = (k) => k; // Fallback function simply returns the key
|
|
21
|
+
fallbackFn.raw = (k) => k;
|
|
21
22
|
setTranslationCache(cacheKey, fallbackFn);
|
|
22
23
|
return fallbackFn;
|
|
23
24
|
};
|
|
@@ -29,10 +30,10 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
|
|
|
29
30
|
// Traverse the translation object based on the namespace parts.
|
|
30
31
|
for (let i = 0; i < namespaceParts.length; i++) {
|
|
31
32
|
const part = namespaceParts[i];
|
|
32
|
-
const nextLevel = currentLevel[part];
|
|
33
|
+
const nextLevel = Array.isArray(currentLevel) ? undefined : currentLevel[part];
|
|
33
34
|
if (i === namespaceParts.length - 1) {
|
|
34
35
|
// Last part of the namespace, should resolve to an object (the base for translations).
|
|
35
|
-
if (typeof nextLevel === 'object' && nextLevel !== null) {
|
|
36
|
+
if (typeof nextLevel === 'object' && nextLevel !== null && !Array.isArray(nextLevel)) {
|
|
36
37
|
translationsBase = nextLevel;
|
|
37
38
|
}
|
|
38
39
|
else {
|
|
@@ -77,7 +78,7 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
|
|
|
77
78
|
console.warn(`Translation key "${key}" in namespace "${namespace}" leads to a string prematurely at "${part}" for locale "${locale}".`);
|
|
78
79
|
return key; // Return the key as fallback
|
|
79
80
|
}
|
|
80
|
-
const value = currentTranslation[part];
|
|
81
|
+
const value = Array.isArray(currentTranslation) ? undefined : currentTranslation[part];
|
|
81
82
|
if (i === keyParts.length - 1) {
|
|
82
83
|
if (typeof value !== 'string') {
|
|
83
84
|
console.warn(`Translation key "${key}" in namespace "${namespace}" resolves to a non-string value for locale "${locale}". Expected string, got "${typeof value}".`);
|
|
@@ -104,6 +105,68 @@ export function getTranslationsImpl(locale, messages, namespace, cacheKey) {
|
|
|
104
105
|
console.warn(`Translation key "${key}" in namespace "${namespace}" is missing or not a string for locale "${locale}".`);
|
|
105
106
|
return key; // Return the key as fallback
|
|
106
107
|
};
|
|
108
|
+
/**
|
|
109
|
+
* `t.raw(key)` — the escape hatch for when a message value isn't a
|
|
110
|
+
* plain string.
|
|
111
|
+
*
|
|
112
|
+
* `t(key)` (the main `translateFunction` above) ALWAYS returns a
|
|
113
|
+
* `string`; if the value at `key` is an array or a nested object, it
|
|
114
|
+
* warns and falls back to returning `key` itself. Use `t.raw(key)`
|
|
115
|
+
* instead whenever your `messages/<locale>.json` stores a list or
|
|
116
|
+
* object under that key (e.g. a list of social links, a table of
|
|
117
|
+
* FAQ entries, a settings sub-object) — it returns the value exactly
|
|
118
|
+
* as it appears in the JSON, unmodified: string stays string, array
|
|
119
|
+
* stays array, object stays object.
|
|
120
|
+
*
|
|
121
|
+
* Mirrors `next-intl`'s `t.raw` API, so existing `next-intl` knowledge
|
|
122
|
+
* transfers directly.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // messages/en.json: { "Index": { "items": ["a", "b", "c"] } }
|
|
126
|
+
* const t = await getTranslations("Index");
|
|
127
|
+
* const items = t.raw("items") as string[]; // ["a", "b", "c"]
|
|
128
|
+
*
|
|
129
|
+
* @param key Dot-separated path into the resolved namespace, same
|
|
130
|
+
* format as `translateFunction`'s `key` (e.g. `"items"` or
|
|
131
|
+
* `"section.list"`).
|
|
132
|
+
* @returns The raw `TranslationEntry` at `key` (string | object | array),
|
|
133
|
+
* or `key` itself if the path doesn't resolve (missing key, or an
|
|
134
|
+
* intermediate segment isn't an object) — matching `translateFunction`'s
|
|
135
|
+
* fallback-to-key behavior on lookup failure.
|
|
136
|
+
*/
|
|
137
|
+
const rawFunction = (key) => {
|
|
138
|
+
const keyParts = key.split('.');
|
|
139
|
+
let currentTranslation = translationsBase;
|
|
140
|
+
for (let i = 0; i < keyParts.length; i++) {
|
|
141
|
+
const part = keyParts[i];
|
|
142
|
+
if (typeof currentTranslation === 'string' || Array.isArray(currentTranslation)) {
|
|
143
|
+
console.warn(`Translation key "${key}" in namespace "${namespace}" leads to a non-object prematurely at "${part}" for locale "${locale}".`);
|
|
144
|
+
return key;
|
|
145
|
+
}
|
|
146
|
+
const value = currentTranslation[part];
|
|
147
|
+
if (i === keyParts.length - 1) {
|
|
148
|
+
if (value === undefined) {
|
|
149
|
+
console.warn(`Translation key "${key}" in namespace "${namespace}" is missing for locale "${locale}".`);
|
|
150
|
+
return key;
|
|
151
|
+
}
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
if (typeof value === 'object' && value !== null) {
|
|
156
|
+
currentTranslation = value;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.warn(`Translation key "${key}" in namespace "${namespace}" has invalid structure at "${part}" for locale "${locale}". Expected object, got "${typeof value}".`);
|
|
160
|
+
return key;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return key;
|
|
165
|
+
};
|
|
166
|
+
// Attach `.raw` onto the same callable function object so callers get
|
|
167
|
+
// one value that works both as `t(key)` and `t.raw(key)`, exactly like
|
|
168
|
+
// `next-intl`'s translator shape.
|
|
169
|
+
translateFunction.raw = rawFunction;
|
|
107
170
|
setTranslationCache(cacheKeyValue, translateFunction);
|
|
108
171
|
return translateFunction;
|
|
109
172
|
}
|
|
@@ -714,12 +714,30 @@ export interface CookieAttributes {
|
|
|
714
714
|
*/
|
|
715
715
|
secure?: boolean | undefined;
|
|
716
716
|
}
|
|
717
|
-
export type TranslationEntry = string | TranslationObject;
|
|
717
|
+
export type TranslationEntry = string | TranslationObject | TranslationEntry[];
|
|
718
718
|
export interface TranslationObject {
|
|
719
719
|
[key: string]: TranslationEntry;
|
|
720
720
|
}
|
|
721
721
|
export type ReturnType = string;
|
|
722
|
-
export
|
|
722
|
+
export interface TranslatorReturnType {
|
|
723
|
+
/** Looks up `key` and coerces it to a `string`. If the value at `key` isn't a plain string (it's an array or object), this warns and returns `key` itself — use {@link TranslatorReturnType.raw} for those cases instead. */
|
|
724
|
+
(key: string): ReturnType;
|
|
725
|
+
/**
|
|
726
|
+
* Escape hatch for non-string message values. `t(key)` always returns a
|
|
727
|
+
* `string` and can't represent arrays/objects; `t.raw(key)` returns the
|
|
728
|
+
* value exactly as stored in `messages/<locale>.json` — string, array,
|
|
729
|
+
* or nested object, unmodified. Use it whenever a message is a list
|
|
730
|
+
* (e.g. social links, FAQ entries) rather than plain text. Mirrors
|
|
731
|
+
* `next-intl`'s `t.raw`, so existing `next-intl` usage patterns apply
|
|
732
|
+
* as-is.
|
|
733
|
+
*
|
|
734
|
+
* @example
|
|
735
|
+
* // messages/en.json: { "Index": { "items": ["a", "b", "c"] } }
|
|
736
|
+
* const t = await getTranslations("Index");
|
|
737
|
+
* const items = t.raw("items") as string[]; // ["a", "b", "c"]
|
|
738
|
+
*/
|
|
739
|
+
raw(key: string): TranslationEntry;
|
|
740
|
+
}
|
|
723
741
|
export type changeFrequency = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never' | undefined;
|
|
724
742
|
export type Alternates = {
|
|
725
743
|
languages?: Languages<string> | undefined;
|