@roxxel/payload-multilang 0.0.4 → 0.0.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.md +7 -0
- package/README.md +50 -14
- package/dist/components/LanguageListToolbar.js +4 -3
- package/dist/components/LanguageListToolbar.js.map +1 -1
- package/dist/components/LanguageMetabox.js +18 -17
- package/dist/components/LanguageMetabox.js.map +1 -1
- package/dist/components/TranslationActionsClient.js +16 -14
- package/dist/components/TranslationActionsClient.js.map +1 -1
- package/dist/components/TranslationColumnCell.js +4 -1
- package/dist/components/TranslationColumnCell.js.map +1 -1
- package/dist/components/TranslationColumnCellClient.js +16 -7
- package/dist/components/TranslationColumnCellClient.js.map +1 -1
- package/dist/components/TranslationsTab.js +9 -8
- package/dist/components/TranslationsTab.js.map +1 -1
- package/dist/constants.d.ts +4 -1
- package/dist/constants.js +4 -1
- package/dist/constants.js.map +1 -1
- package/dist/endpoints/translations.js +4 -6
- package/dist/endpoints/translations.js.map +1 -1
- package/dist/hooks/translatedCollection.d.ts +2 -1
- package/dist/hooks/translatedCollection.js +122 -15
- package/dist/hooks/translatedCollection.js.map +1 -1
- package/dist/hooks/translatedGlobal.js +5 -1
- package/dist/hooks/translatedGlobal.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +27 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/config.js +2 -10
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/data.d.ts +72 -35
- package/dist/lib/data.js +118 -60
- package/dist/lib/data.js.map +1 -1
- package/dist/translations.d.ts +72 -0
- package/dist/translations.js +72 -0
- package/dist/translations.js.map +1 -0
- package/dist/types.d.ts +0 -19
- package/dist/types.js.map +1 -1
- package/docs/configuration.md +75 -10
- package/docs/helpers.md +95 -121
- package/docs/usage.md +115 -120
- package/package.json +1 -1
- package/dist/payload-config.d.js +0 -2
- package/dist/payload-config.d.js.map +0 -1
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Roxxel
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -23,9 +23,7 @@ export default buildConfig({
|
|
|
23
23
|
plugins: [
|
|
24
24
|
payloadMultilang({
|
|
25
25
|
collections: {
|
|
26
|
-
posts:
|
|
27
|
-
synchronizedFields: ['featuredImage', 'author'],
|
|
28
|
-
},
|
|
26
|
+
posts: true,
|
|
29
27
|
},
|
|
30
28
|
globals: {
|
|
31
29
|
'site-settings': {
|
|
@@ -54,6 +52,21 @@ export default buildConfig({
|
|
|
54
52
|
})
|
|
55
53
|
```
|
|
56
54
|
|
|
55
|
+
Mark shared fields in the collection field config:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
{
|
|
59
|
+
name: 'featuredImage',
|
|
60
|
+
type: 'upload',
|
|
61
|
+
relationTo: 'media',
|
|
62
|
+
custom: {
|
|
63
|
+
multilang: {
|
|
64
|
+
synchronize: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
57
70
|
In the Payload admin panel, enabled collections get:
|
|
58
71
|
|
|
59
72
|
- a language selector in the document sidebar
|
|
@@ -97,13 +110,14 @@ const posts = await payload.find({
|
|
|
97
110
|
|
|
98
111
|
## Translation Helpers
|
|
99
112
|
|
|
113
|
+
In request-scoped code, use the `WithPayload` helpers with `req.payload`.
|
|
114
|
+
|
|
100
115
|
```ts
|
|
101
116
|
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
updateGlobalByLanguage,
|
|
117
|
+
findGlobalByLanguageWithPayload,
|
|
118
|
+
getDocumentTranslationWithPayload,
|
|
119
|
+
getDocumentTranslationsWithPayload,
|
|
120
|
+
updateGlobalByLanguageWithPayload,
|
|
107
121
|
withLanguage,
|
|
108
122
|
} from '@roxxel/payload-multilang'
|
|
109
123
|
```
|
|
@@ -111,33 +125,55 @@ import {
|
|
|
111
125
|
Common examples:
|
|
112
126
|
|
|
113
127
|
```ts
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
const state = await getDocumentTranslations({
|
|
128
|
+
const state = await getDocumentTranslationsWithPayload({
|
|
117
129
|
collection: 'posts',
|
|
118
130
|
id: post.id,
|
|
131
|
+
payload: req.payload,
|
|
132
|
+
req,
|
|
133
|
+
overrideAccess: false,
|
|
119
134
|
})
|
|
120
135
|
|
|
121
|
-
const ukrainianPost = await
|
|
136
|
+
const ukrainianPost = await getDocumentTranslationWithPayload({
|
|
122
137
|
collection: 'posts',
|
|
123
138
|
id: post.id,
|
|
124
139
|
language: 'uk',
|
|
140
|
+
payload: req.payload,
|
|
141
|
+
req,
|
|
142
|
+
overrideAccess: false,
|
|
125
143
|
})
|
|
126
144
|
|
|
127
|
-
const settings = await
|
|
145
|
+
const settings = await findGlobalByLanguageWithPayload({
|
|
128
146
|
slug: 'site-settings',
|
|
129
147
|
language: 'uk',
|
|
148
|
+
payload: req.payload,
|
|
149
|
+
req,
|
|
150
|
+
overrideAccess: false,
|
|
130
151
|
})
|
|
131
152
|
|
|
132
|
-
await
|
|
153
|
+
await updateGlobalByLanguageWithPayload({
|
|
133
154
|
slug: 'site-settings',
|
|
134
155
|
language: 'uk',
|
|
156
|
+
payload: req.payload,
|
|
157
|
+
req,
|
|
158
|
+
overrideAccess: false,
|
|
135
159
|
data: {
|
|
136
160
|
title: 'Localized title',
|
|
137
161
|
},
|
|
138
162
|
})
|
|
139
163
|
```
|
|
140
164
|
|
|
165
|
+
For app-level convenience, create your own configured helper module:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { createMultilangHelpers } from '@roxxel/payload-multilang'
|
|
169
|
+
import { getPayload } from 'payload'
|
|
170
|
+
import config from '@payload-config'
|
|
171
|
+
|
|
172
|
+
export const multilang = createMultilangHelpers({
|
|
173
|
+
getPayload: () => getPayload({ config }),
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
141
177
|
## Documentation
|
|
142
178
|
|
|
143
179
|
- [Configuration](./docs/configuration.md)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useConfig, useRouteTransition } from '@payloadcms/ui';
|
|
3
|
+
import { useConfig, useRouteTransition, useTranslation } from '@payloadcms/ui';
|
|
4
4
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation.js';
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
import '../styles/admin.css';
|
|
@@ -11,6 +11,7 @@ export const LanguageListToolbar = ({ collectionSlug })=>{
|
|
|
11
11
|
const router = useRouter();
|
|
12
12
|
const searchParams = useSearchParams();
|
|
13
13
|
const { isTransitioning, startRouteTransition } = useRouteTransition();
|
|
14
|
+
const { t } = useTranslation();
|
|
14
15
|
const languages = getConfiguredLanguagesFromConfig(config);
|
|
15
16
|
const fieldNames = useMemo(()=>getCollectionFieldNames({
|
|
16
17
|
collectionSlug,
|
|
@@ -44,14 +45,14 @@ export const LanguageListToolbar = ({ collectionSlug })=>{
|
|
|
44
45
|
children: /*#__PURE__*/ _jsx("label", {
|
|
45
46
|
className: "payload-multilang-toolbar__select",
|
|
46
47
|
children: /*#__PURE__*/ _jsxs("select", {
|
|
47
|
-
"aria-label":
|
|
48
|
+
"aria-label": t('payloadMultilang:language'),
|
|
48
49
|
disabled: isTransitioning,
|
|
49
50
|
onChange: (event)=>handleLanguageChange(event.target.value),
|
|
50
51
|
value: currentLanguage,
|
|
51
52
|
children: [
|
|
52
53
|
/*#__PURE__*/ _jsx("option", {
|
|
53
54
|
value: "",
|
|
54
|
-
children:
|
|
55
|
+
children: t('payloadMultilang:allLanguages')
|
|
55
56
|
}),
|
|
56
57
|
languages.map((language)=>/*#__PURE__*/ _jsxs("option", {
|
|
57
58
|
value: language.code,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/LanguageListToolbar.tsx"],"sourcesContent":["'use client'\n\nimport { useConfig, useRouteTransition } from '@payloadcms/ui'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation.js'\nimport { useMemo } from 'react'\n\nimport '../styles/admin.css'\nimport { getCollectionFieldNames, getConfiguredLanguagesFromConfig } from './config.js'\n\ntype Props = {\n collectionSlug: string\n}\n\nexport const LanguageListToolbar = ({ collectionSlug }: Props) => {\n const { config } = useConfig()\n const pathname = usePathname()\n const router = useRouter()\n const searchParams = useSearchParams()\n const { isTransitioning, startRouteTransition } = useRouteTransition()\n const languages = getConfiguredLanguagesFromConfig(config)\n const fieldNames = useMemo(\n () => getCollectionFieldNames({ collectionSlug, config }),\n [collectionSlug, config],\n )\n const languageParam = `where[${fieldNames.language}][equals]`\n const currentLanguage = searchParams.get(languageParam) || ''\n\n if (!languages.length) {\n return null\n }\n\n const handleLanguageChange = (language: string) => {\n const nextParams = new URLSearchParams(searchParams.toString())\n\n if (language) {\n nextParams.set(languageParam, language)\n } else {\n nextParams.delete(languageParam)\n }\n\n nextParams.set('page', '1')\n\n const query = nextParams.toString()\n const href = query ? `${pathname}?${query}` : pathname\n\n startRouteTransition(() => {\n router.push(href)\n })\n }\n\n return (\n <div className=\"payload-multilang-toolbar\" data-testid=\"multilang-list-toolbar\">\n <label className=\"payload-multilang-toolbar__select\">\n <select\n aria-label
|
|
1
|
+
{"version":3,"sources":["../../src/components/LanguageListToolbar.tsx"],"sourcesContent":["'use client'\n\nimport { useConfig, useRouteTransition, useTranslation } from '@payloadcms/ui'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation.js'\nimport { useMemo } from 'react'\n\nimport '../styles/admin.css'\n\nimport type {\n PayloadMultilangTranslationKey,\n PayloadMultilangTranslations,\n} from '../translations.js'\n\nimport { getCollectionFieldNames, getConfiguredLanguagesFromConfig } from './config.js'\n\ntype Props = {\n collectionSlug: string\n}\n\nexport const LanguageListToolbar = ({ collectionSlug }: Props) => {\n const { config } = useConfig()\n const pathname = usePathname()\n const router = useRouter()\n const searchParams = useSearchParams()\n const { isTransitioning, startRouteTransition } = useRouteTransition()\n const { t } = useTranslation<\n PayloadMultilangTranslations,\n PayloadMultilangTranslationKey\n >()\n const languages = getConfiguredLanguagesFromConfig(config)\n const fieldNames = useMemo(\n () => getCollectionFieldNames({ collectionSlug, config }),\n [collectionSlug, config],\n )\n const languageParam = `where[${fieldNames.language}][equals]`\n const currentLanguage = searchParams.get(languageParam) || ''\n\n if (!languages.length) {\n return null\n }\n\n const handleLanguageChange = (language: string) => {\n const nextParams = new URLSearchParams(searchParams.toString())\n\n if (language) {\n nextParams.set(languageParam, language)\n } else {\n nextParams.delete(languageParam)\n }\n\n nextParams.set('page', '1')\n\n const query = nextParams.toString()\n const href = query ? `${pathname}?${query}` : pathname\n\n startRouteTransition(() => {\n router.push(href)\n })\n }\n\n return (\n <div className=\"payload-multilang-toolbar\" data-testid=\"multilang-list-toolbar\">\n <label className=\"payload-multilang-toolbar__select\">\n <select\n aria-label={t('payloadMultilang:language')}\n disabled={isTransitioning}\n onChange={(event) => handleLanguageChange(event.target.value)}\n value={currentLanguage}\n >\n <option value=\"\">{t('payloadMultilang:allLanguages')}</option>\n {languages.map((language) => (\n <option key={language.code} value={language.code}>\n {language.flagLabel ? `${language.flagLabel} ` : ''}\n {language.name}\n </option>\n ))}\n </select>\n </label>\n </div>\n )\n}\n"],"names":["useConfig","useRouteTransition","useTranslation","usePathname","useRouter","useSearchParams","useMemo","getCollectionFieldNames","getConfiguredLanguagesFromConfig","LanguageListToolbar","collectionSlug","config","pathname","router","searchParams","isTransitioning","startRouteTransition","t","languages","fieldNames","languageParam","language","currentLanguage","get","length","handleLanguageChange","nextParams","URLSearchParams","toString","set","delete","query","href","push","div","className","data-testid","label","select","aria-label","disabled","onChange","event","target","value","option","map","code","flagLabel","name"],"mappings":"AAAA;;AAEA,SAASA,SAAS,EAAEC,kBAAkB,EAAEC,cAAc,QAAQ,iBAAgB;AAC9E,SAASC,WAAW,EAAEC,SAAS,EAAEC,eAAe,QAAQ,qBAAoB;AAC5E,SAASC,OAAO,QAAQ,QAAO;AAE/B,OAAO,sBAAqB;AAO5B,SAASC,uBAAuB,EAAEC,gCAAgC,QAAQ,cAAa;AAMvF,OAAO,MAAMC,sBAAsB,CAAC,EAAEC,cAAc,EAAS;IAC3D,MAAM,EAAEC,MAAM,EAAE,GAAGX;IACnB,MAAMY,WAAWT;IACjB,MAAMU,SAAST;IACf,MAAMU,eAAeT;IACrB,MAAM,EAAEU,eAAe,EAAEC,oBAAoB,EAAE,GAAGf;IAClD,MAAM,EAAEgB,CAAC,EAAE,GAAGf;IAId,MAAMgB,YAAYV,iCAAiCG;IACnD,MAAMQ,aAAab,QACjB,IAAMC,wBAAwB;YAAEG;YAAgBC;QAAO,IACvD;QAACD;QAAgBC;KAAO;IAE1B,MAAMS,gBAAgB,CAAC,MAAM,EAAED,WAAWE,QAAQ,CAAC,SAAS,CAAC;IAC7D,MAAMC,kBAAkBR,aAAaS,GAAG,CAACH,kBAAkB;IAE3D,IAAI,CAACF,UAAUM,MAAM,EAAE;QACrB,OAAO;IACT;IAEA,MAAMC,uBAAuB,CAACJ;QAC5B,MAAMK,aAAa,IAAIC,gBAAgBb,aAAac,QAAQ;QAE5D,IAAIP,UAAU;YACZK,WAAWG,GAAG,CAACT,eAAeC;QAChC,OAAO;YACLK,WAAWI,MAAM,CAACV;QACpB;QAEAM,WAAWG,GAAG,CAAC,QAAQ;QAEvB,MAAME,QAAQL,WAAWE,QAAQ;QACjC,MAAMI,OAAOD,QAAQ,GAAGnB,SAAS,CAAC,EAAEmB,OAAO,GAAGnB;QAE9CI,qBAAqB;YACnBH,OAAOoB,IAAI,CAACD;QACd;IACF;IAEA,qBACE,KAACE;QAAIC,WAAU;QAA4BC,eAAY;kBACrD,cAAA,KAACC;YAAMF,WAAU;sBACf,cAAA,MAACG;gBACCC,cAAYtB,EAAE;gBACduB,UAAUzB;gBACV0B,UAAU,CAACC,QAAUjB,qBAAqBiB,MAAMC,MAAM,CAACC,KAAK;gBAC5DA,OAAOtB;;kCAEP,KAACuB;wBAAOD,OAAM;kCAAI3B,EAAE;;oBACnBC,UAAU4B,GAAG,CAAC,CAACzB,yBACd,MAACwB;4BAA2BD,OAAOvB,SAAS0B,IAAI;;gCAC7C1B,SAAS2B,SAAS,GAAG,GAAG3B,SAAS2B,SAAS,CAAC,CAAC,CAAC,GAAG;gCAChD3B,SAAS4B,IAAI;;2BAFH5B,SAAS0B,IAAI;;;;;AAStC,EAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { toast, useConfig, useDocumentInfo, useField, useRouteTransition } from '@payloadcms/ui';
|
|
3
|
+
import { toast, useConfig, useDocumentInfo, useField, useRouteTransition, useTranslation } from '@payloadcms/ui';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
5
|
import { formatAdminURL } from 'payload/shared';
|
|
6
6
|
import { useEffect, useMemo, useState } from 'react';
|
|
@@ -29,16 +29,13 @@ const PenIcon = ()=>/*#__PURE__*/ _jsxs("svg", {
|
|
|
29
29
|
})
|
|
30
30
|
]
|
|
31
31
|
});
|
|
32
|
-
const getDocumentID = (doc)=>{
|
|
33
|
-
const id = doc?.id;
|
|
34
|
-
return typeof id === 'number' || typeof id === 'string' ? id : undefined;
|
|
35
|
-
};
|
|
36
32
|
const isSameDocumentID = (left, right)=>left !== undefined && right !== undefined && String(left) === String(right);
|
|
37
33
|
export const LanguageMetabox = ()=>{
|
|
38
34
|
const { config } = useConfig();
|
|
39
|
-
const {
|
|
35
|
+
const { id, collectionSlug } = useDocumentInfo();
|
|
40
36
|
const router = useRouter();
|
|
41
37
|
const { isTransitioning, startRouteTransition } = useRouteTransition();
|
|
38
|
+
const { t } = useTranslation();
|
|
42
39
|
const fieldNames = useMemo(()=>getCollectionFieldNames({
|
|
43
40
|
collectionSlug,
|
|
44
41
|
config
|
|
@@ -134,7 +131,7 @@ export const LanguageMetabox = ()=>{
|
|
|
134
131
|
};
|
|
135
132
|
const createTranslation = async (targetLanguage)=>{
|
|
136
133
|
if (!collectionSlug || !docID) {
|
|
137
|
-
toast.error('
|
|
134
|
+
toast.error(t('payloadMultilang:saveBeforeCreatingTranslations'));
|
|
138
135
|
return;
|
|
139
136
|
}
|
|
140
137
|
setBusyLanguage(targetLanguage);
|
|
@@ -155,7 +152,7 @@ export const LanguageMetabox = ()=>{
|
|
|
155
152
|
setBusyLanguage(undefined);
|
|
156
153
|
if (!response.ok) {
|
|
157
154
|
const result = await response.json().catch(()=>undefined);
|
|
158
|
-
toast.error(result?.message || '
|
|
155
|
+
toast.error(result?.message || t('payloadMultilang:translationCouldNotBeCreated'));
|
|
159
156
|
return;
|
|
160
157
|
}
|
|
161
158
|
const result = await response.json();
|
|
@@ -171,14 +168,14 @@ export const LanguageMetabox = ()=>{
|
|
|
171
168
|
/*#__PURE__*/ _jsx("div", {
|
|
172
169
|
className: "payload-multilang-metabox__header",
|
|
173
170
|
children: /*#__PURE__*/ _jsx("h3", {
|
|
174
|
-
children:
|
|
171
|
+
children: t('payloadMultilang:language')
|
|
175
172
|
})
|
|
176
173
|
}),
|
|
177
174
|
/*#__PURE__*/ _jsxs("label", {
|
|
178
175
|
className: "payload-multilang-field",
|
|
179
176
|
children: [
|
|
180
177
|
/*#__PURE__*/ _jsx("span", {
|
|
181
|
-
children:
|
|
178
|
+
children: t('payloadMultilang:language')
|
|
182
179
|
}),
|
|
183
180
|
/*#__PURE__*/ _jsxs("div", {
|
|
184
181
|
className: "payload-multilang-language-select",
|
|
@@ -194,7 +191,7 @@ export const LanguageMetabox = ()=>{
|
|
|
194
191
|
children: [
|
|
195
192
|
/*#__PURE__*/ _jsx("option", {
|
|
196
193
|
value: "",
|
|
197
|
-
children:
|
|
194
|
+
children: t('payloadMultilang:unassigned')
|
|
198
195
|
}),
|
|
199
196
|
languages.map((language)=>{
|
|
200
197
|
const isCurrent = selectedLanguageCode === language.code;
|
|
@@ -220,7 +217,7 @@ export const LanguageMetabox = ()=>{
|
|
|
220
217
|
"data-testid": "multilang-sidebar-translations",
|
|
221
218
|
children: [
|
|
222
219
|
/*#__PURE__*/ _jsx("h4", {
|
|
223
|
-
children:
|
|
220
|
+
children: t('payloadMultilang:translations')
|
|
224
221
|
}),
|
|
225
222
|
languages.map((language)=>{
|
|
226
223
|
const translation = translationState?.translations[language.code];
|
|
@@ -240,19 +237,23 @@ export const LanguageMetabox = ()=>{
|
|
|
240
237
|
}),
|
|
241
238
|
isCurrent ? /*#__PURE__*/ _jsx("span", {
|
|
242
239
|
className: "payload-multilang-translation-row__current",
|
|
243
|
-
children:
|
|
240
|
+
children: t('payloadMultilang:current')
|
|
244
241
|
}) : isUnavailableForCreate ? /*#__PURE__*/ _jsx("span", {
|
|
245
242
|
className: "payload-multilang-translation-row__current",
|
|
246
|
-
children:
|
|
243
|
+
children: t('payloadMultilang:localized')
|
|
247
244
|
}) : translationID ? /*#__PURE__*/ _jsx("button", {
|
|
248
|
-
"aria-label":
|
|
245
|
+
"aria-label": t('payloadMultilang:editTranslationFor', {
|
|
246
|
+
language: language.name
|
|
247
|
+
}),
|
|
249
248
|
className: "payload-multilang-link-button",
|
|
250
249
|
disabled: isTransitioning,
|
|
251
250
|
onClick: ()=>navigateToTranslation(translationID),
|
|
252
251
|
type: "button",
|
|
253
252
|
children: /*#__PURE__*/ _jsx(PenIcon, {})
|
|
254
253
|
}) : /*#__PURE__*/ _jsx("button", {
|
|
255
|
-
"aria-label":
|
|
254
|
+
"aria-label": t('payloadMultilang:createTranslationFor', {
|
|
255
|
+
language: language.name
|
|
256
|
+
}),
|
|
256
257
|
className: "payload-multilang-icon-button",
|
|
257
258
|
disabled: !docID || busyLanguage === language.code || isTransitioning,
|
|
258
259
|
onClick: ()=>void createTranslation(language.code),
|
|
@@ -264,7 +265,7 @@ export const LanguageMetabox = ()=>{
|
|
|
264
265
|
}),
|
|
265
266
|
!docID ? /*#__PURE__*/ _jsx("div", {
|
|
266
267
|
className: "payload-multilang-muted",
|
|
267
|
-
children:
|
|
268
|
+
children: t('payloadMultilang:saveBeforeAddingTranslations')
|
|
268
269
|
}) : null
|
|
269
270
|
]
|
|
270
271
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/LanguageMetabox.tsx"],"sourcesContent":["'use client'\n\nimport { toast, useConfig, useDocumentInfo, useField, useRouteTransition } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport { useEffect, useMemo, useState } from 'react'\n\nimport '../styles/admin.css'\nimport type { TranslationState } from '../types.js'\nimport {\n getCollectionFieldNames,\n getConfiguredLanguagesFromConfig,\n getDefaultLanguageFromConfig,\n} from './config.js'\n\nconst PenIcon = () => (\n <svg\n aria-hidden=\"true\"\n className=\"payload-multilang-pen-icon\"\n fill=\"none\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n width=\"16\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M9.9 2.6 13.4 6 5.7 13.7 2.2 14.4 2.9 10.9 9.9 2.6Z\"\n stroke=\"currentColor\"\n strokeLinejoin=\"round\"\n strokeWidth=\"1.4\"\n />\n <path d=\"M8.8 3.9 12.1 7.2\" stroke=\"currentColor\" strokeLinecap=\"round\" strokeWidth=\"1.4\" />\n </svg>\n)\n\nconst getDocumentID = (doc: Record<string, unknown> | undefined): number | string | undefined => {\n const id = doc?.id\n\n return typeof id === 'number' || typeof id === 'string' ? id : undefined\n}\n\nconst isSameDocumentID = (\n left: number | string | undefined,\n right: number | string | undefined,\n): boolean => left !== undefined && right !== undefined && String(left) === String(right)\n\nexport const LanguageMetabox = () => {\n const { config } = useConfig()\n const { collectionSlug, id } = useDocumentInfo()\n const router = useRouter()\n const { isTransitioning, startRouteTransition } = useRouteTransition()\n const fieldNames = useMemo(\n () => getCollectionFieldNames({ collectionSlug, config }),\n [collectionSlug, config],\n )\n const languageField = useField<string>({ path: fieldNames.language })\n const languages = getConfiguredLanguagesFromConfig(config)\n const defaultLanguage = getDefaultLanguageFromConfig(config)\n const [busyLanguage, setBusyLanguage] = useState<string>()\n const [translationState, setTranslationState] = useState<TranslationState>()\n const groupField = useField<string>({ path: fieldNames.group })\n const docID = typeof id === 'number' || typeof id === 'string' ? id : undefined\n const groupValue = groupField.value\n const isCreation = !docID\n const localizedLanguageCodes = useMemo(\n () => new Set(Object.keys(translationState?.translations || {})),\n [translationState?.translations],\n )\n const configuredLanguageCodes = useMemo(\n () => new Set(languages.map((language) => language.code)),\n [languages],\n )\n const availableCreationLanguage = useMemo(\n () => languages.find((language) => !localizedLanguageCodes.has(language.code)),\n [languages, localizedLanguageCodes],\n )\n const preferredCreationLanguage =\n (defaultLanguage && !localizedLanguageCodes.has(defaultLanguage.code)\n ? defaultLanguage\n : availableCreationLanguage\n )?.code || ''\n\n useEffect(() => {\n if (isCreation) {\n if (!preferredCreationLanguage) {\n if (languageField.value && localizedLanguageCodes.has(languageField.value)) {\n languageField.setValue('')\n }\n\n return\n }\n\n if (!languageField.value || localizedLanguageCodes.has(languageField.value)) {\n languageField.setValue(preferredCreationLanguage)\n }\n\n return\n }\n\n if (\n languageField.value &&\n !configuredLanguageCodes.has(languageField.value)\n ) {\n languageField.setValue('')\n }\n }, [\n configuredLanguageCodes,\n isCreation,\n languageField,\n localizedLanguageCodes,\n preferredCreationLanguage,\n ])\n\n useEffect(() => {\n if (!collectionSlug || (!docID && !groupValue)) {\n setTranslationState(undefined)\n return\n }\n\n const loadState = async () => {\n const query = docID\n ? `id=${encodeURIComponent(String(docID))}`\n : `group=${encodeURIComponent(groupValue!)}`\n\n const response = await fetch(\n formatAdminURL({\n apiRoute: config.routes.api,\n path: `/${collectionSlug}/multilang/state?${query}`,\n }),\n {\n credentials: 'include',\n },\n )\n\n if (!response.ok) {\n return\n }\n\n const result = (await response.json()) as TranslationState\n setTranslationState(result)\n }\n\n void loadState()\n }, [collectionSlug, config.routes.api, docID, groupValue])\n\n const selectedLanguageCode = isCreation\n ? languageField.value || preferredCreationLanguage\n : languageField.value && configuredLanguageCodes.has(languageField.value)\n ? languageField.value\n : ''\n const selectedLanguage = languages.find((language) => language.code === selectedLanguageCode)\n const persistedLanguage = !isCreation ? translationState?.language : undefined\n const hasAssignedConfiguredLanguage = Boolean(\n !isCreation &&\n (translationState\n ? persistedLanguage && configuredLanguageCodes.has(persistedLanguage)\n : selectedLanguage),\n )\n\n const collectionAdminURL = (documentID: number | string) =>\n formatAdminURL({\n adminRoute: config.routes.admin,\n path: `/collections/${collectionSlug}/${documentID}`,\n })\n\n const navigateToTranslation = (documentID: number | string) => {\n startRouteTransition(() => {\n router.push(collectionAdminURL(documentID))\n })\n }\n\n const createTranslation = async (targetLanguage: string) => {\n if (!collectionSlug || !docID) {\n toast.error('Save the document before creating translations.')\n return\n }\n\n setBusyLanguage(targetLanguage)\n\n const response = await fetch(\n formatAdminURL({\n apiRoute: config.routes.api,\n path: `/${collectionSlug}/multilang/create`,\n }),\n {\n body: JSON.stringify({\n sourceId: docID,\n targetLanguage,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n },\n )\n\n setBusyLanguage(undefined)\n\n if (!response.ok) {\n const result = (await response.json().catch(() => undefined)) as\n { message?: string } | undefined\n toast.error(result?.message || 'Translation could not be created.')\n return\n }\n\n const result = (await response.json()) as {\n doc?: { id?: number | string }\n }\n\n if (result.doc?.id) {\n startRouteTransition(() => {\n router.push(collectionAdminURL(result.doc!.id!))\n })\n }\n }\n\n return (\n <section className=\"payload-multilang-metabox\">\n <div className=\"payload-multilang-metabox__header\">\n <h3>Language</h3>\n </div>\n\n <label className=\"payload-multilang-field\">\n <span>Language</span>\n <div className=\"payload-multilang-language-select\">\n <span className=\"payload-multilang-language-select__flag\">\n {selectedLanguage?.flagLabel || '--'}\n </span>\n <select\n disabled={hasAssignedConfiguredLanguage}\n onChange={(event) => languageField.setValue(event.target.value)}\n value={selectedLanguageCode}\n >\n <option value=\"\">Unassigned</option>\n {languages.map((language) => {\n const isCurrent = selectedLanguageCode === language.code\n const isLocalized = localizedLanguageCodes.has(language.code)\n const disabled =\n isCreation || !hasAssignedConfiguredLanguage\n ? isLocalized && !isCurrent\n : true\n\n return (\n <option disabled={disabled} key={language.code} value={language.code}>\n {language.flagLabel ? `${language.flagLabel} ` : ''}\n {language.name}\n </option>\n )\n })}\n </select>\n </div>\n </label>\n\n <div className=\"payload-multilang-translations\" data-testid=\"multilang-sidebar-translations\">\n <h4>Translations</h4>\n {languages.map((language) => {\n const translation = translationState?.translations[language.code]\n const translationID =\n typeof translation?.id === 'number' || typeof translation?.id === 'string'\n ? translation.id\n : undefined\n const isCurrent = isSameDocumentID(translationID, docID)\n const isUnavailableForCreate =\n isCreation && Boolean(translationID) && selectedLanguageCode !== language.code\n\n return (\n <div className=\"payload-multilang-translation-row\" key={language.code}>\n <span className=\"payload-multilang-translation-row__flag\">\n {language.flagLabel || language.code.toUpperCase()}\n </span>\n <span className=\"payload-multilang-translation-row__name\">{language.name}</span>\n {isCurrent ? (\n <span className=\"payload-multilang-translation-row__current\">Current</span>\n ) : isUnavailableForCreate ? (\n <span className=\"payload-multilang-translation-row__current\">Localized</span>\n ) : translationID ? (\n <button\n aria-label={`Edit ${language.name} translation`}\n className=\"payload-multilang-link-button\"\n disabled={isTransitioning}\n onClick={() => navigateToTranslation(translationID)}\n type=\"button\"\n >\n <PenIcon />\n </button>\n ) : (\n <button\n aria-label={`Create ${language.name} translation`}\n className=\"payload-multilang-icon-button\"\n disabled={!docID || busyLanguage === language.code || isTransitioning}\n onClick={() => void createTranslation(language.code)}\n type=\"button\"\n >\n +\n </button>\n )}\n </div>\n )\n })}\n {!docID ? (\n <div className=\"payload-multilang-muted\">Save before adding translations.</div>\n ) : null}\n </div>\n </section>\n )\n}\n"],"names":["toast","useConfig","useDocumentInfo","useField","useRouteTransition","useRouter","formatAdminURL","useEffect","useMemo","useState","getCollectionFieldNames","getConfiguredLanguagesFromConfig","getDefaultLanguageFromConfig","PenIcon","svg","aria-hidden","className","fill","height","viewBox","width","xmlns","path","d","stroke","strokeLinejoin","strokeWidth","strokeLinecap","getDocumentID","doc","id","undefined","isSameDocumentID","left","right","String","LanguageMetabox","config","collectionSlug","router","isTransitioning","startRouteTransition","fieldNames","languageField","language","languages","defaultLanguage","busyLanguage","setBusyLanguage","translationState","setTranslationState","groupField","group","docID","groupValue","value","isCreation","localizedLanguageCodes","Set","Object","keys","translations","configuredLanguageCodes","map","code","availableCreationLanguage","find","has","preferredCreationLanguage","setValue","loadState","query","encodeURIComponent","response","fetch","apiRoute","routes","api","credentials","ok","result","json","selectedLanguageCode","selectedLanguage","persistedLanguage","hasAssignedConfiguredLanguage","Boolean","collectionAdminURL","documentID","adminRoute","admin","navigateToTranslation","push","createTranslation","targetLanguage","error","body","JSON","stringify","sourceId","headers","method","catch","message","section","div","h3","label","span","flagLabel","select","disabled","onChange","event","target","option","isCurrent","isLocalized","name","data-testid","h4","translation","translationID","isUnavailableForCreate","toUpperCase","button","aria-label","onClick","type"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/LanguageMetabox.tsx"],"sourcesContent":["'use client'\n\nimport {\n toast,\n useConfig,\n useDocumentInfo,\n useField,\n useRouteTransition,\n useTranslation,\n} from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport { useEffect, useMemo, useState } from 'react'\n\nimport '../styles/admin.css'\n\nimport type {\n PayloadMultilangTranslationKey,\n PayloadMultilangTranslations,\n} from '../translations.js'\nimport type { TranslationState } from '../types.js'\n\nimport {\n getCollectionFieldNames,\n getConfiguredLanguagesFromConfig,\n getDefaultLanguageFromConfig,\n} from './config.js'\n\nconst PenIcon = () => (\n <svg\n aria-hidden=\"true\"\n className=\"payload-multilang-pen-icon\"\n fill=\"none\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n width=\"16\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M9.9 2.6 13.4 6 5.7 13.7 2.2 14.4 2.9 10.9 9.9 2.6Z\"\n stroke=\"currentColor\"\n strokeLinejoin=\"round\"\n strokeWidth=\"1.4\"\n />\n <path d=\"M8.8 3.9 12.1 7.2\" stroke=\"currentColor\" strokeLinecap=\"round\" strokeWidth=\"1.4\" />\n </svg>\n)\n\nconst isSameDocumentID = (\n left: number | string | undefined,\n right: number | string | undefined,\n): boolean => left !== undefined && right !== undefined && String(left) === String(right)\n\nexport const LanguageMetabox = () => {\n const { config } = useConfig()\n const { id, collectionSlug } = useDocumentInfo()\n const router = useRouter()\n const { isTransitioning, startRouteTransition } = useRouteTransition()\n const { t } = useTranslation<\n PayloadMultilangTranslations,\n PayloadMultilangTranslationKey\n >()\n const fieldNames = useMemo(\n () => getCollectionFieldNames({ collectionSlug, config }),\n [collectionSlug, config],\n )\n const languageField = useField<string>({ path: fieldNames.language })\n const languages = getConfiguredLanguagesFromConfig(config)\n const defaultLanguage = getDefaultLanguageFromConfig(config)\n const [busyLanguage, setBusyLanguage] = useState<string>()\n const [translationState, setTranslationState] = useState<TranslationState>()\n const groupField = useField<string>({ path: fieldNames.group })\n const docID = typeof id === 'number' || typeof id === 'string' ? id : undefined\n const groupValue = groupField.value\n const isCreation = !docID\n const localizedLanguageCodes = useMemo(\n () => new Set(Object.keys(translationState?.translations || {})),\n [translationState?.translations],\n )\n const configuredLanguageCodes = useMemo(\n () => new Set(languages.map((language) => language.code)),\n [languages],\n )\n const availableCreationLanguage = useMemo(\n () => languages.find((language) => !localizedLanguageCodes.has(language.code)),\n [languages, localizedLanguageCodes],\n )\n const preferredCreationLanguage =\n (defaultLanguage && !localizedLanguageCodes.has(defaultLanguage.code)\n ? defaultLanguage\n : availableCreationLanguage\n )?.code || ''\n\n useEffect(() => {\n if (isCreation) {\n if (!preferredCreationLanguage) {\n if (languageField.value && localizedLanguageCodes.has(languageField.value)) {\n languageField.setValue('')\n }\n\n return\n }\n\n if (!languageField.value || localizedLanguageCodes.has(languageField.value)) {\n languageField.setValue(preferredCreationLanguage)\n }\n\n return\n }\n\n if (\n languageField.value &&\n !configuredLanguageCodes.has(languageField.value)\n ) {\n languageField.setValue('')\n }\n }, [\n configuredLanguageCodes,\n isCreation,\n languageField,\n localizedLanguageCodes,\n preferredCreationLanguage,\n ])\n\n useEffect(() => {\n if (!collectionSlug || (!docID && !groupValue)) {\n setTranslationState(undefined)\n return\n }\n\n const loadState = async () => {\n const query = docID\n ? `id=${encodeURIComponent(String(docID))}`\n : `group=${encodeURIComponent(groupValue)}`\n\n const response = await fetch(\n formatAdminURL({\n apiRoute: config.routes.api,\n path: `/${collectionSlug}/multilang/state?${query}`,\n }),\n {\n credentials: 'include',\n },\n )\n\n if (!response.ok) {\n return\n }\n\n const result = (await response.json()) as TranslationState\n setTranslationState(result)\n }\n\n void loadState()\n }, [collectionSlug, config.routes.api, docID, groupValue])\n\n const selectedLanguageCode = isCreation\n ? languageField.value || preferredCreationLanguage\n : languageField.value && configuredLanguageCodes.has(languageField.value)\n ? languageField.value\n : ''\n const selectedLanguage = languages.find((language) => language.code === selectedLanguageCode)\n const persistedLanguage = !isCreation ? translationState?.language : undefined\n const hasAssignedConfiguredLanguage = Boolean(\n !isCreation &&\n (translationState\n ? persistedLanguage && configuredLanguageCodes.has(persistedLanguage)\n : selectedLanguage),\n )\n\n const collectionAdminURL = (documentID: number | string) =>\n formatAdminURL({\n adminRoute: config.routes.admin,\n path: `/collections/${collectionSlug}/${documentID}`,\n })\n\n const navigateToTranslation = (documentID: number | string) => {\n startRouteTransition(() => {\n router.push(collectionAdminURL(documentID))\n })\n }\n\n const createTranslation = async (targetLanguage: string) => {\n if (!collectionSlug || !docID) {\n toast.error(t('payloadMultilang:saveBeforeCreatingTranslations'))\n return\n }\n\n setBusyLanguage(targetLanguage)\n\n const response = await fetch(\n formatAdminURL({\n apiRoute: config.routes.api,\n path: `/${collectionSlug}/multilang/create`,\n }),\n {\n body: JSON.stringify({\n sourceId: docID,\n targetLanguage,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n },\n )\n\n setBusyLanguage(undefined)\n\n if (!response.ok) {\n const result = (await response.json().catch(() => undefined)) as\n { message?: string } | undefined\n toast.error(result?.message || t('payloadMultilang:translationCouldNotBeCreated'))\n return\n }\n\n const result = (await response.json()) as {\n doc?: { id?: number | string }\n }\n\n if (result.doc?.id) {\n startRouteTransition(() => {\n router.push(collectionAdminURL(result.doc!.id!))\n })\n }\n }\n\n return (\n <section className=\"payload-multilang-metabox\">\n <div className=\"payload-multilang-metabox__header\">\n <h3>{t('payloadMultilang:language')}</h3>\n </div>\n\n <label className=\"payload-multilang-field\">\n <span>{t('payloadMultilang:language')}</span>\n <div className=\"payload-multilang-language-select\">\n <span className=\"payload-multilang-language-select__flag\">\n {selectedLanguage?.flagLabel || '--'}\n </span>\n <select\n disabled={hasAssignedConfiguredLanguage}\n onChange={(event) => languageField.setValue(event.target.value)}\n value={selectedLanguageCode}\n >\n <option value=\"\">{t('payloadMultilang:unassigned')}</option>\n {languages.map((language) => {\n const isCurrent = selectedLanguageCode === language.code\n const isLocalized = localizedLanguageCodes.has(language.code)\n const disabled =\n isCreation || !hasAssignedConfiguredLanguage\n ? isLocalized && !isCurrent\n : true\n\n return (\n <option disabled={disabled} key={language.code} value={language.code}>\n {language.flagLabel ? `${language.flagLabel} ` : ''}\n {language.name}\n </option>\n )\n })}\n </select>\n </div>\n </label>\n\n <div className=\"payload-multilang-translations\" data-testid=\"multilang-sidebar-translations\">\n <h4>{t('payloadMultilang:translations')}</h4>\n {languages.map((language) => {\n const translation = translationState?.translations[language.code]\n const translationID =\n typeof translation?.id === 'number' || typeof translation?.id === 'string'\n ? translation.id\n : undefined\n const isCurrent = isSameDocumentID(translationID, docID)\n const isUnavailableForCreate =\n isCreation && Boolean(translationID) && selectedLanguageCode !== language.code\n\n return (\n <div className=\"payload-multilang-translation-row\" key={language.code}>\n <span className=\"payload-multilang-translation-row__flag\">\n {language.flagLabel || language.code.toUpperCase()}\n </span>\n <span className=\"payload-multilang-translation-row__name\">{language.name}</span>\n {isCurrent ? (\n <span className=\"payload-multilang-translation-row__current\">\n {t('payloadMultilang:current')}\n </span>\n ) : isUnavailableForCreate ? (\n <span className=\"payload-multilang-translation-row__current\">\n {t('payloadMultilang:localized')}\n </span>\n ) : translationID ? (\n <button\n aria-label={t('payloadMultilang:editTranslationFor', {\n language: language.name,\n })}\n className=\"payload-multilang-link-button\"\n disabled={isTransitioning}\n onClick={() => navigateToTranslation(translationID)}\n type=\"button\"\n >\n <PenIcon />\n </button>\n ) : (\n <button\n aria-label={t('payloadMultilang:createTranslationFor', {\n language: language.name,\n })}\n className=\"payload-multilang-icon-button\"\n disabled={!docID || busyLanguage === language.code || isTransitioning}\n onClick={() => void createTranslation(language.code)}\n type=\"button\"\n >\n +\n </button>\n )}\n </div>\n )\n })}\n {!docID ? (\n <div className=\"payload-multilang-muted\">\n {t('payloadMultilang:saveBeforeAddingTranslations')}\n </div>\n ) : null}\n </div>\n </section>\n )\n}\n"],"names":["toast","useConfig","useDocumentInfo","useField","useRouteTransition","useTranslation","useRouter","formatAdminURL","useEffect","useMemo","useState","getCollectionFieldNames","getConfiguredLanguagesFromConfig","getDefaultLanguageFromConfig","PenIcon","svg","aria-hidden","className","fill","height","viewBox","width","xmlns","path","d","stroke","strokeLinejoin","strokeWidth","strokeLinecap","isSameDocumentID","left","right","undefined","String","LanguageMetabox","config","id","collectionSlug","router","isTransitioning","startRouteTransition","t","fieldNames","languageField","language","languages","defaultLanguage","busyLanguage","setBusyLanguage","translationState","setTranslationState","groupField","group","docID","groupValue","value","isCreation","localizedLanguageCodes","Set","Object","keys","translations","configuredLanguageCodes","map","code","availableCreationLanguage","find","has","preferredCreationLanguage","setValue","loadState","query","encodeURIComponent","response","fetch","apiRoute","routes","api","credentials","ok","result","json","selectedLanguageCode","selectedLanguage","persistedLanguage","hasAssignedConfiguredLanguage","Boolean","collectionAdminURL","documentID","adminRoute","admin","navigateToTranslation","push","createTranslation","targetLanguage","error","body","JSON","stringify","sourceId","headers","method","catch","message","doc","section","div","h3","label","span","flagLabel","select","disabled","onChange","event","target","option","isCurrent","isLocalized","name","data-testid","h4","translation","translationID","isUnavailableForCreate","toUpperCase","button","aria-label","onClick","type"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Button, toast, useConfig, useRouteTransition } from '@payloadcms/ui';
|
|
3
|
+
import { Button, toast, useConfig, useRouteTransition, useTranslation } from '@payloadcms/ui';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
5
|
import { formatAdminURL } from 'payload/shared';
|
|
6
6
|
import { useState } from 'react';
|
|
@@ -9,6 +9,7 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
9
9
|
const { config } = useConfig();
|
|
10
10
|
const router = useRouter();
|
|
11
11
|
const { startRouteTransition } = useRouteTransition();
|
|
12
|
+
const { t } = useTranslation();
|
|
12
13
|
const [targetLanguage, setTargetLanguage] = useState('');
|
|
13
14
|
const [targetID, setTargetID] = useState('');
|
|
14
15
|
const [isBusy, setIsBusy] = useState(false);
|
|
@@ -18,7 +19,7 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
18
19
|
});
|
|
19
20
|
const createTranslation = async ()=>{
|
|
20
21
|
if (!targetLanguage) {
|
|
21
|
-
toast.error('
|
|
22
|
+
toast.error(t('payloadMultilang:chooseTargetLanguage'));
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
25
|
setIsBusy(true);
|
|
@@ -36,7 +37,7 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
36
37
|
setIsBusy(false);
|
|
37
38
|
if (!response.ok) {
|
|
38
39
|
const result = await response.json().catch(()=>undefined);
|
|
39
|
-
toast.error(result?.message || '
|
|
40
|
+
toast.error(result?.message || t('payloadMultilang:translationCouldNotBeCreated'));
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
43
|
const result = await response.json();
|
|
@@ -52,7 +53,7 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
52
53
|
};
|
|
53
54
|
const connectTranslation = async ()=>{
|
|
54
55
|
if (!targetID || !targetLanguage) {
|
|
55
|
-
toast.error('
|
|
56
|
+
toast.error(t('payloadMultilang:targetIDAndLanguageRequired'));
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
setIsBusy(true);
|
|
@@ -71,10 +72,10 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
71
72
|
setIsBusy(false);
|
|
72
73
|
if (!response.ok) {
|
|
73
74
|
const result = await response.json().catch(()=>undefined);
|
|
74
|
-
toast.error(result?.message || '
|
|
75
|
+
toast.error(result?.message || t('payloadMultilang:translationCouldNotBeConnected'));
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
77
|
-
toast.success('
|
|
78
|
+
toast.success(t('payloadMultilang:translationConnected'));
|
|
78
79
|
startRouteTransition(()=>{
|
|
79
80
|
router.refresh();
|
|
80
81
|
});
|
|
@@ -93,10 +94,10 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
93
94
|
});
|
|
94
95
|
setIsBusy(false);
|
|
95
96
|
if (!response.ok) {
|
|
96
|
-
toast.error('
|
|
97
|
+
toast.error(t('payloadMultilang:translationCouldNotBeDisconnected'));
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
99
|
-
toast.success('
|
|
100
|
+
toast.success(t('payloadMultilang:translationDisconnected'));
|
|
100
101
|
startRouteTransition(()=>{
|
|
101
102
|
router.refresh();
|
|
102
103
|
});
|
|
@@ -106,7 +107,7 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
106
107
|
className: "payload-multilang-panel",
|
|
107
108
|
children: [
|
|
108
109
|
/*#__PURE__*/ _jsx("h2", {
|
|
109
|
-
children:
|
|
110
|
+
children: t('payloadMultilang:translationActions')
|
|
110
111
|
}),
|
|
111
112
|
/*#__PURE__*/ _jsxs("div", {
|
|
112
113
|
className: "payload-multilang-inline-form",
|
|
@@ -117,7 +118,7 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
117
118
|
children: [
|
|
118
119
|
/*#__PURE__*/ _jsx("option", {
|
|
119
120
|
value: "",
|
|
120
|
-
children:
|
|
121
|
+
children: t('payloadMultilang:chooseLanguage')
|
|
121
122
|
}),
|
|
122
123
|
availableLanguages.map((language)=>/*#__PURE__*/ _jsxs("option", {
|
|
123
124
|
value: language.code,
|
|
@@ -132,7 +133,7 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
132
133
|
disabled: isBusy || !targetLanguage,
|
|
133
134
|
onClick: createTranslation,
|
|
134
135
|
type: "button",
|
|
135
|
-
children:
|
|
136
|
+
children: t('payloadMultilang:createTranslation')
|
|
136
137
|
})
|
|
137
138
|
]
|
|
138
139
|
}),
|
|
@@ -140,15 +141,16 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
140
141
|
className: "payload-multilang-inline-form",
|
|
141
142
|
children: [
|
|
142
143
|
/*#__PURE__*/ _jsx("input", {
|
|
144
|
+
"aria-label": t('payloadMultilang:existingDocumentID'),
|
|
143
145
|
onChange: (event)=>setTargetID(event.target.value),
|
|
144
|
-
placeholder:
|
|
146
|
+
placeholder: t('payloadMultilang:existingDocumentID'),
|
|
145
147
|
value: targetID
|
|
146
148
|
}),
|
|
147
149
|
/*#__PURE__*/ _jsx(Button, {
|
|
148
150
|
disabled: isBusy || !targetID || !targetLanguage,
|
|
149
151
|
onClick: connectTranslation,
|
|
150
152
|
type: "button",
|
|
151
|
-
children:
|
|
153
|
+
children: t('payloadMultilang:connectExisting')
|
|
152
154
|
})
|
|
153
155
|
]
|
|
154
156
|
}),
|
|
@@ -157,7 +159,7 @@ export const TranslationActionsClient = ({ collectionSlug, docID, languages, sta
|
|
|
157
159
|
disabled: isBusy,
|
|
158
160
|
onClick: disconnectTranslation,
|
|
159
161
|
type: "button",
|
|
160
|
-
children:
|
|
162
|
+
children: t('payloadMultilang:disconnectDocument')
|
|
161
163
|
})
|
|
162
164
|
]
|
|
163
165
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/TranslationActionsClient.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useConfig, useRouteTransition } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport { useState } from 'react'\n\nimport '../styles/admin.css'\nimport type { MultilangLanguage, TranslationState } from '../types.js'\n\ntype Props = {\n collectionSlug: string\n docID: number | string\n languages: MultilangLanguage[]\n state: TranslationState\n}\n\nexport const TranslationActionsClient = ({\n collectionSlug,\n docID,\n languages,\n state,\n}: Props) => {\n const { config } = useConfig()\n const router = useRouter()\n const { startRouteTransition } = useRouteTransition()\n const [targetLanguage, setTargetLanguage] = useState('')\n const [targetID, setTargetID] = useState('')\n const [isBusy, setIsBusy] = useState(false)\n\n const collectionAPIPath = (path: string) =>\n formatAdminURL({\n apiRoute: config.routes.api,\n path: `/${collectionSlug}${path}`,\n })\n\n const createTranslation = async () => {\n if (!targetLanguage) {\n toast.error('
|
|
1
|
+
{"version":3,"sources":["../../src/components/TranslationActionsClient.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useConfig, useRouteTransition, useTranslation } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport { useState } from 'react'\n\nimport '../styles/admin.css'\nimport type {\n PayloadMultilangTranslationKey,\n PayloadMultilangTranslations,\n} from '../translations.js'\nimport type { MultilangLanguage, TranslationState } from '../types.js'\n\ntype Props = {\n collectionSlug: string\n docID: number | string\n languages: MultilangLanguage[]\n state: TranslationState\n}\n\nexport const TranslationActionsClient = ({\n collectionSlug,\n docID,\n languages,\n state,\n}: Props) => {\n const { config } = useConfig()\n const router = useRouter()\n const { startRouteTransition } = useRouteTransition()\n const { t } = useTranslation<\n PayloadMultilangTranslations,\n PayloadMultilangTranslationKey\n >()\n const [targetLanguage, setTargetLanguage] = useState('')\n const [targetID, setTargetID] = useState('')\n const [isBusy, setIsBusy] = useState(false)\n\n const collectionAPIPath = (path: string) =>\n formatAdminURL({\n apiRoute: config.routes.api,\n path: `/${collectionSlug}${path}`,\n })\n\n const createTranslation = async () => {\n if (!targetLanguage) {\n toast.error(t('payloadMultilang:chooseTargetLanguage'))\n return\n }\n\n setIsBusy(true)\n const response = await fetch(collectionAPIPath('/multilang/create'), {\n body: JSON.stringify({\n sourceId: docID,\n targetLanguage,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n setIsBusy(false)\n\n if (!response.ok) {\n const result = (await response.json().catch(() => undefined)) as\n | { message?: string }\n | undefined\n toast.error(result?.message || t('payloadMultilang:translationCouldNotBeCreated'))\n return\n }\n\n const result = (await response.json()) as { doc?: { id?: number | string } }\n\n const documentID = result.doc?.id\n\n if (documentID) {\n startRouteTransition(() => {\n router.push(\n formatAdminURL({\n adminRoute: config.routes.admin,\n path: `/collections/${collectionSlug}/${documentID}`,\n }),\n )\n })\n }\n }\n\n const connectTranslation = async () => {\n if (!targetID || !targetLanguage) {\n toast.error(t('payloadMultilang:targetIDAndLanguageRequired'))\n return\n }\n\n setIsBusy(true)\n const response = await fetch(collectionAPIPath('/multilang/connect'), {\n body: JSON.stringify({\n sourceId: docID,\n targetId: targetID,\n targetLanguage,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n setIsBusy(false)\n\n if (!response.ok) {\n const result = (await response.json().catch(() => undefined)) as\n | { message?: string }\n | undefined\n toast.error(result?.message || t('payloadMultilang:translationCouldNotBeConnected'))\n return\n }\n\n toast.success(t('payloadMultilang:translationConnected'))\n startRouteTransition(() => {\n router.refresh()\n })\n }\n\n const disconnectTranslation = async () => {\n setIsBusy(true)\n const response = await fetch(collectionAPIPath('/multilang/disconnect'), {\n body: JSON.stringify({ id: docID }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n setIsBusy(false)\n\n if (!response.ok) {\n toast.error(t('payloadMultilang:translationCouldNotBeDisconnected'))\n return\n }\n\n toast.success(t('payloadMultilang:translationDisconnected'))\n startRouteTransition(() => {\n router.refresh()\n })\n }\n\n const availableLanguages = languages.filter(\n (language) => !state.translations[language.code],\n )\n\n return (\n <section className=\"payload-multilang-panel\">\n <h2>{t('payloadMultilang:translationActions')}</h2>\n <div className=\"payload-multilang-inline-form\">\n <select\n onChange={(event) => setTargetLanguage(event.target.value)}\n value={targetLanguage}\n >\n <option value=\"\">{t('payloadMultilang:chooseLanguage')}</option>\n {availableLanguages.map((language) => (\n <option key={language.code} value={language.code}>\n {language.flagLabel ? `${language.flagLabel} ` : ''}\n {language.name}\n </option>\n ))}\n </select>\n <Button disabled={isBusy || !targetLanguage} onClick={createTranslation} type=\"button\">\n {t('payloadMultilang:createTranslation')}\n </Button>\n </div>\n\n <div className=\"payload-multilang-inline-form\">\n <input\n aria-label={t('payloadMultilang:existingDocumentID')}\n onChange={(event) => setTargetID(event.target.value)}\n placeholder={t('payloadMultilang:existingDocumentID')}\n value={targetID}\n />\n <Button disabled={isBusy || !targetID || !targetLanguage} onClick={connectTranslation} type=\"button\">\n {t('payloadMultilang:connectExisting')}\n </Button>\n </div>\n\n <Button buttonStyle=\"secondary\" disabled={isBusy} onClick={disconnectTranslation} type=\"button\">\n {t('payloadMultilang:disconnectDocument')}\n </Button>\n </section>\n )\n}\n"],"names":["Button","toast","useConfig","useRouteTransition","useTranslation","useRouter","formatAdminURL","useState","TranslationActionsClient","collectionSlug","docID","languages","state","config","router","startRouteTransition","t","targetLanguage","setTargetLanguage","targetID","setTargetID","isBusy","setIsBusy","collectionAPIPath","path","apiRoute","routes","api","createTranslation","error","response","fetch","body","JSON","stringify","sourceId","credentials","headers","method","ok","result","json","catch","undefined","message","documentID","doc","id","push","adminRoute","admin","connectTranslation","targetId","success","refresh","disconnectTranslation","availableLanguages","filter","language","translations","code","section","className","h2","div","select","onChange","event","target","value","option","map","flagLabel","name","disabled","onClick","type","input","aria-label","placeholder","buttonStyle"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { DEFAULT_FIELD_NAMES } from '../constants.js';
|
|
|
3
3
|
import { asDocument, getID, getStringValue } from '../lib/data.js';
|
|
4
4
|
import { getPayloadMultilangCustom } from './config.js';
|
|
5
5
|
import { TranslationColumnCellClient } from './TranslationColumnCellClient.js';
|
|
6
|
+
const translate = (props, key, options)=>props.i18n?.t ? props.i18n.t(key, options) : key;
|
|
6
7
|
const getCollectionConfig = (props)=>props.collectionConfig || props.payload.collections[props.collectionSlug]?.config;
|
|
7
8
|
const getFieldNames = (props)=>({
|
|
8
9
|
...DEFAULT_FIELD_NAMES,
|
|
@@ -53,7 +54,9 @@ export const TranslationColumnCell = async (props)=>{
|
|
|
53
54
|
}
|
|
54
55
|
return /*#__PURE__*/ _jsx("div", {
|
|
55
56
|
className: "payload-multilang-column-stack",
|
|
56
|
-
title: currentLanguage ? undefined : storedLanguage ?
|
|
57
|
+
title: currentLanguage ? undefined : storedLanguage ? translate(props, 'payloadMultilang:unassignedStoredLanguageNotConfigured', {
|
|
58
|
+
language: storedLanguage
|
|
59
|
+
}) : translate(props, 'payloadMultilang:unassigned'),
|
|
57
60
|
children: languages.map((language)=>/*#__PURE__*/ _jsx(TranslationColumnCellClient, {
|
|
58
61
|
collectionSlug: collectionConfig.slug,
|
|
59
62
|
current: currentLanguage === language.code,
|