sanity-plugin-internationalized-array 4.0.3 → 5.0.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sanity-plugin-internationalized-array
2
2
 
3
- A plugin to register array fields with a custom input component to store field values in multiple languages, queryable by using the language ID as an array `_key`.
3
+ A plugin to register array fields with a custom input component to store field values in multiple languages, queryable by using a dedicated `language` field.
4
4
 
5
5
  ![Screenshot of an internationalized input](./img/internationalized-array.png)
6
6
 
@@ -15,6 +15,7 @@ A plugin to register array fields with a custom input component to store field v
15
15
  - [Usage with @sanity/language-filter](#usage-with-sanitylanguage-filter)
16
16
  - [Shape of stored data](#shape-of-stored-data)
17
17
  - [Querying data](#querying-data)
18
+ - [Migrate from v4 to v5](#migrate-from-v4-to-v5)
18
19
  - [Migrate from objects to arrays](#migrate-from-objects-to-arrays)
19
20
  - [Why store localized field data like this?](#why-store-localized-field-data-like-this)
20
21
  - [License](#license)
@@ -42,18 +43,18 @@ Add it as a plugin in sanity.config.ts (or .js):
42
43
  import {defineConfig} from 'sanity'
43
44
  import {internationalizedArray} from 'sanity-plugin-internationalized-array'
44
45
 
45
- export const defineConfig({
46
+ export default defineConfig({
46
47
  // ...
47
48
  plugins: [
48
49
  internationalizedArray({
49
50
  languages: [
50
51
  {id: 'en', title: 'English'},
51
- {id: 'fr', title: 'French'}
52
+ {id: 'fr', title: 'French'},
52
53
  ],
53
54
  defaultLanguages: ['en'],
54
55
  fieldTypes: ['string'],
55
- })
56
- ]
56
+ }),
57
+ ],
57
58
  })
58
59
  ```
59
60
 
@@ -143,16 +144,16 @@ The "Add all languages" button can be hidden with `buttonAddAll`.
143
144
  import {defineConfig} from 'sanity'
144
145
  import {internationalizedArray} from 'sanity-plugin-internationalized-array'
145
146
 
146
- export const defineConfig({
147
+ export default defineConfig({
147
148
  // ...
148
149
  plugins: [
149
150
  internationalizedArray({
150
151
  // ...other config
151
152
  buttonLocations: ['field', 'unstable__fieldAction', 'document'], // default ['field']
152
153
  buttonAddAll: false, // default true
153
- languageDisplay: 'codeOnly' // codeOnly (default) | titleOnly | titleAndCode
154
- })
155
- ]
154
+ languageDisplay: 'codeOnly', // codeOnly (default) | titleOnly | titleAndCode
155
+ }),
156
+ ],
156
157
  })
157
158
  ```
158
159
 
@@ -164,7 +165,7 @@ For more control over the `value` field, you can pass a schema definition into t
164
165
  import {defineConfig} from 'sanity'
165
166
  import {internationalizedArray} from 'sanity-plugin-internationalized-array'
166
167
 
167
- export const defineConfig({
168
+ export default defineConfig({
168
169
  // ...
169
170
  plugins: [
170
171
  internationalizedArray({
@@ -280,75 +281,163 @@ If you have many languages and authors that predominately write in only a few, [
280
281
 
281
282
  ![Internationalized array field filtered with language-filter](https://github.com/sanity-io/language-filter/assets/9684022/4b402520-4128-4e6e-af07-960a10be397e)
282
283
 
283
- Configure both plugins in your sanity.config.ts file:
284
+ The plugin includes built-in integration with `@sanity/language-filter`.
285
+ To enable it, add `languageFilter.documentTypes` in the plugin config for the document types that should show the filter.
284
286
 
285
287
  ```ts
286
- // ./sanity.config.ts
287
-
288
- import {defineConfig, isKeySegment} from 'sanity'
289
- import {languageFilter} from '@sanity/language-filter'
288
+ import {defineConfig} from 'sanity'
289
+ import {internationalizedArray} from 'sanity-plugin-internationalized-array'
290
290
 
291
291
  export default defineConfig({
292
- // ... other config
292
+ // ...
293
293
  plugins: [
294
- // ... other plugins
295
- languageFilter({
296
- // Use the same languages as the internationalized array plugin
297
- supportedLanguages: SUPPORTED_LANGUAGES,
298
- defaultLanguages: [],
299
- documentTypes: ['post'],
300
- filterField: (enclosingType, member, selectedLanguageIds) => {
301
- // Filter internationalized arrays
302
- if (
303
- enclosingType.jsonType === 'object' &&
304
- enclosingType.name.startsWith('internationalizedArray') &&
305
- 'kind' in member
306
- ) {
307
- // Get last two segments of the field's path
308
- const pathEnd = member.field.path.slice(-2)
309
- // If the second-last segment is a _key, and the last segment is `value`,
310
- // It's an internationalized array value
311
- // And the array _key is the language of the field
312
- const language =
313
- pathEnd[1] === 'value' && isKeySegment(pathEnd[0]) ? pathEnd[0]._key : null
314
-
315
- return language ? selectedLanguageIds.includes(language) : false
316
- }
317
-
318
- // Filter internationalized objects if you have them
319
- // `localeString` must be registered as a custom schema type
320
- if (enclosingType.jsonType === 'object' && enclosingType.name.startsWith('locale')) {
321
- return selectedLanguageIds.includes(member.name)
322
- }
323
-
324
- return true
294
+ internationalizedArray({
295
+ languages: [
296
+ {id: 'en', title: 'English'},
297
+ {id: 'fr', title: 'French'},
298
+ ],
299
+ defaultLanguages: ['en'],
300
+ fieldTypes: ['string'],
301
+ languageFilter: {
302
+ documentTypes: ['internationalizedPost', 'lesson'],
325
303
  },
326
304
  }),
327
305
  ],
328
306
  })
329
307
  ```
330
308
 
309
+ If you need more control, you can continue using `@sanity/language-filter` directly and pass `internationalizedArrayLanguageFilter` from this package as your `filterField`.
310
+
331
311
  ## Shape of stored data
332
312
 
333
- The custom input contains buttons which will add new array items with the language as the `_key` value. Data returned from this array will look like this:
313
+ The custom input contains buttons which will add new array items with a random `_key` and the language stored in a dedicated `language` field. Data returned from this array will look like this:
334
314
 
335
315
  ```json
336
316
  "greeting": [
337
- { "_key": "en", "value": "hello" },
338
- { "_key": "fr", "value": "bonjour" },
317
+ { "_key": "abc123", "language": "en", "value": "hello" },
318
+ { "_key": "def456", "language": "fr", "value": "bonjour" },
339
319
  ]
340
320
  ```
341
321
 
342
322
  ## Querying data
343
323
 
344
- Using GROQ filters you can query for a specific language key like so:
324
+ Using GROQ filters you can query for a specific language like so:
345
325
 
346
326
  ```js
347
327
  *[_type == "person"] {
348
- "greeting": greeting[_key == "en"][0].value
328
+ "greeting": greeting[language == "en"][0].value
349
329
  }
350
330
  ```
351
331
 
332
+ ## Migrate from v4 to v5
333
+
334
+ v5 stores the language identifier on a dedicated `language` field instead of `_key`.
335
+
336
+ ### 1. Backup your data.
337
+
338
+ You can manually backup your data using the sanity CLI.
339
+
340
+ ```
341
+ sanity dataset export production
342
+ ```
343
+
344
+ This creates a production.tar.gz file in your current directory containing all your documents and assets.
345
+
346
+ You can also specify a custom filename and location:
347
+
348
+ ```
349
+ sanity dataset export production ./backups/backup-2026-02-16.tar.gz
350
+ ```
351
+
352
+ If you ever need to restore, use the import command:
353
+
354
+ ```
355
+ sanity dataset import backup-2026-02-16.tar.gz production
356
+ ```
357
+
358
+ Or you can use the backup service, read more at https://www.sanity.io/docs/content-lake/backups
359
+
360
+ ### 2. Update your GROQ queries.
361
+
362
+ Use a backwards compatible query until your migration is ready and has been executed.
363
+
364
+ ```diff
365
+ *[_type == "person"] {
366
+ - "greeting": greeting[_key == "en"][0].value
367
+ + "greeting": greeting[language == "en" || _key == "en"][0].value
368
+ }
369
+ ```
370
+
371
+ ### 3. Data migration
372
+
373
+ The package exports a migration helper that you can run with the [migration CLI](https://www.sanity.io/docs/cli-reference/cli-migration).
374
+
375
+ Create a migration file in your project and export it:
376
+
377
+ ```ts
378
+ // ./migrations/migrateToLanguageField.ts
379
+ import {migrateToLanguageField} from 'sanity-plugin-internationalized-array/migrations'
380
+
381
+ // Add the document types that contain internationalized arrays.
382
+ // Example: ['internationalizedPost', 'translation.metadata']
383
+ const DOCUMENT_TYPES: string[] = []
384
+
385
+ export default migrateToLanguageField(DOCUMENT_TYPES)
386
+ ```
387
+
388
+ Then verify your migration with a dry run:
389
+
390
+ ```bash
391
+ pnpm sanity migration run migrateToLanguageField
392
+ ```
393
+
394
+ Once ready, run the migration:
395
+
396
+ ```bash
397
+ pnpm sanity migration run migrateToLanguageField --no-dry-run
398
+ ```
399
+
400
+ **If you use [@sanity/document-internationalization](https://github.com/sanity-io/plugins/tree/main/plugins/@sanity/document-internationalization):** Include `'translation.metadata'` in your migration's document types so that the `translations` array on metadata documents is migrated
401
+ And update `@sanity/document-internationalization` to `v6`
402
+
403
+ ### 4. Update your GROQ queries
404
+
405
+ Previously we updated the GROQ queries to support both locations for the language field. Once migration is complete, update the GROQ queries again to only use `language` and remove the dependency on `_key`.
406
+
407
+ ```diff
408
+ *[_type == "person"] {
409
+ - "greeting": greeting[language == "en" || _key == "en"][0].value
410
+ + "greeting": greeting[language == "en"][0].value
411
+ }
412
+ ```
413
+
414
+ ## Usage with language filter
415
+
416
+ The plugin now includes built-in integration with `@sanity/language-filter`.
417
+ To enable it, add `languageFilter.documentTypes` in the plugin config for the document types that should show the filter.
418
+
419
+ ```ts
420
+ import {defineConfig} from 'sanity'
421
+ import {internationalizedArray} from 'sanity-plugin-internationalized-array'
422
+
423
+ export default defineConfig({
424
+ // ...
425
+ plugins: [
426
+ internationalizedArray({
427
+ languages: [
428
+ {id: 'en', title: 'English'},
429
+ {id: 'fr', title: 'French'},
430
+ ],
431
+ defaultLanguages: ['en'],
432
+ fieldTypes: ['string'],
433
+ languageFilter: {
434
+ documentTypes: ['internationalizedPost', 'lesson'],
435
+ },
436
+ }),
437
+ ],
438
+ })
439
+ ```
440
+
352
441
  ## Migrate from objects to arrays
353
442
 
354
443
  [See the migration script](./migrations/transformObjectToArray.ts) inside `./migrations/transformObjectToArray.ts` of this plugin.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as sanity0 from "sanity";
2
2
  import { FieldDefinition, Rule, RuleTypeConstraint, SanityClient } from "sanity";
3
+ import { FilterFieldFunction } from "@sanity/language-filter";
3
4
  type Language = {
4
5
  id: Intl.UnicodeBCP47LocaleIdentifier;
5
6
  title: string;
@@ -25,13 +26,15 @@ type ArrayConfig = {
25
26
  * @deprecated Use InternationalizedArrayItem instead
26
27
  */
27
28
  type Value = {
28
- _key: string;
29
+ _key: string; /** Language identifier (e.g., 'en', 'fr'). Added in v5. */
30
+ language: string;
29
31
  value?: unknown;
30
32
  };
31
33
  declare function isInternationalizedArrayItemType(type: string): type is InternationalizedArrayItem['_type'];
32
34
  type InternationalizedArrayItem<T = unknown> = {
33
35
  _key: string;
34
- value?: T;
36
+ value?: T; /** Language identifier (e.g., 'en', 'fr'). Added in v5. */
37
+ language: string;
35
38
  /**
36
39
  * string that starts with "internationalizedArray" and ends with "Value"
37
40
  */
@@ -134,15 +137,50 @@ type PluginConfig = {
134
137
  * @defaultValue 'code'
135
138
  * */
136
139
  languageDisplay?: LanguageDisplay;
140
+ /**
141
+ * Configure the language filter for the plugin by providing the document types that should show the filter.
142
+ * ```tsx
143
+ * {
144
+ * languageFilter: {
145
+ * documentTypes: ['internationalizedPost', 'lesson']
146
+ * }
147
+ * }
148
+ * ```
149
+ */
150
+ languageFilter?: {
151
+ documentTypes: string[];
152
+ };
137
153
  };
138
154
  declare const clear: () => void;
139
- declare const internationalizedArray: sanity0.Plugin<PluginConfig>;
140
155
  /**
141
156
  * The field name used to identify the language of an internationalized array item.
142
157
  *
143
- * In v4.x this was '_key', in v5+ this will be 'language'.
144
- * Having this as a constant makes the codebase easier to maintain.
158
+ * In v4.x this was '_key', in v5+ this is 'language'.
159
+ * ```ts
160
+ * {
161
+ * "description": [
162
+ * {
163
+ * "_key": "kjjNvZHK8Y2QpTEf3K5jc",
164
+ * "_type": "internationalizedArrayTextValue",
165
+ * "language": "en"
166
+ * "value": "This is the description in English"
167
+ * },
168
+ * {
169
+ * "_key": "kjjNvZHK8Y2QpTEf3K5jc",
170
+ * "_type": "internationalizedArrayTextValue",
171
+ * "language": "es"
172
+ * "value": "This is the description in Spanish"
173
+ * },
174
+ * ]
175
+ * }
176
+ * ```
177
+ */
178
+ declare const LANGUAGE_FIELD_NAME: "language";
179
+ declare const internationalizedArray: sanity0.Plugin<PluginConfig>;
180
+ /**
181
+ * Default filter function for the internationalized array field.
182
+ * It filter the field base on the `language` value of the object.
145
183
  */
146
- declare const LANGUAGE_FIELD_NAME: "_key";
147
- export { AllowedType, ArrayConfig, InternationalizedArrayItem, LANGUAGE_FIELD_NAME, Language, LanguageCallback, LanguageDisplay, PluginConfig, Value, clear, internationalizedArray, isInternationalizedArrayItemType };
184
+ declare const internationalizedArrayLanguageFilter: FilterFieldFunction;
185
+ export { AllowedType, ArrayConfig, InternationalizedArrayItem, LANGUAGE_FIELD_NAME, Language, LanguageCallback, LanguageDisplay, PluginConfig, Value, clear, internationalizedArray, internationalizedArrayLanguageFilter, isInternationalizedArrayItemType };
148
186
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/cache.ts","../src/plugin.tsx","../src/constants.ts"],"mappings":";;KAEY,QAAA;EACV,EAAA,EAAI,IAAA,CAAK,4BAAA;EACT,KAAA;AAAA;AAAA,KAGU,WAAA;AAAA,KAEA,WAAA;EACV,IAAA;EACA,IAAA,EAAM,WAAA;EACN,SAAA,EAAW,QAAA;EACX,KAAA;EACA,KAAA;EACA,MAAA;EACA,QAAA;EACA,UAAA,GAAa,IAAA,GAAO,IAAA;EACpB,KAAA;IAAA,CAAU,GAAA;IAAuB,OAAA;MAAA,CAAW,GAAA;IAAA;EAAA;AAAA;;;;KAMlC,KAAA;EACV,IAAA;EACA,KAAA;AAAA;AAAA,iBAGc,gCAAA,CACd,IAAA,WACC,IAAA,IAAQ,0BAAA;AAAA,KAIC,0BAAA;EACV,IAAA;EACA,KAAA,GAAQ,CAAA;EAvBR;;;EA2BA,KAAA;AAAA;AAAA,KAGU,gBAAA,IACV,MAAA,EAAQ,YAAA,EACR,aAAA,EAAe,MAAA,sBACZ,OAAA,CAAQ,QAAA;AAAA,KAED,eAAA;AAAA,KAEA,YAAA;EAjCuB;;;;EAsCjC,UAAA;EAhCe;;;;AAKjB;;;;;;;;EAwCE,MAAA,GAAS,MAAA;EAlCC;;;;;;;;;;;AASZ;;;;;;;;;;;;;;;;EAqDE,SAAA,EAAW,QAAA,KAAa,gBAAA;EAhDd;;;;;AAEZ;;;EAuDE,gBAAA;EATW;;;;;;;;;;;;;;;;;;;;;;;EAkCX,UAAA,YAAsB,kBAAA,GAAqB,eAAA;EC3FhC;;;;EDgGX,eAAA;;AE3HF;;;EFgIE,YAAA;EEhIiC;;ACLnC;;EH0IE,eAAA,GAAkB,eAAA;AAAA;AAAA,cC1GP,KAAA;AAAA,cC3BA,sBAAA,EAAsB,OAAA,CAAA,MAAA,CAAA,YAAA;;;AFXnC;;;;cGMa,mBAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/cache.ts","../src/constants.ts","../src/plugin.tsx","../src/utils/internationalizedArrayLanguageFilter.ts"],"mappings":";;;KAEY,QAAA;EACV,EAAA,EAAI,IAAA,CAAK,4BAAA;EACT,KAAA;AAAA;AAAA,KAGU,WAAA;AAAA,KAEA,WAAA;EACV,IAAA;EACA,IAAA,EAAM,WAAA;EACN,SAAA,EAAW,QAAA;EACX,KAAA;EACA,KAAA;EACA,MAAA;EACA,QAAA;EACA,UAAA,GAAa,IAAA,GAAO,IAAA;EACpB,KAAA;IAAA,CAAU,GAAA;IAAuB,OAAA;MAAA,CAAW,GAAA;IAAA;EAAA;AAAA;;;;KAMlC,KAAA;EACV,IAAA,UARwB;EAUxB,QAAA;EACA,KAAA;AAAA;AAAA,iBAGc,gCAAA,CACd,IAAA,WACC,IAAA,IAAQ,0BAAA;AAAA,KAIC,0BAAA;EACV,IAAA;EACA,KAAA,GAAQ,CAAA,EAxBR;EA0BA,QAAA;EAxBA;;;EA4BA,KAAA;AAAA;AAAA,KAGU,gBAAA,IACV,MAAA,EAAQ,YAAA,EACR,aAAA,EAAe,MAAA,sBACZ,OAAA,CAAQ,QAAA;AAAA,KAED,eAAA;AAAA,KAEA,YAAA;EArC6C;AAMzD;;;EAoCE,UAAA;EAnCA;;;;;AAMF;;;;;;;EA0CE,MAAA,GAAS,MAAA;EAxC0B;AAIrC;;;;;;;;;;;;AAWA;;;;;;;;;;;;;;EAqDE,SAAA,EAAW,QAAA,KAAa,gBAAA;EAlDL;;AAErB;;;;;AAEA;EAuDE,gBAAA;;;;;;;;;;;;;;;;;;;;;;;;EAyBA,UAAA,YAAsB,kBAAA,GAAqB,eAAA;EA2BzC;;;;EAtBF,eAAA;EClGD;;;;EDuGC,YAAA;EExHW;;;;EF6HX,eAAA,GAAkB,eAAA;;AGvIpB;;;;;;ACPA;;;EJyJE,cAAA;IACE,aAAA;EAAA;AAAA;AAAA,cC1HS,KAAA;;;;ADtCb;;;;;;;;;;AAKA;;;;;AAEA;;;;;cEgBa,mBAAA;AAAA,cCVA,sBAAA,EAAsB,OAAA,CAAA,MAAA,CAAA,YAAA;;;;AHbnC;cIMa,oCAAA,EAAsC,mBAAA"}