@sanity/document-internationalization 2.0.0-studio-v3-plugin-v2.4 → 2.0.0-studio-v3-plugin-v2.6

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Simeon Griggs
3
+ Copyright (c) 2023 Sanity.io
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -15,6 +15,8 @@ declare type PluginConfig = {
15
15
  bulkPublish?: boolean
16
16
  }
17
17
 
18
- declare type SupportedLanguages = Language[] | ((client: SanityClient) => Promise<Language[]>)
18
+ declare type SupportedLanguages =
19
+ | Language[]
20
+ | ((client: SanityClient) => Promise<Language[]>)
19
21
 
20
22
  export {}
package/lib/index.esm.js CHANGED
@@ -1 +1,643 @@
1
- function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function t(t){for(var a=1;a<arguments.length;a++){var r=null!=arguments[a]?arguments[a]:{};a%2?e(Object(r),!0).forEach((function(e){n(t,e,r[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):e(Object(r)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}))}return t}function n(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var a=n.call(e,t||"default");if("object"!=typeof a)return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}import{jsx as a,jsxs as r,Fragment as i}from"react/jsx-runtime";import{defineType as o,defineField as l,useClient as s,useEditState as c,definePlugin as u}from"sanity";import{internationalizedArray as d}from"sanity-plugin-internationalized-array";import{useToast as p,Button as g,Flex as f,Spinner as m,Text as h,Box as y,Badge as v,useClickOutside as b,Card as T,Stack as _,Popover as O}from"@sanity/ui";import{TranslateIcon as j,SplitVerticalIcon as w,CheckmarkIcon as k,AddIcon as I,CogIcon as P,ChevronRightIcon as S}from"@sanity/icons";import x,{useCallback as C,useState as L}from"react";import{uuid as F}from"@sanity/uuid";import{usePaneRouter as A}from"sanity/desk";import{RouterContext as z}from"sanity/router";import{useListeningQuery as D}from"sanity-plugin-utils";const E="translation.metadata",M="2022-11-27";var N=e=>o({type:"document",name:E,title:"Translation metadata",icon:j,liveEdit:!0,fields:[l({name:"translations",type:"internationalizedArrayReference"}),l({name:"schemaTypes",description:"Used to filter the reference fields above so all translations share the same types.",type:"array",of:[{type:"string"}],options:{list:e},readOnly:e=>{let{value:t}=e;return Boolean(t)}})],preview:{select:{translations:"translations",documentSchemaTypes:"schemaTypes"},prepare(e){const{translations:t,documentSchemaTypes:n}=e,a=1===t.length?"1 Translation":"".concat(t.length," Translations"),r=t.length?t.map((e=>e._key.toUpperCase())).join(", "):"";return{title:a,subtitle:[r?"(".concat(r,")"):null,(null==n?void 0:n.length)?n.map((e=>e.toUpperCase())).join(", "):"No Schemas Defined"].filter(Boolean).join(" ")}}}});function V(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:(e,t)=>e===t;if(e===t)return!0;if(!e||!t)return!1;const a=e.length;if(t.length!==a)return!1;for(let r=0;r<a;r++)if(!n(e[r],t[r]))return!1;return!0}const $=[];const U=(e,t,n)=>function(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};for(const e of $)if(V(t,e.keys,e.equal)){if(n)return;if(Object.prototype.hasOwnProperty.call(e,"error"))throw e.error;if(Object.prototype.hasOwnProperty.call(e,"response"))return e.response;if(!n)throw e.promise}const r={keys:t,equal:a.equal,promise:e(...t).then((e=>r.response=e)).then((()=>{a.lifespan&&a.lifespan>0&&setTimeout((()=>{const e=$.indexOf(r);-1!==e&&$.splice(e,1)}),a.lifespan)})).catch((e=>r.error=e))};if($.push(r),!n)throw r.promise}(e,t,!1,n);function B(e,t){const n=x.useContext(z),{routerPanesState:a,groupIndex:r}=A();return x.useCallback((()=>{if(!n||!e||!t)return;const i=[...a];i.splice(r+1,0,[{id:e,params:{type:t}}]);const o=n.resolvePathFromState({panes:i});n.navigateUrl({path:o})}),[e,t,n,a,r])}function q(e,t,n){return{_key:e,value:{_type:"reference",_ref:t,_weak:!0,_strengthenOnPublish:{type:n}}}}function R(e){var n;const{apiVersion:i=M,index:o,language:l,languageField:c,schemaType:u,documentId:d,disabled:b,current:T,sourceId:_,sourceLanguageId:O,metadata:j,translation:P}=e,S=s({apiVersion:i}),x=p(),L=B(null==(n=null==P?void 0:P.value)?void 0:n._ref,u),A=C((()=>L()),[L]),z=C((async()=>{var e;const n=Boolean(null==j?void 0:j._id),a=S.transaction(),r=d.startsWith("drafts.")?[d,d.replace("drafts.","")]:[d,"drafts.".concat(d)],i=t(t({},await S.fetch("*[_id in $ids]|order(_updatedAt desc)[0]",{ids:r})),{},{_id:"drafts.".concat(F()),[c]:l.id});a.create(i);const s=null!=(e=null==j?void 0:j._id)?e:F(),p=q(l.id,i._id.replace("drafts.",""),u);if(n){const e="translations[".concat(o-1,"]"),t=S.patch(s).setIfMissing({translations:[]}).insert("before",e,[p]);a.patch(t)}else{const e=O?q(O,_,u):null;a.createIfNotExists({_id:s,_type:E,schemaTypes:[u],translations:[p,e].filter(Boolean)})}a.commit().then((()=>x.push({status:"success",title:"Created ".concat(l.title," translation"),description:n?"Updated Translations Metadata":"Created Translations Metadata"}))).catch((e=>(console.error(e),x.push({status:"error",title:"Error creating translation",description:e.message}))))}),[S,d,o,l,c,null==j?void 0:j._id,u,_,O,x]);return a(g,{onClick:P?A:z,mode:T?"default":"bleed",disabled:b||T,children:r(f,{gap:3,align:"center",children:[b?a(m,{}):a(h,{size:2,children:a(P?w:T?k:I,{})}),a(y,{flex:1,children:a(h,{children:l.title})}),a(v,{tone:b||T?"default":"primary",children:l.id})]})})}function W(e){const{id:t}=e,n=B(t,E);return a(g,{disabled:!t,mode:"ghost",text:"Manage Translations",icon:P,onClick:()=>n()})}function G(e){const{apiVersion:t=M,language:n,languageField:r,documentId:i,schemaType:o,source:l,disabled:c=!1}=e,u=s({apiVersion:t}),d=p(),f=x.useCallback((()=>{const e=l?l._id:"draft.".concat(i),t=u.transaction();l||t.createIfNotExists({_id:e,_type:o});const a=u.patch(e).set({[r]:n.id});t.patch(a),t.commit().then((()=>{d.push({title:"Set document language to ".concat(n.title),status:"success"})})).catch((e=>(console.error(e),d.push({title:"Failed to set document language to ".concat(n.title),status:"error"}))))}),[l,i,u,r,n,o,d]);return a(g,{mode:"ghost",text:n.title,icon:S,onClick:()=>f(),disabled:c,justify:"flex-start"})}function H(e){const{apiVersion:t=M,schemaType:n,documentId:o,languageField:l}=e,u=s({apiVersion:t}),d=Array.isArray(e.supportedLanguages)?e.supportedLanguages:U((async()=>"function"==typeof e.supportedLanguages?e.supportedLanguages(u):e.supportedLanguages),[]),[p,f]=L(!1),m=C((()=>f((e=>!e))),[]),[v,w]=L(null),[k,I]=L(null),P=C((()=>f(!1)),[]);b(P,[v,k]);const{data:S,loading:F,error:A}=function(e,t){const{data:n,loading:a,error:r}=D("*[_type == $translationSchema && $id in translations[].value._ref][0]",{params:{id:e,translationSchema:E}});return{data:n,loading:a,error:r}}(o),{draft:z,published:N}=c(o,n),V=z||N,$=null==V?void 0:V[l],B=d.some((e=>e.id===$)),q=x.useMemo((()=>{const e=d.every((e=>e.id&&e.title));return e||console.warn('Not all languages are valid. It should be an array of objects with an "id" and "title" property. Or a function that returns an array of objects with an "id" and "title" property.',d),e}),[d]),H=a(y,{overflow:"auto",children:A?a(T,{tone:"critical",padding:2,children:r(h,{children:["Error: ",A]})}):r(_,{padding:1,space:1,children:[d.length>0?r(i,{children:[d.map(((e,t)=>{var r;return!F&&$&&B?a(R,{index:t,language:e,languageField:l,schemaType:n,documentId:o,disabled:F||!q,current:e.id===$,metadata:S,sourceId:o,sourceLanguageId:$,translation:null==S?void 0:S.translations.find((t=>t._key===e.id))},e.id||e.title||"lang-".concat(t)):a(G,{languageField:l,source:V,documentId:o,schemaType:n,language:e,disabled:null!=(r=!q||(null==S?void 0:S.translations.filter((e=>{var t;return(null==(t=null==e?void 0:e.value)?void 0:t._ref)!==o})).some((t=>t._key===e.id))))&&r},e.id||e.title||"lang-".concat(t))})),F?null:r(i,{children:[q?null:a(T,{tone:"caution",padding:3,children:a(h,{size:1,children:"Not all language objects are valid. See the console."})}),$?null:a(T,{tone:"caution",padding:3,children:r(h,{size:1,children:["Choose a language to ",a("br",{}),"apply to ",a("strong",{children:"this Document"})]})}),$&&!B?a(T,{tone:"caution",padding:3,children:r(h,{size:1,children:["Change the current language value ",a("code",{children:$}),a("br",{}),"to one of the supported languages"]})}):null]})]}):null,a(W,{id:null==S?void 0:S._id})]})});return a(O,{constrainSize:!0,content:H,open:p,portal:!0,ref:I,children:a(g,{text:"Translations",mode:"bleed",disabled:!V,tone:!V||!F&&$&&B?void 0:"caution",icon:j,onClick:m,ref:w,selected:p})})}const J={supportedLanguages:[],schemaTypes:[],languageField:"language",bulkPublish:!1},K=u((e=>{const{supportedLanguages:n,schemaTypes:r,languageField:i,bulkPublish:o}=t(t({},J),e);return{name:"@sanity/document-internationalization",form:{components:{input:e=>o&&"root"===e.id&&e.schemaType.name===E?a(_,{space:5,children:e.renderDefault(e)}):e.renderDefault(e)}},document:{unstable_languageFilter:(e,t)=>{const{schemaType:o,documentId:l}=t;return r.includes(o)?[...e,()=>((e,t)=>a(H,{supportedLanguages:n,schemaType:e,documentId:null!=t?t:"",languageField:i}))(o,l)]:e},badges:(e,t)=>{let{schemaType:a}=t;return r.includes(a)?[e=>function(e,t,n){var a,r;const i=(null==e?void 0:e.draft)||(null==e?void 0:e.published),o=null==i?void 0:i[n];if(!o)return null;const l=Array.isArray(t)?t.find((e=>e.id===o)):null;return{label:null!=(a=null==l?void 0:l.id)?a:String(o),title:null!=(r=null==l?void 0:l.title)?r:void 0,color:"primary"}}(e,n,i),...e]:e}},schema:{types:[N(r)],templates:(e,t)=>{let{schema:a}=t;if(!Array.isArray(n))return e;const o=r.map((e=>{var t,n;return{id:"".concat(e,"-parameterized"),title:"".concat(null!=(n=null==(t=null==a?void 0:a.get(e))?void 0:t.title)?n:e,": with Language"),schemaType:e,parameters:[{name:"languageId",title:"Language ID",type:"string"}],value:e=>{let{languageId:t}=e;return{[i]:t}}}})),l=r.flatMap((e=>n.map((t=>{var n,r;return{id:"".concat(e,"-").concat(t.id),title:"".concat(t.title," ").concat(null!=(r=null==(n=null==a?void 0:a.get(e))?void 0:n.title)?r:e),schemaType:e,value:{[i]:t.id}}}))));return[...e,...o,...l]}},plugins:[d({languages:n,fieldTypes:[l({name:"reference",type:"reference",to:r.map((e=>({type:e}))),options:{collapsed:!1,filter:e=>{let{parent:t,document:n}=e;if(!t)return null;const a=(Array.isArray(t)?t:[t]).find((e=>e._key));return(null==a?void 0:a._key)?n.schemaTypes?{filter:"_type in $schemaTypes && ".concat(i," == $language"),params:{schemaTypes:n.schemaTypes,language:a._key}}:{filter:"".concat(i," == $language"),params:{language:a._key}}:null}}},{strict:!1})]})]}}));export{K as documentInternationalization};//# sourceMappingURL=index.esm.js.map
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { defineType, defineField, useClient, useEditState, definePlugin } from 'sanity';
3
+ import { internationalizedArray } from 'sanity-plugin-internationalized-array';
4
+ import { useToast, Button, Flex, Spinner, Text, Box, Badge, useClickOutside, Card, Stack, Popover } from '@sanity/ui';
5
+ import { TranslateIcon, SplitVerticalIcon, CheckmarkIcon, AddIcon, CogIcon, ChevronRightIcon } from '@sanity/icons';
6
+ import React, { useCallback, useState } from 'react';
7
+ import { uuid } from '@sanity/uuid';
8
+ import { usePaneRouter } from 'sanity/desk';
9
+ import { RouterContext } from 'sanity/router';
10
+ import { useListeningQuery } from 'sanity-plugin-utils';
11
+ const METADATA_SCHEMA_NAME = "translation.metadata";
12
+ const API_VERSION = "2022-11-27";
13
+ var metadata = schemaTypes => defineType({
14
+ type: "document",
15
+ name: METADATA_SCHEMA_NAME,
16
+ title: "Translation metadata",
17
+ icon: TranslateIcon,
18
+ liveEdit: true,
19
+ fields: [defineField({
20
+ name: "translations",
21
+ type: "internationalizedArrayReference"
22
+ }), defineField({
23
+ name: "schemaTypes",
24
+ description: "Used to filter the reference fields above so all translations share the same types.",
25
+ type: "array",
26
+ // For some reason TS dislikes this line because of the DocumentDefinition return type
27
+ // @ts-expect-error
28
+ of: [{
29
+ type: "string"
30
+ }],
31
+ options: {
32
+ list: schemaTypes
33
+ },
34
+ readOnly: _ref => {
35
+ let {
36
+ value
37
+ } = _ref;
38
+ return Boolean(value);
39
+ }
40
+ })],
41
+ preview: {
42
+ select: {
43
+ translations: "translations",
44
+ documentSchemaTypes: "schemaTypes"
45
+ },
46
+ prepare(selection) {
47
+ const {
48
+ translations,
49
+ documentSchemaTypes
50
+ } = selection;
51
+ const title = translations.length === 1 ? "1 Translation" : "".concat(translations.length, " Translations");
52
+ const languageKeys = translations.length ? translations.map(t => t._key.toUpperCase()).join(", ") : "";
53
+ const subtitle = [languageKeys ? "(".concat(languageKeys, ")") : null, (documentSchemaTypes == null ? void 0 : documentSchemaTypes.length) ? documentSchemaTypes.map(s => s.toUpperCase()).join(", ") : "No Schemas Defined"].filter(Boolean).join(" ");
54
+ return {
55
+ title,
56
+ subtitle
57
+ };
58
+ }
59
+ }
60
+ });
61
+ function shallowEqualArrays(arrA, arrB) {
62
+ let equal = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : (a, b) => a === b;
63
+ if (arrA === arrB) return true;
64
+ if (!arrA || !arrB) return false;
65
+ const len = arrA.length;
66
+ if (arrB.length !== len) return false;
67
+ for (let i = 0; i < len; i++) if (!equal(arrA[i], arrB[i])) return false;
68
+ return true;
69
+ }
70
+ const globalCache = [];
71
+ function query(fn, keys) {
72
+ let preload = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
73
+ let config = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
74
+ for (const entry of globalCache) {
75
+ // Find a match
76
+ if (shallowEqualArrays(keys, entry.keys, entry.equal)) {
77
+ // If we're pre-loading and the element is present, just return
78
+ if (preload) return undefined; // If an error occurred, throw
79
+
80
+ if (Object.prototype.hasOwnProperty.call(entry, 'error')) throw entry.error; // If a response was successful, return
81
+
82
+ if (Object.prototype.hasOwnProperty.call(entry, 'response')) return entry.response; // If the promise is still unresolved, throw
83
+
84
+ if (!preload) throw entry.promise;
85
+ }
86
+ } // The request is new or has changed.
87
+
88
+ const entry = {
89
+ keys,
90
+ equal: config.equal,
91
+ promise:
92
+ // Execute the promise
93
+ fn(...keys) // When it resolves, store its value
94
+ .then(response => entry.response = response) // Remove the entry if a lifespan was given
95
+ .then(() => {
96
+ if (config.lifespan && config.lifespan > 0) {
97
+ setTimeout(() => {
98
+ const index = globalCache.indexOf(entry);
99
+ if (index !== -1) globalCache.splice(index, 1);
100
+ }, config.lifespan);
101
+ }
102
+ }) // Store caught errors, they will be thrown in the render-phase to bubble into an error-bound
103
+ .catch(error => entry.error = error)
104
+ }; // Register the entry
105
+
106
+ globalCache.push(entry); // And throw the promise, this yields control back to React
107
+
108
+ if (!preload) throw entry.promise;
109
+ return undefined;
110
+ }
111
+ const suspend = (fn, keys, config) => query(fn, keys, false, config);
112
+ function useOpenInNewPane(id, type) {
113
+ const routerContext = React.useContext(RouterContext);
114
+ const {
115
+ routerPanesState,
116
+ groupIndex
117
+ } = usePaneRouter();
118
+ const openInNewPane = React.useCallback(() => {
119
+ if (!routerContext || !id || !type) {
120
+ return;
121
+ }
122
+ const panes = [...routerPanesState];
123
+ panes.splice(groupIndex + 1, 0, [{
124
+ id,
125
+ params: {
126
+ type
127
+ }
128
+ }]);
129
+ const href = routerContext.resolvePathFromState({
130
+ panes
131
+ });
132
+ routerContext.navigateUrl({
133
+ path: href
134
+ });
135
+ }, [id, type, routerContext, routerPanesState, groupIndex]);
136
+ return openInNewPane;
137
+ }
138
+ function createReference(key, ref, type) {
139
+ return {
140
+ _key: key,
141
+ value: {
142
+ _type: "reference",
143
+ _ref: ref,
144
+ _weak: true,
145
+ _strengthenOnPublish: {
146
+ type
147
+ }
148
+ }
149
+ };
150
+ }
151
+ function LanguageOption(props) {
152
+ var _a;
153
+ const {
154
+ apiVersion = API_VERSION,
155
+ index,
156
+ language,
157
+ languageField,
158
+ schemaType,
159
+ documentId,
160
+ disabled,
161
+ current,
162
+ sourceId,
163
+ sourceLanguageId,
164
+ metadata,
165
+ translation
166
+ } = props;
167
+ const client = useClient({
168
+ apiVersion
169
+ });
170
+ const toast = useToast();
171
+ const open = useOpenInNewPane((_a = translation == null ? void 0 : translation.value) == null ? void 0 : _a._ref, schemaType);
172
+ const handleOpen = useCallback(() => open(), [open]);
173
+ const handleCreate = useCallback(async () => {
174
+ var _a2;
175
+ const metadataExists = Boolean(metadata == null ? void 0 : metadata._id);
176
+ const transaction = client.transaction();
177
+ const documentIds = documentId.startsWith("drafts.") ? [documentId, documentId.replace("drafts.", "")] : [documentId, "drafts.".concat(documentId)];
178
+ const latestDocument = await client.fetch("*[_id in $ids]|order(_updatedAt desc)[0]", {
179
+ ids: documentIds
180
+ });
181
+ const newTranslationDocument = {
182
+ ...latestDocument,
183
+ _id: "drafts.".concat(uuid()),
184
+ [languageField]: language.id
185
+ };
186
+ transaction.create(newTranslationDocument);
187
+ const metadataId = (_a2 = metadata == null ? void 0 : metadata._id) != null ? _a2 : uuid();
188
+ const newTranslationReference = createReference(language.id, newTranslationDocument._id.replace("drafts.", ""), schemaType);
189
+ if (metadataExists) {
190
+ const path = "translations[".concat(index - 1, "]");
191
+ const metadataPatch = client.patch(metadataId).setIfMissing({
192
+ translations: []
193
+ }).insert("before", path, [newTranslationReference]);
194
+ transaction.patch(metadataPatch);
195
+ } else {
196
+ const sourceReference = sourceLanguageId ? createReference(sourceLanguageId, sourceId, schemaType) : null;
197
+ transaction.createIfNotExists({
198
+ _id: metadataId,
199
+ _type: METADATA_SCHEMA_NAME,
200
+ schemaTypes: [schemaType],
201
+ translations: [newTranslationReference, sourceReference].filter(Boolean)
202
+ });
203
+ }
204
+ transaction.commit().then(() => {
205
+ return toast.push({
206
+ status: "success",
207
+ title: "Created ".concat(language.title, " translation"),
208
+ description: metadataExists ? "Updated Translations Metadata" : "Created Translations Metadata"
209
+ });
210
+ }).catch(err => {
211
+ console.error(err);
212
+ return toast.push({
213
+ status: "error",
214
+ title: "Error creating translation",
215
+ description: err.message
216
+ });
217
+ });
218
+ }, [client, documentId, index, language, languageField, metadata == null ? void 0 : metadata._id, schemaType, sourceId, sourceLanguageId, toast]);
219
+ return /* @__PURE__ */jsx(Button, {
220
+ onClick: translation ? handleOpen : handleCreate,
221
+ mode: current ? "default" : "bleed",
222
+ disabled: disabled || current,
223
+ children: /* @__PURE__ */jsxs(Flex, {
224
+ gap: 3,
225
+ align: "center",
226
+ children: [disabled ? /* @__PURE__ */jsx(Spinner, {}) : /* @__PURE__ */jsx(Text, {
227
+ size: 2,
228
+ children: translation ? /* @__PURE__ */jsx(SplitVerticalIcon, {}) : current ? /* @__PURE__ */jsx(CheckmarkIcon, {}) : /* @__PURE__ */jsx(AddIcon, {})
229
+ }), /* @__PURE__ */jsx(Box, {
230
+ flex: 1,
231
+ children: /* @__PURE__ */jsx(Text, {
232
+ children: language.title
233
+ })
234
+ }), /* @__PURE__ */jsx(Badge, {
235
+ tone: disabled || current ? "default" : "primary",
236
+ children: language.id
237
+ })]
238
+ })
239
+ });
240
+ }
241
+ function useTranslationMetadata(id, schemaType) {
242
+ const query = "*[_type == $translationSchema && $id in translations[].value._ref][0]";
243
+ const {
244
+ data,
245
+ loading,
246
+ error
247
+ } = useListeningQuery(query, {
248
+ params: {
249
+ id,
250
+ translationSchema: METADATA_SCHEMA_NAME
251
+ }
252
+ });
253
+ return {
254
+ data,
255
+ loading,
256
+ error
257
+ };
258
+ }
259
+ function LanguageManage(props) {
260
+ const {
261
+ id
262
+ } = props;
263
+ const open = useOpenInNewPane(id, METADATA_SCHEMA_NAME);
264
+ return /* @__PURE__ */jsx(Button, {
265
+ disabled: !id,
266
+ mode: "ghost",
267
+ text: "Manage Translations",
268
+ icon: CogIcon,
269
+ onClick: () => open()
270
+ });
271
+ }
272
+ function LanguagePatch(props) {
273
+ const {
274
+ apiVersion = API_VERSION,
275
+ language,
276
+ languageField,
277
+ documentId,
278
+ schemaType,
279
+ source,
280
+ disabled = false
281
+ } = props;
282
+ const client = useClient({
283
+ apiVersion
284
+ });
285
+ const toast = useToast();
286
+ const handleClick = React.useCallback(() => {
287
+ const currentId = source ? source._id : "draft.".concat(documentId);
288
+ const transaction = client.transaction();
289
+ if (!source) {
290
+ transaction.createIfNotExists({
291
+ _id: currentId,
292
+ _type: schemaType
293
+ });
294
+ }
295
+ const patch = client.patch(currentId).set({
296
+ [languageField]: language.id
297
+ });
298
+ transaction.patch(patch);
299
+ transaction.commit().then(() => {
300
+ toast.push({
301
+ title: "Set document language to ".concat(language.title),
302
+ status: "success"
303
+ });
304
+ }).catch(err => {
305
+ console.error(err);
306
+ return toast.push({
307
+ title: "Failed to set document language to ".concat(language.title),
308
+ status: "error"
309
+ });
310
+ });
311
+ }, [source, documentId, client, languageField, language, schemaType, toast]);
312
+ return /* @__PURE__ */jsx(Button, {
313
+ mode: "ghost",
314
+ text: language.title,
315
+ icon: ChevronRightIcon,
316
+ onClick: () => handleClick(),
317
+ disabled,
318
+ justify: "flex-start"
319
+ });
320
+ }
321
+ function MenuButton(props) {
322
+ const {
323
+ apiVersion = API_VERSION,
324
+ schemaType,
325
+ documentId,
326
+ languageField
327
+ } = props;
328
+ const client = useClient({
329
+ apiVersion
330
+ });
331
+ const supportedLanguages = Array.isArray(props.supportedLanguages) ? props.supportedLanguages :
332
+ // eslint-disable-next-line require-await
333
+ suspend(async () => {
334
+ if (typeof props.supportedLanguages === "function") {
335
+ return props.supportedLanguages(client);
336
+ }
337
+ return props.supportedLanguages;
338
+ }, []);
339
+ const [open, setOpen] = useState(false);
340
+ const handleClick = useCallback(() => setOpen(o => !o), []);
341
+ const [button, setButton] = useState(null);
342
+ const [popover, setPopover] = useState(null);
343
+ const handleClickOutside = useCallback(() => setOpen(false), []);
344
+ useClickOutside(handleClickOutside, [button, popover]);
345
+ const {
346
+ data: metadata,
347
+ loading,
348
+ error
349
+ } = useTranslationMetadata(documentId);
350
+ const {
351
+ draft,
352
+ published
353
+ } = useEditState(documentId, schemaType);
354
+ const source = draft || published;
355
+ const sourceLanguageId = source == null ? void 0 : source[languageField];
356
+ const sourceLanguageIsValid = supportedLanguages.some(l => l.id === sourceLanguageId);
357
+ const allLanguagesAreValid = React.useMemo(() => {
358
+ const valid = supportedLanguages.every(l => l.id && l.title);
359
+ if (!valid) {
360
+ console.warn("Not all languages are valid. It should be an array of objects with an \"id\" and \"title\" property. Or a function that returns an array of objects with an \"id\" and \"title\" property.", supportedLanguages);
361
+ }
362
+ return valid;
363
+ }, [supportedLanguages]);
364
+ const content = /* @__PURE__ */jsx(Box, {
365
+ children: error ? /* @__PURE__ */jsx(Card, {
366
+ tone: "critical",
367
+ padding: 2,
368
+ children: /* @__PURE__ */jsxs(Text, {
369
+ children: ["Error: ", error]
370
+ })
371
+ }) : /* @__PURE__ */jsxs(Stack, {
372
+ padding: 1,
373
+ space: 1,
374
+ children: [supportedLanguages.length > 0 ? /* @__PURE__ */jsxs(Fragment, {
375
+ children: [supportedLanguages.map((language, langIndex) => {
376
+ var _a;
377
+ return !loading && sourceLanguageId && sourceLanguageIsValid ?
378
+ // Button to duplicate this document to a new translation
379
+ // And either create or update the metadata document
380
+ /* @__PURE__ */
381
+ jsx(LanguageOption, {
382
+ index: langIndex,
383
+ language,
384
+ languageField,
385
+ schemaType,
386
+ documentId,
387
+ disabled: loading || !allLanguagesAreValid,
388
+ current: language.id === sourceLanguageId,
389
+ metadata,
390
+ sourceId: documentId,
391
+ sourceLanguageId,
392
+ translation: metadata == null ? void 0 : metadata.translations.find(t => t._key === language.id)
393
+ }, language.id || language.title || "lang-".concat(langIndex)) :
394
+ // Button to set a language field on *this* document
395
+ /* @__PURE__ */
396
+ jsx(LanguagePatch, {
397
+ languageField,
398
+ source,
399
+ documentId,
400
+ schemaType,
401
+ language,
402
+ disabled: (_a = !allLanguagesAreValid || (metadata == null ? void 0 : metadata.translations.filter(t => {
403
+ var _a2;
404
+ return ((_a2 = t == null ? void 0 : t.value) == null ? void 0 : _a2._ref) !== documentId;
405
+ }).some(t => t._key === language.id))) != null ? _a : false
406
+ }, language.id || language.title || "lang-".concat(langIndex));
407
+ }), loading ? null : /* @__PURE__ */jsxs(Fragment, {
408
+ children: [allLanguagesAreValid ? null : /* @__PURE__ */jsx(Card, {
409
+ tone: "caution",
410
+ padding: 3,
411
+ children: /* @__PURE__ */jsx(Text, {
412
+ size: 1,
413
+ children: "Not all language objects are valid. See the console."
414
+ })
415
+ }), sourceLanguageId ? null : /* @__PURE__ */jsx(Card, {
416
+ tone: "caution",
417
+ padding: 3,
418
+ children: /* @__PURE__ */jsxs(Text, {
419
+ size: 1,
420
+ children: ["Choose a language to ", /* @__PURE__ */jsx("br", {}), "apply to ", /* @__PURE__ */jsx("strong", {
421
+ children: "this Document"
422
+ })]
423
+ })
424
+ }), sourceLanguageId && !sourceLanguageIsValid ? /* @__PURE__ */jsx(Card, {
425
+ tone: "caution",
426
+ padding: 3,
427
+ children: /* @__PURE__ */jsxs(Text, {
428
+ size: 1,
429
+ children: ["Change the current language value ", /* @__PURE__ */jsx("code", {
430
+ children: sourceLanguageId
431
+ }), /* @__PURE__ */jsx("br", {}), "to one of the supported languages"]
432
+ })
433
+ }) : null]
434
+ })]
435
+ }) : null, /* @__PURE__ */jsx(LanguageManage, {
436
+ id: metadata == null ? void 0 : metadata._id
437
+ })]
438
+ })
439
+ });
440
+ return /* @__PURE__ */jsx(Popover, {
441
+ constrainSize: true,
442
+ content,
443
+ open,
444
+ portal: true,
445
+ ref: setPopover,
446
+ overflow: "auto",
447
+ children: /* @__PURE__ */jsx(Button, {
448
+ text: "Translations",
449
+ mode: "bleed",
450
+ disabled: !source,
451
+ tone: !source || !loading && sourceLanguageId && sourceLanguageIsValid ? void 0 : "caution",
452
+ icon: TranslateIcon,
453
+ onClick: handleClick,
454
+ ref: setButton,
455
+ selected: open
456
+ })
457
+ });
458
+ }
459
+ function LanguageBadge(props, supportedLanguages, languageField) {
460
+ var _a, _b;
461
+ const source = (props == null ? void 0 : props.draft) || (props == null ? void 0 : props.published);
462
+ const languageId = source == null ? void 0 : source[languageField];
463
+ if (!languageId) {
464
+ return null;
465
+ }
466
+ const language = Array.isArray(supportedLanguages) ? supportedLanguages.find(l => l.id === languageId) : null;
467
+ return {
468
+ label: (_a = language == null ? void 0 : language.id) != null ? _a : String(languageId),
469
+ title: (_b = language == null ? void 0 : language.title) != null ? _b : void 0,
470
+ color: "primary"
471
+ };
472
+ }
473
+ const DEFAULT_CONFIG = {
474
+ supportedLanguages: [],
475
+ schemaTypes: [],
476
+ languageField: "language",
477
+ bulkPublish: false
478
+ };
479
+ const documentInternationalization = definePlugin(config => {
480
+ const {
481
+ supportedLanguages,
482
+ schemaTypes,
483
+ languageField,
484
+ bulkPublish
485
+ } = {
486
+ ...DEFAULT_CONFIG,
487
+ ...config
488
+ };
489
+ const renderLanguageFilter = (schemaType, documentId) => {
490
+ return /* @__PURE__ */jsx(MenuButton, {
491
+ supportedLanguages,
492
+ schemaType,
493
+ documentId: documentId != null ? documentId : "",
494
+ languageField
495
+ });
496
+ };
497
+ return {
498
+ name: "@sanity/document-internationalization",
499
+ // Adds:
500
+ // - A bulk-publishing UI component to the form
501
+ // - Will only work for projects on a compatible plan
502
+ form: {
503
+ components: {
504
+ input: props => {
505
+ if (bulkPublish && props.id === "root" && props.schemaType.name === METADATA_SCHEMA_NAME) {
506
+ return /* @__PURE__ */jsx(Stack, {
507
+ space: 5,
508
+ children: props.renderDefault(props)
509
+ });
510
+ }
511
+ return props.renderDefault(props);
512
+ }
513
+ }
514
+ },
515
+ // Adds:
516
+ // - The `Translations` dropdown to the editing form
517
+ // - `Badges` to documents with a language value
518
+ document: {
519
+ unstable_languageFilter: (prev, ctx) => {
520
+ const {
521
+ schemaType,
522
+ documentId
523
+ } = ctx;
524
+ return schemaTypes.includes(schemaType) ? [...prev, () => renderLanguageFilter(schemaType, documentId)] : prev;
525
+ },
526
+ badges: (prev, _ref2) => {
527
+ let {
528
+ schemaType
529
+ } = _ref2;
530
+ if (!schemaTypes.includes(schemaType)) {
531
+ return prev;
532
+ }
533
+ return [props => LanguageBadge(props, supportedLanguages, languageField), ...prev];
534
+ }
535
+ },
536
+ // Adds:
537
+ // - The `Translations metadata` document type to the schema
538
+ schema: {
539
+ // Create the metadata document type
540
+ types: [metadata(schemaTypes)],
541
+ // For every schema type this plugin is enabled on
542
+ // Create an initial value template to set the language
543
+ templates: (prev, _ref3) => {
544
+ let {
545
+ schema
546
+ } = _ref3;
547
+ if (!Array.isArray(supportedLanguages)) {
548
+ return prev;
549
+ }
550
+ const parameterizedTemplates = schemaTypes.map(schemaType => {
551
+ var _a, _b;
552
+ return {
553
+ id: "".concat(schemaType, "-parameterized"),
554
+ title: "".concat((_b = (_a = schema == null ? void 0 : schema.get(schemaType)) == null ? void 0 : _a.title) != null ? _b : schemaType, ": with Language"),
555
+ schemaType,
556
+ parameters: [{
557
+ name: "languageId",
558
+ title: "Language ID",
559
+ type: "string"
560
+ }],
561
+ value: _ref4 => {
562
+ let {
563
+ languageId
564
+ } = _ref4;
565
+ return {
566
+ [languageField]: languageId
567
+ };
568
+ }
569
+ };
570
+ });
571
+ const staticTemplates = schemaTypes.flatMap(schemaType => {
572
+ return supportedLanguages.map(language => {
573
+ var _a, _b;
574
+ return {
575
+ id: "".concat(schemaType, "-").concat(language.id),
576
+ title: "".concat(language.title, " ").concat((_b = (_a = schema == null ? void 0 : schema.get(schemaType)) == null ? void 0 : _a.title) != null ? _b : schemaType),
577
+ schemaType,
578
+ value: {
579
+ [languageField]: language.id
580
+ }
581
+ };
582
+ });
583
+ });
584
+ return [...prev, ...parameterizedTemplates, ...staticTemplates];
585
+ }
586
+ },
587
+ // Uses:
588
+ // - `sanity-plugin-internationalized-array` to maintain the translations array
589
+ plugins: [
590
+ // Translation metadata stores its references using this plugin
591
+ // It cuts down on attribute usage and gives UI conveniences to add new translations
592
+ internationalizedArray({
593
+ languages: supportedLanguages,
594
+ fieldTypes: [
595
+ // TODO: The plugin should allow this kind of input
596
+ // @ts-ignore
597
+ defineField({
598
+ name: "reference",
599
+ type: "reference",
600
+ to: schemaTypes.map(type => ({
601
+ type
602
+ })),
603
+ // TODO: Add a validation rule to *ensure* the document's language matches the array key
604
+ // Reference filters don't actually enforce validation!
605
+ // validation: (Rule) => Rule.custom(),
606
+ options: {
607
+ collapsed: false,
608
+ // TODO: Update type once it knows the values of this filter
609
+ // @ts-ignore
610
+ filter: _ref5 => {
611
+ let {
612
+ parent,
613
+ document
614
+ } = _ref5;
615
+ if (!parent) return null;
616
+ const parentArray = Array.isArray(parent) ? parent : [parent];
617
+ const language = parentArray.find(p => p._key);
618
+ if (!(language == null ? void 0 : language._key)) return null;
619
+ if (document.schemaTypes) {
620
+ return {
621
+ filter: "_type in $schemaTypes && ".concat(languageField, " == $language"),
622
+ params: {
623
+ schemaTypes: document.schemaTypes,
624
+ language: language._key
625
+ }
626
+ };
627
+ }
628
+ return {
629
+ filter: "".concat(languageField, " == $language"),
630
+ params: {
631
+ language: language._key
632
+ }
633
+ };
634
+ }
635
+ }
636
+ }, {
637
+ strict: false
638
+ })]
639
+ })]
640
+ };
641
+ });
642
+ export { documentInternationalization };
643
+ //# sourceMappingURL=index.esm.js.map