@roxxel/payload-multilang 0.0.2 → 0.0.5

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 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
@@ -97,13 +97,14 @@ const posts = await payload.find({
97
97
 
98
98
  ## Translation Helpers
99
99
 
100
+ In request-scoped code, use the `WithPayload` helpers with `req.payload`.
101
+
100
102
  ```ts
101
103
  import {
102
- findGlobalByLanguage,
103
- getDocumentTranslation,
104
- getDocumentTranslations,
105
- getLanguages,
106
- updateGlobalByLanguage,
104
+ findGlobalByLanguageWithPayload,
105
+ getDocumentTranslationWithPayload,
106
+ getDocumentTranslationsWithPayload,
107
+ updateGlobalByLanguageWithPayload,
107
108
  withLanguage,
108
109
  } from '@roxxel/payload-multilang'
109
110
  ```
@@ -111,33 +112,55 @@ import {
111
112
  Common examples:
112
113
 
113
114
  ```ts
114
- const languages = await getLanguages()
115
-
116
- const state = await getDocumentTranslations({
115
+ const state = await getDocumentTranslationsWithPayload({
117
116
  collection: 'posts',
118
117
  id: post.id,
118
+ payload: req.payload,
119
+ req,
120
+ overrideAccess: false,
119
121
  })
120
122
 
121
- const ukrainianPost = await getDocumentTranslation({
123
+ const ukrainianPost = await getDocumentTranslationWithPayload({
122
124
  collection: 'posts',
123
125
  id: post.id,
124
126
  language: 'uk',
127
+ payload: req.payload,
128
+ req,
129
+ overrideAccess: false,
125
130
  })
126
131
 
127
- const settings = await findGlobalByLanguage({
132
+ const settings = await findGlobalByLanguageWithPayload({
128
133
  slug: 'site-settings',
129
134
  language: 'uk',
135
+ payload: req.payload,
136
+ req,
137
+ overrideAccess: false,
130
138
  })
131
139
 
132
- await updateGlobalByLanguage({
140
+ await updateGlobalByLanguageWithPayload({
133
141
  slug: 'site-settings',
134
142
  language: 'uk',
143
+ payload: req.payload,
144
+ req,
145
+ overrideAccess: false,
135
146
  data: {
136
147
  title: 'Localized title',
137
148
  },
138
149
  })
139
150
  ```
140
151
 
152
+ For app-level convenience, create your own configured helper module:
153
+
154
+ ```ts
155
+ import { createMultilangHelpers } from '@roxxel/payload-multilang'
156
+ import { getPayload } from 'payload'
157
+ import config from '@payload-config'
158
+
159
+ export const multilang = createMultilangHelpers({
160
+ getPayload: () => getPayload({ config }),
161
+ })
162
+ ```
163
+
141
164
  ## Documentation
142
165
 
143
166
  - [Configuration](./docs/configuration.md)
@@ -1,5 +1,8 @@
1
1
  import type { MultilangFieldNames } from './types.js';
2
2
  export declare const DEFAULT_FIELD_NAMES: MultilangFieldNames;
3
+ export declare const MULTILANG_GROUP_FIELD: string;
4
+ export declare const MULTILANG_LANGUAGE_FIELD: string;
5
+ export declare const MULTILANG_META_FIELD: string;
3
6
  export declare const DEFAULT_GLOBAL_TABS_LABEL = "Localized content";
4
7
  export declare const DEFAULT_DUPLICATE_EXCLUDE_FIELDS: string[];
5
8
  export declare const MULTILANG_SKIP_HOOK = "payloadMultilangSkip";
package/dist/constants.js CHANGED
@@ -3,6 +3,9 @@ export const DEFAULT_FIELD_NAMES = {
3
3
  language: '_multilangLanguage',
4
4
  meta: '_multilangMeta'
5
5
  };
6
+ export const MULTILANG_GROUP_FIELD = DEFAULT_FIELD_NAMES.group;
7
+ export const MULTILANG_LANGUAGE_FIELD = DEFAULT_FIELD_NAMES.language;
8
+ export const MULTILANG_META_FIELD = DEFAULT_FIELD_NAMES.meta;
6
9
  export const DEFAULT_GLOBAL_TABS_LABEL = 'Localized content';
7
10
  export const DEFAULT_DUPLICATE_EXCLUDE_FIELDS = [
8
11
  'id',
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["import type { MultilangFieldNames } from './types.js'\n\nexport const DEFAULT_FIELD_NAMES: MultilangFieldNames = {\n group: '_multilangGroup',\n language: '_multilangLanguage',\n meta: '_multilangMeta',\n}\n\nexport const DEFAULT_GLOBAL_TABS_LABEL = 'Localized content'\n\nexport const DEFAULT_DUPLICATE_EXCLUDE_FIELDS = [\n 'id',\n 'createdAt',\n 'updatedAt',\n 'deletedAt',\n '_status',\n 'sizes',\n 'filename',\n 'filesize',\n 'height',\n 'mimeType',\n 'thumbnailURL',\n 'url',\n 'width',\n]\n\nexport const MULTILANG_SKIP_HOOK = 'payloadMultilangSkip'\n"],"names":["DEFAULT_FIELD_NAMES","group","language","meta","DEFAULT_GLOBAL_TABS_LABEL","DEFAULT_DUPLICATE_EXCLUDE_FIELDS","MULTILANG_SKIP_HOOK"],"mappings":"AAEA,OAAO,MAAMA,sBAA2C;IACtDC,OAAO;IACPC,UAAU;IACVC,MAAM;AACR,EAAC;AAED,OAAO,MAAMC,4BAA4B,oBAAmB;AAE5D,OAAO,MAAMC,mCAAmC;IAC9C;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAED,OAAO,MAAMC,sBAAsB,uBAAsB"}
1
+ {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["import type { MultilangFieldNames } from './types.js'\n\nexport const DEFAULT_FIELD_NAMES: MultilangFieldNames = {\n group: '_multilangGroup',\n language: '_multilangLanguage',\n meta: '_multilangMeta',\n}\n\nexport const MULTILANG_GROUP_FIELD = DEFAULT_FIELD_NAMES.group\nexport const MULTILANG_LANGUAGE_FIELD = DEFAULT_FIELD_NAMES.language\nexport const MULTILANG_META_FIELD = DEFAULT_FIELD_NAMES.meta\n\nexport const DEFAULT_GLOBAL_TABS_LABEL = 'Localized content'\n\nexport const DEFAULT_DUPLICATE_EXCLUDE_FIELDS = [\n 'id',\n 'createdAt',\n 'updatedAt',\n 'deletedAt',\n '_status',\n 'sizes',\n 'filename',\n 'filesize',\n 'height',\n 'mimeType',\n 'thumbnailURL',\n 'url',\n 'width',\n]\n\nexport const MULTILANG_SKIP_HOOK = 'payloadMultilangSkip'\n"],"names":["DEFAULT_FIELD_NAMES","group","language","meta","MULTILANG_GROUP_FIELD","MULTILANG_LANGUAGE_FIELD","MULTILANG_META_FIELD","DEFAULT_GLOBAL_TABS_LABEL","DEFAULT_DUPLICATE_EXCLUDE_FIELDS","MULTILANG_SKIP_HOOK"],"mappings":"AAEA,OAAO,MAAMA,sBAA2C;IACtDC,OAAO;IACPC,UAAU;IACVC,MAAM;AACR,EAAC;AAED,OAAO,MAAMC,wBAAwBJ,oBAAoBC,KAAK,CAAA;AAC9D,OAAO,MAAMI,2BAA2BL,oBAAoBE,QAAQ,CAAA;AACpE,OAAO,MAAMI,uBAAuBN,oBAAoBG,IAAI,CAAA;AAE5D,OAAO,MAAMI,4BAA4B,oBAAmB;AAE5D,OAAO,MAAMC,mCAAmC;IAC9C;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAED,OAAO,MAAMC,sBAAsB,uBAAsB"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { Plugin } from 'payload';
2
2
  import type { PayloadMultilangConfig } from './types.js';
3
- export { findGlobalByLanguage, findGlobalByLanguageWithPayload, getDocumentTranslation, getDocumentTranslations, getLanguages, updateGlobalByLanguage, updateGlobalByLanguageWithPayload, withLanguage, } from './lib/data.js';
3
+ export { DEFAULT_FIELD_NAMES, MULTILANG_GROUP_FIELD, MULTILANG_LANGUAGE_FIELD, MULTILANG_META_FIELD, } from './constants.js';
4
+ export { createMultilangHelpers, findGlobalByLanguageWithPayload, getDocumentTranslationsWithPayload, getDocumentTranslationWithPayload, getGroupTranslationsWithPayload, getMultilangDocumentLanguage, localizedSlugQuery, updateGlobalByLanguageWithPayload, withLanguage, } from './lib/data.js';
5
+ export type { FindGlobalByLanguageArgs, GetDocumentTranslationArgs, GetDocumentTranslationsArgs, LocalizedSlugQuery, LocalizedSlugQueryArgs, LocalizedSlugQueryStatus, MultilangDocumentLanguageArgs, MultilangHelpers, PayloadFactory, UpdateGlobalByLanguageArgs, } from './lib/data.js';
4
6
  export type { MultilangCollectionOptions, MultilangFieldNames, MultilangGlobalOptions, MultilangLanguage, PayloadMultilangConfig, ResolvedMultilangGlobal, TranslationMap, TranslationState, } from './types.js';
5
7
  export declare const payloadMultilang: (pluginOptions?: PayloadMultilangConfig) => Plugin;
package/dist/index.js CHANGED
@@ -2,13 +2,17 @@ import { createTranslationEndpoints } from './endpoints/translations.js';
2
2
  import { withTranslatedCollection } from './hooks/translatedCollection.js';
3
3
  import { withTranslatedGlobal } from './hooks/translatedGlobal.js';
4
4
  import { getCollectionConfig, getGlobalConfig, sanitizePluginConfig } from './lib/config.js';
5
- export { findGlobalByLanguage, findGlobalByLanguageWithPayload, getDocumentTranslation, getDocumentTranslations, getLanguages, updateGlobalByLanguage, updateGlobalByLanguageWithPayload, withLanguage } from './lib/data.js';
5
+ export { DEFAULT_FIELD_NAMES, MULTILANG_GROUP_FIELD, MULTILANG_LANGUAGE_FIELD, MULTILANG_META_FIELD } from './constants.js';
6
+ export { createMultilangHelpers, findGlobalByLanguageWithPayload, getDocumentTranslationsWithPayload, getDocumentTranslationWithPayload, getGroupTranslationsWithPayload, getMultilangDocumentLanguage, localizedSlugQuery, updateGlobalByLanguageWithPayload, withLanguage } from './lib/data.js';
6
7
  export const payloadMultilang = (pluginOptions = {
7
8
  languages: []
8
9
  })=>(config)=>{
9
10
  if (pluginOptions.disabled) {
10
11
  return config;
11
12
  }
13
+ if (config.localization) {
14
+ throw new Error('payload-multilang uses per-language documents and cannot run with Payload built-in localization enabled. Set localization: false and do not use localized: true fields for this plugin model.');
15
+ }
12
16
  const pluginConfig = sanitizePluginConfig(pluginOptions);
13
17
  const incomingCollections = config.collections || [];
14
18
  const collections = incomingCollections.map((collection)=>{
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, GlobalConfig, Plugin } from 'payload'\n\nimport type { PayloadMultilangConfig } from './types.js'\n\nimport { createTranslationEndpoints } from './endpoints/translations.js'\nimport { withTranslatedCollection } from './hooks/translatedCollection.js'\nimport { withTranslatedGlobal } from './hooks/translatedGlobal.js'\nimport {\n getCollectionConfig,\n getGlobalConfig,\n sanitizePluginConfig,\n} from './lib/config.js'\n\nexport {\n findGlobalByLanguage,\n findGlobalByLanguageWithPayload,\n getDocumentTranslation,\n getDocumentTranslations,\n getLanguages,\n updateGlobalByLanguage,\n updateGlobalByLanguageWithPayload,\n withLanguage,\n} from './lib/data.js'\n\nexport type {\n MultilangCollectionOptions,\n MultilangFieldNames,\n MultilangGlobalOptions,\n MultilangLanguage,\n PayloadMultilangConfig,\n ResolvedMultilangGlobal,\n TranslationMap,\n TranslationState,\n} from './types.js'\n\nexport const payloadMultilang =\n (pluginOptions: PayloadMultilangConfig = { languages: [] }): Plugin =>\n (config: Config): Config => {\n if (pluginOptions.disabled) {\n return config\n }\n\n const pluginConfig = sanitizePluginConfig(pluginOptions)\n\n const incomingCollections = config.collections || []\n const collections = incomingCollections.map(\n (collection): CollectionConfig => {\n const translatedCollection = getCollectionConfig(\n pluginConfig.collections,\n collection.slug,\n )\n\n if (!translatedCollection) {\n return collection\n }\n\n const translated = withTranslatedCollection({\n collection: translatedCollection,\n config: collection,\n })\n\n return {\n ...translated,\n endpoints: [\n ...(translated.endpoints || []),\n ...createTranslationEndpoints({\n collection: translatedCollection,\n collectionConfig: collection,\n }),\n ],\n }\n },\n )\n\n const incomingGlobals = config.globals || []\n const globals = incomingGlobals.map((globalConfig): GlobalConfig => {\n const translatedGlobal = getGlobalConfig(\n pluginConfig.globals,\n globalConfig.slug,\n )\n\n if (!translatedGlobal) {\n return globalConfig\n }\n\n return withTranslatedGlobal({\n config: globalConfig,\n global: translatedGlobal,\n })\n })\n\n return {\n ...config,\n admin: {\n ...config.admin,\n custom: {\n ...config.admin?.custom,\n payloadMultilang: {\n ...((config.admin?.custom as\n | { payloadMultilang?: Record<string, unknown> }\n | undefined)?.payloadMultilang || {}),\n defaultLanguage: pluginConfig.defaultLanguage,\n languages: pluginConfig.languages,\n },\n },\n },\n collections,\n globals,\n }\n }\n"],"names":["createTranslationEndpoints","withTranslatedCollection","withTranslatedGlobal","getCollectionConfig","getGlobalConfig","sanitizePluginConfig","findGlobalByLanguage","findGlobalByLanguageWithPayload","getDocumentTranslation","getDocumentTranslations","getLanguages","updateGlobalByLanguage","updateGlobalByLanguageWithPayload","withLanguage","payloadMultilang","pluginOptions","languages","config","disabled","pluginConfig","incomingCollections","collections","map","collection","translatedCollection","slug","translated","endpoints","collectionConfig","incomingGlobals","globals","globalConfig","translatedGlobal","global","admin","custom","defaultLanguage"],"mappings":"AAIA,SAASA,0BAA0B,QAAQ,8BAA6B;AACxE,SAASC,wBAAwB,QAAQ,kCAAiC;AAC1E,SAASC,oBAAoB,QAAQ,8BAA6B;AAClE,SACEC,mBAAmB,EACnBC,eAAe,EACfC,oBAAoB,QACf,kBAAiB;AAExB,SACEC,oBAAoB,EACpBC,+BAA+B,EAC/BC,sBAAsB,EACtBC,uBAAuB,EACvBC,YAAY,EACZC,sBAAsB,EACtBC,iCAAiC,EACjCC,YAAY,QACP,gBAAe;AAatB,OAAO,MAAMC,mBACX,CAACC,gBAAwC;IAAEC,WAAW,EAAE;AAAC,CAAC,GAC1D,CAACC;QACC,IAAIF,cAAcG,QAAQ,EAAE;YAC1B,OAAOD;QACT;QAEA,MAAME,eAAed,qBAAqBU;QAE1C,MAAMK,sBAAsBH,OAAOI,WAAW,IAAI,EAAE;QACpD,MAAMA,cAAcD,oBAAoBE,GAAG,CACzC,CAACC;YACC,MAAMC,uBAAuBrB,oBAC3BgB,aAAaE,WAAW,EACxBE,WAAWE,IAAI;YAGjB,IAAI,CAACD,sBAAsB;gBACzB,OAAOD;YACT;YAEA,MAAMG,aAAazB,yBAAyB;gBAC1CsB,YAAYC;gBACZP,QAAQM;YACV;YAEA,OAAO;gBACL,GAAGG,UAAU;gBACbC,WAAW;uBACLD,WAAWC,SAAS,IAAI,EAAE;uBAC3B3B,2BAA2B;wBAC5BuB,YAAYC;wBACZI,kBAAkBL;oBACpB;iBACD;YACH;QACF;QAGF,MAAMM,kBAAkBZ,OAAOa,OAAO,IAAI,EAAE;QAC5C,MAAMA,UAAUD,gBAAgBP,GAAG,CAAC,CAACS;YACnC,MAAMC,mBAAmB5B,gBACvBe,aAAaW,OAAO,EACpBC,aAAaN,IAAI;YAGnB,IAAI,CAACO,kBAAkB;gBACrB,OAAOD;YACT;YAEA,OAAO7B,qBAAqB;gBAC1Be,QAAQc;gBACRE,QAAQD;YACV;QACF;QAEA,OAAO;YACL,GAAGf,MAAM;YACTiB,OAAO;gBACL,GAAGjB,OAAOiB,KAAK;gBACfC,QAAQ;oBACN,GAAGlB,OAAOiB,KAAK,EAAEC,MAAM;oBACvBrB,kBAAkB;wBAChB,GAAI,AAACG,OAAOiB,KAAK,EAAEC,QAEHrB,oBAAoB,CAAC,CAAC;wBACtCsB,iBAAiBjB,aAAaiB,eAAe;wBAC7CpB,WAAWG,aAAaH,SAAS;oBACnC;gBACF;YACF;YACAK;YACAS;QACF;IACF,EAAC"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, GlobalConfig, Plugin } from 'payload'\n\nimport type { PayloadMultilangConfig } from './types.js'\n\nimport { createTranslationEndpoints } from './endpoints/translations.js'\nimport { withTranslatedCollection } from './hooks/translatedCollection.js'\nimport { withTranslatedGlobal } from './hooks/translatedGlobal.js'\nimport {\n getCollectionConfig,\n getGlobalConfig,\n sanitizePluginConfig,\n} from './lib/config.js'\n\nexport {\n DEFAULT_FIELD_NAMES,\n MULTILANG_GROUP_FIELD,\n MULTILANG_LANGUAGE_FIELD,\n MULTILANG_META_FIELD,\n} from './constants.js'\n\nexport {\n createMultilangHelpers,\n findGlobalByLanguageWithPayload,\n getDocumentTranslationsWithPayload,\n getDocumentTranslationWithPayload,\n getGroupTranslationsWithPayload,\n getMultilangDocumentLanguage,\n localizedSlugQuery,\n updateGlobalByLanguageWithPayload,\n withLanguage,\n} from './lib/data.js'\n\nexport type {\n FindGlobalByLanguageArgs,\n GetDocumentTranslationArgs,\n GetDocumentTranslationsArgs,\n LocalizedSlugQuery,\n LocalizedSlugQueryArgs,\n LocalizedSlugQueryStatus,\n MultilangDocumentLanguageArgs,\n MultilangHelpers,\n PayloadFactory,\n UpdateGlobalByLanguageArgs,\n} from './lib/data.js'\n\nexport type {\n MultilangCollectionOptions,\n MultilangFieldNames,\n MultilangGlobalOptions,\n MultilangLanguage,\n PayloadMultilangConfig,\n ResolvedMultilangGlobal,\n TranslationMap,\n TranslationState,\n} from './types.js'\n\nexport const payloadMultilang =\n (pluginOptions: PayloadMultilangConfig = { languages: [] }): Plugin =>\n (config: Config): Config => {\n if (pluginOptions.disabled) {\n return config\n }\n\n if (config.localization) {\n throw new Error(\n 'payload-multilang uses per-language documents and cannot run with Payload built-in localization enabled. Set localization: false and do not use localized: true fields for this plugin model.',\n )\n }\n\n const pluginConfig = sanitizePluginConfig(pluginOptions)\n\n const incomingCollections = config.collections || []\n const collections = incomingCollections.map(\n (collection): CollectionConfig => {\n const translatedCollection = getCollectionConfig(\n pluginConfig.collections,\n collection.slug,\n )\n\n if (!translatedCollection) {\n return collection\n }\n\n const translated = withTranslatedCollection({\n collection: translatedCollection,\n config: collection,\n })\n\n return {\n ...translated,\n endpoints: [\n ...(translated.endpoints || []),\n ...createTranslationEndpoints({\n collection: translatedCollection,\n collectionConfig: collection,\n }),\n ],\n }\n },\n )\n\n const incomingGlobals = config.globals || []\n const globals = incomingGlobals.map((globalConfig): GlobalConfig => {\n const translatedGlobal = getGlobalConfig(\n pluginConfig.globals,\n globalConfig.slug,\n )\n\n if (!translatedGlobal) {\n return globalConfig\n }\n\n return withTranslatedGlobal({\n config: globalConfig,\n global: translatedGlobal,\n })\n })\n\n return {\n ...config,\n admin: {\n ...config.admin,\n custom: {\n ...config.admin?.custom,\n payloadMultilang: {\n ...((config.admin?.custom as\n | { payloadMultilang?: Record<string, unknown> }\n | undefined)?.payloadMultilang || {}),\n defaultLanguage: pluginConfig.defaultLanguage,\n languages: pluginConfig.languages,\n },\n },\n },\n collections,\n globals,\n }\n }\n"],"names":["createTranslationEndpoints","withTranslatedCollection","withTranslatedGlobal","getCollectionConfig","getGlobalConfig","sanitizePluginConfig","DEFAULT_FIELD_NAMES","MULTILANG_GROUP_FIELD","MULTILANG_LANGUAGE_FIELD","MULTILANG_META_FIELD","createMultilangHelpers","findGlobalByLanguageWithPayload","getDocumentTranslationsWithPayload","getDocumentTranslationWithPayload","getGroupTranslationsWithPayload","getMultilangDocumentLanguage","localizedSlugQuery","updateGlobalByLanguageWithPayload","withLanguage","payloadMultilang","pluginOptions","languages","config","disabled","localization","Error","pluginConfig","incomingCollections","collections","map","collection","translatedCollection","slug","translated","endpoints","collectionConfig","incomingGlobals","globals","globalConfig","translatedGlobal","global","admin","custom","defaultLanguage"],"mappings":"AAIA,SAASA,0BAA0B,QAAQ,8BAA6B;AACxE,SAASC,wBAAwB,QAAQ,kCAAiC;AAC1E,SAASC,oBAAoB,QAAQ,8BAA6B;AAClE,SACEC,mBAAmB,EACnBC,eAAe,EACfC,oBAAoB,QACf,kBAAiB;AAExB,SACEC,mBAAmB,EACnBC,qBAAqB,EACrBC,wBAAwB,EACxBC,oBAAoB,QACf,iBAAgB;AAEvB,SACEC,sBAAsB,EACtBC,+BAA+B,EAC/BC,kCAAkC,EAClCC,iCAAiC,EACjCC,+BAA+B,EAC/BC,4BAA4B,EAC5BC,kBAAkB,EAClBC,iCAAiC,EACjCC,YAAY,QACP,gBAAe;AA0BtB,OAAO,MAAMC,mBACX,CAACC,gBAAwC;IAAEC,WAAW,EAAE;AAAC,CAAC,GAC1D,CAACC;QACC,IAAIF,cAAcG,QAAQ,EAAE;YAC1B,OAAOD;QACT;QAEA,IAAIA,OAAOE,YAAY,EAAE;YACvB,MAAM,IAAIC,MACR;QAEJ;QAEA,MAAMC,eAAerB,qBAAqBe;QAE1C,MAAMO,sBAAsBL,OAAOM,WAAW,IAAI,EAAE;QACpD,MAAMA,cAAcD,oBAAoBE,GAAG,CACzC,CAACC;YACC,MAAMC,uBAAuB5B,oBAC3BuB,aAAaE,WAAW,EACxBE,WAAWE,IAAI;YAGjB,IAAI,CAACD,sBAAsB;gBACzB,OAAOD;YACT;YAEA,MAAMG,aAAahC,yBAAyB;gBAC1C6B,YAAYC;gBACZT,QAAQQ;YACV;YAEA,OAAO;gBACL,GAAGG,UAAU;gBACbC,WAAW;uBACLD,WAAWC,SAAS,IAAI,EAAE;uBAC3BlC,2BAA2B;wBAC5B8B,YAAYC;wBACZI,kBAAkBL;oBACpB;iBACD;YACH;QACF;QAGF,MAAMM,kBAAkBd,OAAOe,OAAO,IAAI,EAAE;QAC5C,MAAMA,UAAUD,gBAAgBP,GAAG,CAAC,CAACS;YACnC,MAAMC,mBAAmBnC,gBACvBsB,aAAaW,OAAO,EACpBC,aAAaN,IAAI;YAGnB,IAAI,CAACO,kBAAkB;gBACrB,OAAOD;YACT;YAEA,OAAOpC,qBAAqB;gBAC1BoB,QAAQgB;gBACRE,QAAQD;YACV;QACF;QAEA,OAAO;YACL,GAAGjB,MAAM;YACTmB,OAAO;gBACL,GAAGnB,OAAOmB,KAAK;gBACfC,QAAQ;oBACN,GAAGpB,OAAOmB,KAAK,EAAEC,MAAM;oBACvBvB,kBAAkB;wBAChB,GAAI,AAACG,OAAOmB,KAAK,EAAEC,QAEHvB,oBAAoB,CAAC,CAAC;wBACtCwB,iBAAiBjB,aAAaiB,eAAe;wBAC7CtB,WAAWK,aAAaL,SAAS;oBACnC;gBACF;YACF;YACAO;YACAS;QACF;IACF,EAAC"}
@@ -11,18 +11,17 @@ type ConfigWithMultilang = Pick<Config, 'admin'> | Pick<Payload['config'], 'admi
11
11
  export declare const getLanguagesFromCustom: (custom: unknown) => MultilangLanguage[];
12
12
  /**
13
13
  * Returns the languages configured for payload-multilang.
14
- *
15
- * Pass `languages` or a Payload `config` object when you already have them. Otherwise the helper
16
- * resolves the cached Payload config from `@payload-config`.
17
14
  */
18
- export declare const getLanguages: ({ config, languages, }?: {
15
+ export declare const getLanguages: ({ config, languages, payload, }?: {
19
16
  config?: ConfigWithMultilang;
20
17
  languages?: MultilangLanguage[];
21
- }) => Promise<MultilangLanguage[]>;
22
- export declare const getDefaultLanguage: ({ config, languages, }: {
18
+ payload?: Payload;
19
+ }) => MultilangLanguage[];
20
+ export declare const getDefaultLanguage: ({ config, languages, payload, }: {
23
21
  config?: ConfigWithMultilang;
24
22
  languages?: MultilangLanguage[];
25
- }) => Promise<MultilangLanguage | undefined>;
23
+ payload?: Payload;
24
+ }) => MultilangLanguage | undefined;
26
25
  /**
27
26
  * Adds a language constraint to a Payload `where` query.
28
27
  *
@@ -30,6 +29,36 @@ export declare const getDefaultLanguage: ({ config, languages, }: {
30
29
  * payload-multilang language.
31
30
  */
32
31
  export declare const withLanguage: ({ fieldName, language, where, }: WithLanguageArgs) => Where;
32
+ export type MultilangDocumentLanguageArgs = {
33
+ data?: unknown;
34
+ doc?: unknown;
35
+ fallback?: string;
36
+ fieldName?: string;
37
+ };
38
+ /**
39
+ * Resolves a document's payload-multilang language from draft/form data first, then persisted doc data.
40
+ *
41
+ * Useful in Payload admin preview and livePreview URL builders where unsaved form data may already
42
+ * contain the selected language.
43
+ */
44
+ export declare const getMultilangDocumentLanguage: ({ data, doc, fallback, fieldName, }: MultilangDocumentLanguageArgs) => string | undefined;
45
+ export type LocalizedSlugQueryStatus = 'any' | 'draft' | 'published';
46
+ export type LocalizedSlugQueryArgs = {
47
+ fieldName?: string;
48
+ language: string;
49
+ slug: string;
50
+ slugFieldName?: string;
51
+ status?: LocalizedSlugQueryStatus;
52
+ where?: Where;
53
+ };
54
+ export type LocalizedSlugQuery = {
55
+ draft?: boolean;
56
+ where: Where;
57
+ };
58
+ /**
59
+ * Builds the common Payload Local API query for per-language documents addressed by slug.
60
+ */
61
+ export declare const localizedSlugQuery: ({ slug, fieldName, language, slugFieldName, status, where, }: LocalizedSlugQueryArgs) => LocalizedSlugQuery;
33
62
  export declare const getDocumentTranslationsWithPayload: <TDoc extends DocumentData = DocumentData>({ id, collection, fieldNames, overrideAccess, payload, req, }: {
34
63
  collection: CollectionSlug;
35
64
  fieldNames?: MultilangFieldNames;
@@ -48,26 +77,15 @@ export declare const getGroupTranslationsWithPayload: <TDoc extends DocumentData
48
77
  }) => Promise<TranslationState<TDoc>>;
49
78
  /**
50
79
  * Returns the source document and every document in the same translation group, keyed by language.
51
- *
52
- * The helper resolves Payload with the cached project config, so callers only need to pass the
53
- * collection slug and document id.
54
- */
55
- export declare const getDocumentTranslations: <TDoc extends DocumentData = DocumentData>({ id, collection, fieldNames, }: {
56
- collection: CollectionSlug;
57
- fieldNames?: MultilangFieldNames;
58
- id: number | string;
59
- }) => Promise<TranslationState<TDoc>>;
60
- /**
61
- * Returns one target-language document from the source document's translation group.
62
- *
63
- * The helper resolves Payload with the cached project config, so callers only need to pass the
64
- * collection slug, source document id, and target language code.
65
80
  */
66
- export declare const getDocumentTranslation: <TDoc extends DocumentData = DocumentData>({ id, collection, fieldNames, language, }: {
81
+ export declare const getDocumentTranslationWithPayload: <TDoc extends DocumentData = DocumentData>({ id, collection, fieldNames, language, overrideAccess, payload, req, }: {
67
82
  collection: CollectionSlug;
68
83
  fieldNames?: MultilangFieldNames;
69
84
  id: number | string;
70
85
  language: string;
86
+ overrideAccess?: boolean;
87
+ payload: Payload;
88
+ req?: PayloadRequest;
71
89
  }) => Promise<TDoc | undefined>;
72
90
  export declare const findGlobalByLanguageWithPayload: <TDoc extends DocumentData = DocumentData>({ slug, depth, language, overrideAccess, payload, req, }: {
73
91
  depth?: number;
@@ -86,17 +104,6 @@ export declare const updateGlobalByLanguageWithPayload: <TDoc extends DocumentDa
86
104
  req?: PayloadRequest;
87
105
  slug: GlobalSlug;
88
106
  }) => Promise<TDoc>;
89
- export declare const findGlobalByLanguage: <TDoc extends DocumentData = DocumentData>({ slug, depth, language, }: {
90
- depth?: number;
91
- language: string;
92
- slug: GlobalSlug;
93
- }) => Promise<TDoc>;
94
- export declare const updateGlobalByLanguage: <TDoc extends DocumentData = DocumentData>({ slug, data, depth, language, }: {
95
- data: Record<string, unknown>;
96
- depth?: number;
97
- language: string;
98
- slug: GlobalSlug;
99
- }) => Promise<TDoc>;
100
107
  export declare const duplicateDocumentData: ({ collection, doc, duplicateExcludeFields, fieldNames, targetLanguage, }: {
101
108
  collection: CollectionConfig;
102
109
  doc: DocumentData;
@@ -104,4 +111,35 @@ export declare const duplicateDocumentData: ({ collection, doc, duplicateExclude
104
111
  fieldNames: MultilangFieldNames;
105
112
  targetLanguage: string;
106
113
  }) => DocumentData;
114
+ export type PayloadFactory = () => Payload | Promise<Payload>;
115
+ export type GetDocumentTranslationsArgs = {
116
+ collection: CollectionSlug;
117
+ fieldNames?: MultilangFieldNames;
118
+ id: number | string;
119
+ overrideAccess?: boolean;
120
+ req?: PayloadRequest;
121
+ };
122
+ export type GetDocumentTranslationArgs = {
123
+ language: string;
124
+ } & GetDocumentTranslationsArgs;
125
+ export type FindGlobalByLanguageArgs = {
126
+ depth?: number;
127
+ language: string;
128
+ overrideAccess?: boolean;
129
+ req?: PayloadRequest;
130
+ slug: GlobalSlug;
131
+ };
132
+ export type UpdateGlobalByLanguageArgs = {
133
+ data: Record<string, unknown>;
134
+ } & FindGlobalByLanguageArgs;
135
+ export type MultilangHelpers = {
136
+ findGlobalByLanguage: <TDoc extends DocumentData = DocumentData>(args: FindGlobalByLanguageArgs) => Promise<TDoc>;
137
+ getDocumentTranslation: <TDoc extends DocumentData = DocumentData>(args: GetDocumentTranslationArgs) => Promise<TDoc | undefined>;
138
+ getDocumentTranslations: <TDoc extends DocumentData = DocumentData>(args: GetDocumentTranslationsArgs) => Promise<TranslationState<TDoc>>;
139
+ getLanguages: () => Promise<MultilangLanguage[]>;
140
+ updateGlobalByLanguage: <TDoc extends DocumentData = DocumentData>(args: UpdateGlobalByLanguageArgs) => Promise<TDoc>;
141
+ };
142
+ export declare const createMultilangHelpers: ({ getPayload, }: {
143
+ getPayload: PayloadFactory;
144
+ }) => MultilangHelpers;
107
145
  export {};
package/dist/lib/data.js CHANGED
@@ -1,5 +1,3 @@
1
- import payloadConfig from '@payload-config';
2
- import { getPayload } from 'payload';
3
1
  import { DEFAULT_FIELD_NAMES } from '../constants.js';
4
2
  export const asDocument = (value)=>value && typeof value === 'object' ? value : {};
5
3
  export const getStringValue = (value)=>typeof value === 'string' && value.length > 0 ? value : undefined;
@@ -29,25 +27,23 @@ export const getLanguagesFromCustom = (custom)=>{
29
27
  };
30
28
  /**
31
29
  * Returns the languages configured for payload-multilang.
32
- *
33
- * Pass `languages` or a Payload `config` object when you already have them. Otherwise the helper
34
- * resolves the cached Payload config from `@payload-config`.
35
- */ export const getLanguages = async ({ config, languages } = {})=>{
30
+ */ export const getLanguages = ({ config, languages, payload } = {})=>{
36
31
  if (languages) {
37
32
  return languages.map(normalizeLanguage).filter((language)=>language.code);
38
33
  }
34
+ if (payload) {
35
+ return getLanguagesFromCustom(payload.config.admin?.custom);
36
+ }
39
37
  if (config) {
40
38
  return getLanguagesFromCustom(config.admin?.custom);
41
39
  }
42
- const payload = await getPayload({
43
- config: payloadConfig
44
- });
45
- return getLanguagesFromCustom(payload.config.admin?.custom);
40
+ throw new Error('getLanguages requires languages, config, or payload. Use createMultilangHelpers({ getPayload }) for app-level convenience.');
46
41
  };
47
- export const getDefaultLanguage = async ({ config, languages })=>{
48
- const configuredLanguages = await getLanguages({
42
+ export const getDefaultLanguage = ({ config, languages, payload })=>{
43
+ const configuredLanguages = getLanguages({
49
44
  config,
50
- languages
45
+ languages,
46
+ payload
51
47
  });
52
48
  return configuredLanguages.find((language)=>language.isDefault) || configuredLanguages[0];
53
49
  };
@@ -72,6 +68,46 @@ export const getDefaultLanguage = async ({ config, languages })=>{
72
68
  ]
73
69
  };
74
70
  };
71
+ /**
72
+ * Resolves a document's payload-multilang language from draft/form data first, then persisted doc data.
73
+ *
74
+ * Useful in Payload admin preview and livePreview URL builders where unsaved form data may already
75
+ * contain the selected language.
76
+ */ export const getMultilangDocumentLanguage = ({ data, doc, fallback, fieldName = DEFAULT_FIELD_NAMES.language })=>getStringValue(asDocument(data)[fieldName]) || getStringValue(asDocument(doc)[fieldName]) || fallback;
77
+ /**
78
+ * Builds the common Payload Local API query for per-language documents addressed by slug.
79
+ */ export const localizedSlugQuery = ({ slug, fieldName = DEFAULT_FIELD_NAMES.language, language, slugFieldName = 'slug', status = 'published', where })=>{
80
+ const constraints = [
81
+ {
82
+ [slugFieldName]: {
83
+ equals: slug
84
+ }
85
+ },
86
+ {
87
+ [fieldName]: {
88
+ equals: language
89
+ }
90
+ }
91
+ ];
92
+ if (where) {
93
+ constraints.unshift(where);
94
+ }
95
+ if (status !== 'any') {
96
+ constraints.push({
97
+ _status: {
98
+ equals: status
99
+ }
100
+ });
101
+ }
102
+ return {
103
+ ...status === 'draft' ? {
104
+ draft: true
105
+ } : {},
106
+ where: constraints.length === 1 ? constraints[0] : {
107
+ and: constraints
108
+ }
109
+ };
110
+ };
75
111
  export const getDocumentTranslationsWithPayload = async ({ id, collection, fieldNames = DEFAULT_FIELD_NAMES, overrideAccess = true, payload, req })=>{
76
112
  const source = asDocument(await payload.findByID({
77
113
  id,
@@ -146,30 +182,14 @@ export const getGroupTranslationsWithPayload = async ({ collection, fieldNames =
146
182
  };
147
183
  /**
148
184
  * Returns the source document and every document in the same translation group, keyed by language.
149
- *
150
- * The helper resolves Payload with the cached project config, so callers only need to pass the
151
- * collection slug and document id.
152
- */ export const getDocumentTranslations = async ({ id, collection, fieldNames })=>{
153
- const payload = await getPayload({
154
- config: payloadConfig
155
- });
156
- return getDocumentTranslationsWithPayload({
185
+ */ export const getDocumentTranslationWithPayload = async ({ id, collection, fieldNames, language, overrideAccess = true, payload, req })=>{
186
+ const state = await getDocumentTranslationsWithPayload({
157
187
  id,
158
188
  collection,
159
189
  fieldNames,
160
- payload
161
- });
162
- };
163
- /**
164
- * Returns one target-language document from the source document's translation group.
165
- *
166
- * The helper resolves Payload with the cached project config, so callers only need to pass the
167
- * collection slug, source document id, and target language code.
168
- */ export const getDocumentTranslation = async ({ id, collection, fieldNames, language })=>{
169
- const state = await getDocumentTranslations({
170
- id,
171
- collection,
172
- fieldNames
190
+ overrideAccess,
191
+ payload,
192
+ req
173
193
  });
174
194
  return state.translations[language];
175
195
  };
@@ -257,29 +277,6 @@ export const updateGlobalByLanguageWithPayload = async ({ slug, data, depth, lan
257
277
  language
258
278
  });
259
279
  };
260
- export const findGlobalByLanguage = async ({ slug, depth, language })=>{
261
- const payload = await getPayload({
262
- config: payloadConfig
263
- });
264
- return findGlobalByLanguageWithPayload({
265
- slug,
266
- depth,
267
- language,
268
- payload
269
- });
270
- };
271
- export const updateGlobalByLanguage = async ({ slug, data, depth, language })=>{
272
- const payload = await getPayload({
273
- config: payloadConfig
274
- });
275
- return updateGlobalByLanguageWithPayload({
276
- slug,
277
- data,
278
- depth,
279
- language,
280
- payload
281
- });
282
- };
283
280
  const fieldAffectsData = (field)=>'name' in field && typeof field.name === 'string' && field.type !== 'ui';
284
281
  export const duplicateDocumentData = ({ collection, doc, duplicateExcludeFields, fieldNames, targetLanguage })=>{
285
282
  const excluded = new Set([
@@ -303,5 +300,38 @@ export const duplicateDocumentData = ({ collection, doc, duplicateExcludeFields,
303
300
  }
304
301
  return data;
305
302
  };
303
+ export const createMultilangHelpers = ({ getPayload })=>{
304
+ const resolvePayload = async ()=>getPayload();
305
+ return {
306
+ findGlobalByLanguage: async ({ slug, depth, language, overrideAccess, req })=>findGlobalByLanguageWithPayload({
307
+ slug,
308
+ depth,
309
+ language,
310
+ overrideAccess,
311
+ payload: await resolvePayload(),
312
+ req
313
+ }),
314
+ getDocumentTranslation: async (args)=>getDocumentTranslationWithPayload({
315
+ ...args,
316
+ payload: await resolvePayload()
317
+ }),
318
+ getDocumentTranslations: async (args)=>getDocumentTranslationsWithPayload({
319
+ ...args,
320
+ payload: await resolvePayload()
321
+ }),
322
+ getLanguages: async ()=>getLanguages({
323
+ payload: await resolvePayload()
324
+ }),
325
+ updateGlobalByLanguage: async ({ slug, data, depth, language, overrideAccess, req })=>updateGlobalByLanguageWithPayload({
326
+ slug,
327
+ data,
328
+ depth,
329
+ language,
330
+ overrideAccess,
331
+ payload: await resolvePayload(),
332
+ req
333
+ })
334
+ };
335
+ };
306
336
 
307
337
  //# sourceMappingURL=data.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/data.ts"],"sourcesContent":["import type {\n CollectionConfig,\n CollectionSlug,\n Config,\n Field,\n GlobalSlug,\n Payload,\n PayloadRequest,\n Where,\n} from 'payload'\n\nimport payloadConfig from '@payload-config'\nimport { getPayload } from 'payload'\n\nimport type {\n MultilangFieldNames,\n MultilangLanguage,\n TranslationState,\n WithLanguageArgs,\n} from '../types.js'\n\nimport { DEFAULT_FIELD_NAMES } from '../constants.js'\n\nexport type DocumentData = {\n id?: number | string\n} & Record<string, unknown>\n\nexport const asDocument = (value: unknown): DocumentData =>\n value && typeof value === 'object' ? (value as DocumentData) : {}\n\nexport const getStringValue = (value: unknown): string | undefined =>\n typeof value === 'string' && value.length > 0 ? value : undefined\n\nexport const getID = (value: unknown): number | string | undefined =>\n typeof value === 'number' || typeof value === 'string' ? value : undefined\n\nexport const normalizeLanguage = (doc: unknown): MultilangLanguage => {\n const data = asDocument(doc)\n const code = getStringValue(data.code)?.trim().toLowerCase() || ''\n\n return {\n id: getID(data.id),\n name: getStringValue(data.name)?.trim() || code,\n active: data.active !== false,\n code,\n direction: data.direction === 'rtl' ? 'rtl' : 'ltr',\n flagLabel: getStringValue(data.flagLabel),\n isDefault: data.isDefault === true,\n locale: getStringValue(data.locale),\n order: typeof data.order === 'number' ? data.order : 0,\n }\n}\n\ntype ConfigWithMultilang = Pick<Config, 'admin'> | Pick<Payload['config'], 'admin'>\n\nexport const getLanguagesFromCustom = (custom: unknown): MultilangLanguage[] => {\n const payloadMultilang = asDocument(asDocument(custom).payloadMultilang)\n const languages = payloadMultilang.languages\n\n if (!Array.isArray(languages)) {\n return []\n }\n\n return languages.map(normalizeLanguage).filter((language) => language.code)\n}\n\n/**\n * Returns the languages configured for payload-multilang.\n *\n * Pass `languages` or a Payload `config` object when you already have them. Otherwise the helper\n * resolves the cached Payload config from `@payload-config`.\n */\nexport const getLanguages = async ({\n config,\n languages,\n}: {\n config?: ConfigWithMultilang\n languages?: MultilangLanguage[]\n} = {}): Promise<MultilangLanguage[]> => {\n if (languages) {\n return languages.map(normalizeLanguage).filter((language) => language.code)\n }\n\n if (config) {\n return getLanguagesFromCustom(config.admin?.custom)\n }\n\n const payload = await getPayload({ config: payloadConfig })\n\n return getLanguagesFromCustom(payload.config.admin?.custom)\n}\n\nexport const getDefaultLanguage = async ({\n config,\n languages,\n}: {\n config?: ConfigWithMultilang\n languages?: MultilangLanguage[]\n}): Promise<MultilangLanguage | undefined> => {\n const configuredLanguages = await getLanguages({ config, languages })\n\n return configuredLanguages.find((language) => language.isDefault) || configuredLanguages[0]\n}\n\n/**\n * Adds a language constraint to a Payload `where` query.\n *\n * Use this when querying an enabled collection directly and you only want documents for one\n * payload-multilang language.\n */\nexport const withLanguage = ({\n fieldName = DEFAULT_FIELD_NAMES.language,\n language,\n where,\n}: WithLanguageArgs): Where => {\n const languageWhere: Where = {\n [fieldName]: {\n equals: language,\n },\n }\n\n if (!where) {\n return languageWhere\n }\n\n return {\n and: [where, languageWhere],\n }\n}\n\nexport const getDocumentTranslationsWithPayload = async <TDoc extends DocumentData = DocumentData>({\n id,\n collection,\n fieldNames = DEFAULT_FIELD_NAMES,\n overrideAccess = true,\n payload,\n req,\n}: {\n collection: CollectionSlug\n fieldNames?: MultilangFieldNames\n id: number | string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n}): Promise<TranslationState<TDoc>> => {\n const source = asDocument(\n await payload.findByID({\n id,\n collection,\n depth: 0,\n overrideAccess,\n req,\n }),\n ) as TDoc\n\n const group = getStringValue(source[fieldNames.group])\n const language = getStringValue(source[fieldNames.language])\n\n if (!group) {\n return {\n language,\n source,\n translations: language ? { [language]: source } : {},\n }\n }\n\n const result = await payload.find({\n collection,\n depth: 0,\n limit: 200,\n overrideAccess,\n req,\n where: {\n [fieldNames.group]: {\n equals: group,\n },\n },\n })\n\n const translations = result.docs.reduce<TranslationState<TDoc>['translations']>((acc, doc) => {\n const translation = asDocument(doc) as TDoc\n const code = getStringValue(translation[fieldNames.language])\n\n if (code) {\n acc[code] = translation\n }\n\n return acc\n }, {})\n\n return {\n group,\n language,\n source,\n translations,\n }\n}\n\nexport const getGroupTranslationsWithPayload = async <TDoc extends DocumentData = DocumentData>({\n collection,\n fieldNames = DEFAULT_FIELD_NAMES,\n group,\n overrideAccess = true,\n payload,\n req,\n}: {\n collection: CollectionSlug\n fieldNames?: MultilangFieldNames\n group: string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n}): Promise<TranslationState<TDoc>> => {\n const result = await payload.find({\n collection,\n depth: 0,\n limit: 200,\n overrideAccess,\n req,\n where: {\n [fieldNames.group]: {\n equals: group,\n },\n },\n })\n\n const translations = result.docs.reduce<TranslationState<TDoc>['translations']>((acc, doc) => {\n const translation = asDocument(doc) as TDoc\n const code = getStringValue(translation[fieldNames.language])\n\n if (code) {\n acc[code] = translation\n }\n\n return acc\n }, {})\n\n return {\n group,\n translations,\n }\n}\n\n/**\n * Returns the source document and every document in the same translation group, keyed by language.\n *\n * The helper resolves Payload with the cached project config, so callers only need to pass the\n * collection slug and document id.\n */\nexport const getDocumentTranslations = async <TDoc extends DocumentData = DocumentData>({\n id,\n collection,\n fieldNames,\n}: {\n collection: CollectionSlug\n fieldNames?: MultilangFieldNames\n id: number | string\n}): Promise<TranslationState<TDoc>> => {\n const payload = await getPayload({ config: payloadConfig })\n\n return getDocumentTranslationsWithPayload<TDoc>({\n id,\n collection,\n fieldNames,\n payload,\n })\n}\n\n/**\n * Returns one target-language document from the source document's translation group.\n *\n * The helper resolves Payload with the cached project config, so callers only need to pass the\n * collection slug, source document id, and target language code.\n */\nexport const getDocumentTranslation = async <TDoc extends DocumentData = DocumentData>({\n id,\n collection,\n fieldNames,\n language,\n}: {\n collection: CollectionSlug\n fieldNames?: MultilangFieldNames\n id: number | string\n language: string\n}): Promise<TDoc | undefined> => {\n const state = await getDocumentTranslations<TDoc>({\n id,\n collection,\n fieldNames,\n })\n\n return state.translations[language]\n}\n\nconst GLOBAL_SYSTEM_FIELDS = new Set(['_status', 'createdAt', 'globalType', 'id', 'updatedAt'])\n\nconst getGlobalPayloadMultilangCustom = ({\n slug,\n payload,\n}: {\n payload: Payload\n slug: GlobalSlug\n}): {\n defaultLanguage?: MultilangLanguage\n languages?: MultilangLanguage[]\n} => {\n const global = payload.config.globals.find((item) => item.slug === slug)\n const payloadMultilang = asDocument(asDocument(global?.admin?.custom).payloadMultilang)\n\n return payloadMultilang as {\n defaultLanguage?: MultilangLanguage\n languages?: MultilangLanguage[]\n }\n}\n\nconst validateGlobalLanguage = ({\n slug,\n language,\n payload,\n}: {\n language: string\n payload: Payload\n slug: GlobalSlug\n}): void => {\n const languages = getGlobalPayloadMultilangCustom({ slug, payload }).languages\n\n if (!Array.isArray(languages) || languages.length === 0) {\n return\n }\n\n if (!languages.some((candidate) => candidate.code === language)) {\n throw new Error(`Language \"${language}\" is not configured for ${slug}.`)\n }\n}\n\nconst getGlobalLanguageTab = <TDoc extends DocumentData = DocumentData>({\n doc,\n language,\n}: {\n doc: DocumentData\n language: string\n}): TDoc => {\n const languageData = asDocument(doc[language])\n const systemData = Object.entries(doc).reduce<DocumentData>((acc, [key, value]) => {\n if (GLOBAL_SYSTEM_FIELDS.has(key)) {\n acc[key] = value\n }\n\n return acc\n }, {})\n\n return {\n ...systemData,\n ...languageData,\n } as TDoc\n}\n\nexport const findGlobalByLanguageWithPayload = async <TDoc extends DocumentData = DocumentData>({\n slug,\n depth,\n language,\n overrideAccess = true,\n payload,\n req,\n}: {\n depth?: number\n language: string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n slug: GlobalSlug\n}): Promise<TDoc> => {\n validateGlobalLanguage({ slug, language, payload })\n\n const doc = asDocument(\n await payload.findGlobal({\n slug,\n depth,\n overrideAccess,\n req,\n } as never),\n )\n\n return getGlobalLanguageTab<TDoc>({\n doc,\n language,\n })\n}\n\nexport const updateGlobalByLanguageWithPayload = async <TDoc extends DocumentData = DocumentData>({\n slug,\n data,\n depth,\n language,\n overrideAccess = true,\n payload,\n req,\n}: {\n data: Record<string, unknown>\n depth?: number\n language: string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n slug: GlobalSlug\n}): Promise<TDoc> => {\n validateGlobalLanguage({ slug, language, payload })\n\n const currentDoc = asDocument(\n await payload.findGlobal({\n slug,\n depth: 0,\n overrideAccess,\n req,\n } as never),\n )\n const currentLanguageData = asDocument(currentDoc[language])\n\n const doc = asDocument(\n await payload.updateGlobal({\n slug,\n data: {\n [language]: {\n ...currentLanguageData,\n ...data,\n },\n },\n depth,\n overrideAccess,\n req,\n } as never),\n )\n\n return getGlobalLanguageTab<TDoc>({\n doc,\n language,\n })\n}\n\nexport const findGlobalByLanguage = async <TDoc extends DocumentData = DocumentData>({\n slug,\n depth,\n language,\n}: {\n depth?: number\n language: string\n slug: GlobalSlug\n}): Promise<TDoc> => {\n const payload = await getPayload({ config: payloadConfig })\n\n return findGlobalByLanguageWithPayload<TDoc>({\n slug,\n depth,\n language,\n payload,\n })\n}\n\nexport const updateGlobalByLanguage = async <TDoc extends DocumentData = DocumentData>({\n slug,\n data,\n depth,\n language,\n}: {\n data: Record<string, unknown>\n depth?: number\n language: string\n slug: GlobalSlug\n}): Promise<TDoc> => {\n const payload = await getPayload({ config: payloadConfig })\n\n return updateGlobalByLanguageWithPayload<TDoc>({\n slug,\n data,\n depth,\n language,\n payload,\n })\n}\n\nconst fieldAffectsData = (field: Field): field is { name: string } & Field =>\n 'name' in field && typeof field.name === 'string' && field.type !== 'ui'\n\nexport const duplicateDocumentData = ({\n collection,\n doc,\n duplicateExcludeFields,\n fieldNames,\n targetLanguage,\n}: {\n collection: CollectionConfig\n doc: DocumentData\n duplicateExcludeFields: string[]\n fieldNames: MultilangFieldNames\n targetLanguage: string\n}): DocumentData => {\n const excluded = new Set([\n fieldNames.group,\n fieldNames.language,\n fieldNames.meta,\n ...duplicateExcludeFields,\n ])\n\n const data: DocumentData = {}\n\n for (const field of collection.fields || []) {\n if (!fieldAffectsData(field) || excluded.has(field.name)) {\n continue\n }\n\n if (!(field.name in doc)) {\n continue\n }\n\n data[field.name] = doc[field.name]\n }\n\n if (typeof data.slug === 'string' && data.slug.length > 0) {\n data.slug = `${data.slug}-${targetLanguage}`\n }\n\n return data\n}\n"],"names":["payloadConfig","getPayload","DEFAULT_FIELD_NAMES","asDocument","value","getStringValue","length","undefined","getID","normalizeLanguage","doc","data","code","trim","toLowerCase","id","name","active","direction","flagLabel","isDefault","locale","order","getLanguagesFromCustom","custom","payloadMultilang","languages","Array","isArray","map","filter","language","getLanguages","config","admin","payload","getDefaultLanguage","configuredLanguages","find","withLanguage","fieldName","where","languageWhere","equals","and","getDocumentTranslationsWithPayload","collection","fieldNames","overrideAccess","req","source","findByID","depth","group","translations","result","limit","docs","reduce","acc","translation","getGroupTranslationsWithPayload","getDocumentTranslations","getDocumentTranslation","state","GLOBAL_SYSTEM_FIELDS","Set","getGlobalPayloadMultilangCustom","slug","global","globals","item","validateGlobalLanguage","some","candidate","Error","getGlobalLanguageTab","languageData","systemData","Object","entries","key","has","findGlobalByLanguageWithPayload","findGlobal","updateGlobalByLanguageWithPayload","currentDoc","currentLanguageData","updateGlobal","findGlobalByLanguage","updateGlobalByLanguage","fieldAffectsData","field","type","duplicateDocumentData","duplicateExcludeFields","targetLanguage","excluded","meta","fields"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/lib/data.ts"],"sourcesContent":["import type {\n CollectionConfig,\n CollectionSlug,\n Config,\n Field,\n GlobalSlug,\n Payload,\n PayloadRequest,\n Where,\n} from 'payload'\n\nimport type {\n MultilangFieldNames,\n MultilangLanguage,\n TranslationState,\n WithLanguageArgs,\n} from '../types.js'\n\nimport { DEFAULT_FIELD_NAMES } from '../constants.js'\n\nexport type DocumentData = {\n id?: number | string\n} & Record<string, unknown>\n\nexport const asDocument = (value: unknown): DocumentData =>\n value && typeof value === 'object' ? (value as DocumentData) : {}\n\nexport const getStringValue = (value: unknown): string | undefined =>\n typeof value === 'string' && value.length > 0 ? value : undefined\n\nexport const getID = (value: unknown): number | string | undefined =>\n typeof value === 'number' || typeof value === 'string' ? value : undefined\n\nexport const normalizeLanguage = (doc: unknown): MultilangLanguage => {\n const data = asDocument(doc)\n const code = getStringValue(data.code)?.trim().toLowerCase() || ''\n\n return {\n id: getID(data.id),\n name: getStringValue(data.name)?.trim() || code,\n active: data.active !== false,\n code,\n direction: data.direction === 'rtl' ? 'rtl' : 'ltr',\n flagLabel: getStringValue(data.flagLabel),\n isDefault: data.isDefault === true,\n locale: getStringValue(data.locale),\n order: typeof data.order === 'number' ? data.order : 0,\n }\n}\n\ntype ConfigWithMultilang = Pick<Config, 'admin'> | Pick<Payload['config'], 'admin'>\n\nexport const getLanguagesFromCustom = (custom: unknown): MultilangLanguage[] => {\n const payloadMultilang = asDocument(asDocument(custom).payloadMultilang)\n const languages = payloadMultilang.languages\n\n if (!Array.isArray(languages)) {\n return []\n }\n\n return languages.map(normalizeLanguage).filter((language) => language.code)\n}\n\n/**\n * Returns the languages configured for payload-multilang.\n */\nexport const getLanguages = ({\n config,\n languages,\n payload,\n}: {\n config?: ConfigWithMultilang\n languages?: MultilangLanguage[]\n payload?: Payload\n} = {}): MultilangLanguage[] => {\n if (languages) {\n return languages.map(normalizeLanguage).filter((language) => language.code)\n }\n\n if (payload) {\n return getLanguagesFromCustom(payload.config.admin?.custom)\n }\n\n if (config) {\n return getLanguagesFromCustom(config.admin?.custom)\n }\n\n throw new Error(\n 'getLanguages requires languages, config, or payload. Use createMultilangHelpers({ getPayload }) for app-level convenience.',\n )\n}\n\nexport const getDefaultLanguage = ({\n config,\n languages,\n payload,\n}: {\n config?: ConfigWithMultilang\n languages?: MultilangLanguage[]\n payload?: Payload\n}): MultilangLanguage | undefined => {\n const configuredLanguages = getLanguages({ config, languages, payload })\n\n return configuredLanguages.find((language) => language.isDefault) || configuredLanguages[0]\n}\n\n/**\n * Adds a language constraint to a Payload `where` query.\n *\n * Use this when querying an enabled collection directly and you only want documents for one\n * payload-multilang language.\n */\nexport const withLanguage = ({\n fieldName = DEFAULT_FIELD_NAMES.language,\n language,\n where,\n}: WithLanguageArgs): Where => {\n const languageWhere: Where = {\n [fieldName]: {\n equals: language,\n },\n }\n\n if (!where) {\n return languageWhere\n }\n\n return {\n and: [where, languageWhere],\n }\n}\n\nexport type MultilangDocumentLanguageArgs = {\n data?: unknown\n doc?: unknown\n fallback?: string\n fieldName?: string\n}\n\n/**\n * Resolves a document's payload-multilang language from draft/form data first, then persisted doc data.\n *\n * Useful in Payload admin preview and livePreview URL builders where unsaved form data may already\n * contain the selected language.\n */\nexport const getMultilangDocumentLanguage = ({\n data,\n doc,\n fallback,\n fieldName = DEFAULT_FIELD_NAMES.language,\n}: MultilangDocumentLanguageArgs): string | undefined =>\n getStringValue(asDocument(data)[fieldName]) ||\n getStringValue(asDocument(doc)[fieldName]) ||\n fallback\n\nexport type LocalizedSlugQueryStatus = 'any' | 'draft' | 'published'\n\nexport type LocalizedSlugQueryArgs = {\n fieldName?: string\n language: string\n slug: string\n slugFieldName?: string\n status?: LocalizedSlugQueryStatus\n where?: Where\n}\n\nexport type LocalizedSlugQuery = {\n draft?: boolean\n where: Where\n}\n\n/**\n * Builds the common Payload Local API query for per-language documents addressed by slug.\n */\nexport const localizedSlugQuery = ({\n slug,\n fieldName = DEFAULT_FIELD_NAMES.language,\n language,\n slugFieldName = 'slug',\n status = 'published',\n where,\n}: LocalizedSlugQueryArgs): LocalizedSlugQuery => {\n const constraints: Where[] = [\n {\n [slugFieldName]: {\n equals: slug,\n },\n },\n {\n [fieldName]: {\n equals: language,\n },\n },\n ]\n\n if (where) {\n constraints.unshift(where)\n }\n\n if (status !== 'any') {\n constraints.push({\n _status: {\n equals: status,\n },\n })\n }\n\n return {\n ...(status === 'draft' ? { draft: true } : {}),\n where:\n constraints.length === 1\n ? constraints[0]\n : {\n and: constraints,\n },\n }\n}\n\nexport const getDocumentTranslationsWithPayload = async <TDoc extends DocumentData = DocumentData>({\n id,\n collection,\n fieldNames = DEFAULT_FIELD_NAMES,\n overrideAccess = true,\n payload,\n req,\n}: {\n collection: CollectionSlug\n fieldNames?: MultilangFieldNames\n id: number | string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n}): Promise<TranslationState<TDoc>> => {\n const source = asDocument(\n await payload.findByID({\n id,\n collection,\n depth: 0,\n overrideAccess,\n req,\n }),\n ) as TDoc\n\n const group = getStringValue(source[fieldNames.group])\n const language = getStringValue(source[fieldNames.language])\n\n if (!group) {\n return {\n language,\n source,\n translations: language ? { [language]: source } : {},\n }\n }\n\n const result = await payload.find({\n collection,\n depth: 0,\n limit: 200,\n overrideAccess,\n req,\n where: {\n [fieldNames.group]: {\n equals: group,\n },\n },\n })\n\n const translations = result.docs.reduce<TranslationState<TDoc>['translations']>((acc, doc) => {\n const translation = asDocument(doc) as TDoc\n const code = getStringValue(translation[fieldNames.language])\n\n if (code) {\n acc[code] = translation\n }\n\n return acc\n }, {})\n\n return {\n group,\n language,\n source,\n translations,\n }\n}\n\nexport const getGroupTranslationsWithPayload = async <TDoc extends DocumentData = DocumentData>({\n collection,\n fieldNames = DEFAULT_FIELD_NAMES,\n group,\n overrideAccess = true,\n payload,\n req,\n}: {\n collection: CollectionSlug\n fieldNames?: MultilangFieldNames\n group: string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n}): Promise<TranslationState<TDoc>> => {\n const result = await payload.find({\n collection,\n depth: 0,\n limit: 200,\n overrideAccess,\n req,\n where: {\n [fieldNames.group]: {\n equals: group,\n },\n },\n })\n\n const translations = result.docs.reduce<TranslationState<TDoc>['translations']>((acc, doc) => {\n const translation = asDocument(doc) as TDoc\n const code = getStringValue(translation[fieldNames.language])\n\n if (code) {\n acc[code] = translation\n }\n\n return acc\n }, {})\n\n return {\n group,\n translations,\n }\n}\n\n/**\n * Returns the source document and every document in the same translation group, keyed by language.\n */\nexport const getDocumentTranslationWithPayload = async <TDoc extends DocumentData = DocumentData>({\n id,\n collection,\n fieldNames,\n language,\n overrideAccess = true,\n payload,\n req,\n}: {\n collection: CollectionSlug\n fieldNames?: MultilangFieldNames\n id: number | string\n language: string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n}): Promise<TDoc | undefined> => {\n const state = await getDocumentTranslationsWithPayload<TDoc>({\n id,\n collection,\n fieldNames,\n overrideAccess,\n payload,\n req,\n })\n\n return state.translations[language]\n}\n\nconst GLOBAL_SYSTEM_FIELDS = new Set(['_status', 'createdAt', 'globalType', 'id', 'updatedAt'])\n\nconst getGlobalPayloadMultilangCustom = ({\n slug,\n payload,\n}: {\n payload: Payload\n slug: GlobalSlug\n}): {\n defaultLanguage?: MultilangLanguage\n languages?: MultilangLanguage[]\n} => {\n const global = payload.config.globals.find((item) => item.slug === slug)\n const payloadMultilang = asDocument(asDocument(global?.admin?.custom).payloadMultilang)\n\n return payloadMultilang as {\n defaultLanguage?: MultilangLanguage\n languages?: MultilangLanguage[]\n }\n}\n\nconst validateGlobalLanguage = ({\n slug,\n language,\n payload,\n}: {\n language: string\n payload: Payload\n slug: GlobalSlug\n}): void => {\n const languages = getGlobalPayloadMultilangCustom({ slug, payload }).languages\n\n if (!Array.isArray(languages) || languages.length === 0) {\n return\n }\n\n if (!languages.some((candidate) => candidate.code === language)) {\n throw new Error(`Language \"${language}\" is not configured for ${slug}.`)\n }\n}\n\nconst getGlobalLanguageTab = <TDoc extends DocumentData = DocumentData>({\n doc,\n language,\n}: {\n doc: DocumentData\n language: string\n}): TDoc => {\n const languageData = asDocument(doc[language])\n const systemData = Object.entries(doc).reduce<DocumentData>((acc, [key, value]) => {\n if (GLOBAL_SYSTEM_FIELDS.has(key)) {\n acc[key] = value\n }\n\n return acc\n }, {})\n\n return {\n ...systemData,\n ...languageData,\n } as TDoc\n}\n\nexport const findGlobalByLanguageWithPayload = async <TDoc extends DocumentData = DocumentData>({\n slug,\n depth,\n language,\n overrideAccess = true,\n payload,\n req,\n}: {\n depth?: number\n language: string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n slug: GlobalSlug\n}): Promise<TDoc> => {\n validateGlobalLanguage({ slug, language, payload })\n\n const doc = asDocument(\n await payload.findGlobal({\n slug,\n depth,\n overrideAccess,\n req,\n } as never),\n )\n\n return getGlobalLanguageTab<TDoc>({\n doc,\n language,\n })\n}\n\nexport const updateGlobalByLanguageWithPayload = async <TDoc extends DocumentData = DocumentData>({\n slug,\n data,\n depth,\n language,\n overrideAccess = true,\n payload,\n req,\n}: {\n data: Record<string, unknown>\n depth?: number\n language: string\n overrideAccess?: boolean\n payload: Payload\n req?: PayloadRequest\n slug: GlobalSlug\n}): Promise<TDoc> => {\n validateGlobalLanguage({ slug, language, payload })\n\n const currentDoc = asDocument(\n await payload.findGlobal({\n slug,\n depth: 0,\n overrideAccess,\n req,\n } as never),\n )\n const currentLanguageData = asDocument(currentDoc[language])\n\n const doc = asDocument(\n await payload.updateGlobal({\n slug,\n data: {\n [language]: {\n ...currentLanguageData,\n ...data,\n },\n },\n depth,\n overrideAccess,\n req,\n } as never),\n )\n\n return getGlobalLanguageTab<TDoc>({\n doc,\n language,\n })\n}\n\nconst fieldAffectsData = (field: Field): field is { name: string } & Field =>\n 'name' in field && typeof field.name === 'string' && field.type !== 'ui'\n\nexport const duplicateDocumentData = ({\n collection,\n doc,\n duplicateExcludeFields,\n fieldNames,\n targetLanguage,\n}: {\n collection: CollectionConfig\n doc: DocumentData\n duplicateExcludeFields: string[]\n fieldNames: MultilangFieldNames\n targetLanguage: string\n}): DocumentData => {\n const excluded = new Set([\n fieldNames.group,\n fieldNames.language,\n fieldNames.meta,\n ...duplicateExcludeFields,\n ])\n\n const data: DocumentData = {}\n\n for (const field of collection.fields || []) {\n if (!fieldAffectsData(field) || excluded.has(field.name)) {\n continue\n }\n\n if (!(field.name in doc)) {\n continue\n }\n\n data[field.name] = doc[field.name]\n }\n\n if (typeof data.slug === 'string' && data.slug.length > 0) {\n data.slug = `${data.slug}-${targetLanguage}`\n }\n\n return data\n}\n\nexport type PayloadFactory = () => Payload | Promise<Payload>\n\nexport type GetDocumentTranslationsArgs = {\n collection: CollectionSlug\n fieldNames?: MultilangFieldNames\n id: number | string\n overrideAccess?: boolean\n req?: PayloadRequest\n}\n\nexport type GetDocumentTranslationArgs = {\n language: string\n} & GetDocumentTranslationsArgs\n\nexport type FindGlobalByLanguageArgs = {\n depth?: number\n language: string\n overrideAccess?: boolean\n req?: PayloadRequest\n slug: GlobalSlug\n}\n\nexport type UpdateGlobalByLanguageArgs = {\n data: Record<string, unknown>\n} & FindGlobalByLanguageArgs\n\nexport type MultilangHelpers = {\n findGlobalByLanguage: <TDoc extends DocumentData = DocumentData>(\n args: FindGlobalByLanguageArgs,\n ) => Promise<TDoc>\n getDocumentTranslation: <TDoc extends DocumentData = DocumentData>(\n args: GetDocumentTranslationArgs,\n ) => Promise<TDoc | undefined>\n getDocumentTranslations: <TDoc extends DocumentData = DocumentData>(\n args: GetDocumentTranslationsArgs,\n ) => Promise<TranslationState<TDoc>>\n getLanguages: () => Promise<MultilangLanguage[]>\n updateGlobalByLanguage: <TDoc extends DocumentData = DocumentData>(\n args: UpdateGlobalByLanguageArgs,\n ) => Promise<TDoc>\n}\n\nexport const createMultilangHelpers = ({\n getPayload,\n}: {\n getPayload: PayloadFactory\n}): MultilangHelpers => {\n const resolvePayload = async () => getPayload()\n\n return {\n findGlobalByLanguage: async <TDoc extends DocumentData = DocumentData>({\n slug,\n depth,\n language,\n overrideAccess,\n req,\n }: FindGlobalByLanguageArgs): Promise<TDoc> =>\n findGlobalByLanguageWithPayload<TDoc>({\n slug,\n depth,\n language,\n overrideAccess,\n payload: await resolvePayload(),\n req,\n }),\n\n getDocumentTranslation: async <TDoc extends DocumentData = DocumentData>(\n args: GetDocumentTranslationArgs,\n ): Promise<TDoc | undefined> =>\n getDocumentTranslationWithPayload<TDoc>({\n ...args,\n payload: await resolvePayload(),\n }),\n\n getDocumentTranslations: async <TDoc extends DocumentData = DocumentData>(\n args: GetDocumentTranslationsArgs,\n ): Promise<TranslationState<TDoc>> =>\n getDocumentTranslationsWithPayload<TDoc>({\n ...args,\n payload: await resolvePayload(),\n }),\n\n getLanguages: async (): Promise<MultilangLanguage[]> =>\n getLanguages({ payload: await resolvePayload() }),\n\n updateGlobalByLanguage: async <TDoc extends DocumentData = DocumentData>({\n slug,\n data,\n depth,\n language,\n overrideAccess,\n req,\n }: UpdateGlobalByLanguageArgs): Promise<TDoc> =>\n updateGlobalByLanguageWithPayload<TDoc>({\n slug,\n data,\n depth,\n language,\n overrideAccess,\n payload: await resolvePayload(),\n req,\n }),\n }\n}\n"],"names":["DEFAULT_FIELD_NAMES","asDocument","value","getStringValue","length","undefined","getID","normalizeLanguage","doc","data","code","trim","toLowerCase","id","name","active","direction","flagLabel","isDefault","locale","order","getLanguagesFromCustom","custom","payloadMultilang","languages","Array","isArray","map","filter","language","getLanguages","config","payload","admin","Error","getDefaultLanguage","configuredLanguages","find","withLanguage","fieldName","where","languageWhere","equals","and","getMultilangDocumentLanguage","fallback","localizedSlugQuery","slug","slugFieldName","status","constraints","unshift","push","_status","draft","getDocumentTranslationsWithPayload","collection","fieldNames","overrideAccess","req","source","findByID","depth","group","translations","result","limit","docs","reduce","acc","translation","getGroupTranslationsWithPayload","getDocumentTranslationWithPayload","state","GLOBAL_SYSTEM_FIELDS","Set","getGlobalPayloadMultilangCustom","global","globals","item","validateGlobalLanguage","some","candidate","getGlobalLanguageTab","languageData","systemData","Object","entries","key","has","findGlobalByLanguageWithPayload","findGlobal","updateGlobalByLanguageWithPayload","currentDoc","currentLanguageData","updateGlobal","fieldAffectsData","field","type","duplicateDocumentData","duplicateExcludeFields","targetLanguage","excluded","meta","fields","createMultilangHelpers","getPayload","resolvePayload","findGlobalByLanguage","getDocumentTranslation","args","getDocumentTranslations","updateGlobalByLanguage"],"mappings":"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"}
@@ -32,6 +32,8 @@ export default buildConfig({
32
32
  })
33
33
  ```
34
34
 
35
+ Payload built-in localization must stay disabled. This plugin models translations as separate documents linked by metadata fields, so `localized: true` fields are not the intended model. If `config.localization` is enabled, `payloadMultilang()` throws during config setup.
36
+
35
37
  ## Plugin Options
36
38
 
37
39
  | Option | Type | Description |
@@ -43,6 +45,20 @@ export default buildConfig({
43
45
  | `duplicateExcludeFields` | `string[]` | Extra top-level fields to skip when a new translation duplicates source document data. |
44
46
  | `disabled` | `boolean` | Returns the Payload config unchanged when true. Useful for temporary opt-out in specific environments. |
45
47
 
48
+ ## Content Model
49
+
50
+ Translated collection entries are independent Payload documents. The plugin adds hidden metadata fields:
51
+
52
+ ```ts
53
+ {
54
+ language: '_multilangLanguage',
55
+ group: '_multilangGroup',
56
+ meta: '_multilangMeta',
57
+ }
58
+ ```
59
+
60
+ Do not mark content fields as `localized: true` for this plugin model. Put translated text directly on each language document instead.
61
+
46
62
  ## Languages
47
63
 
48
64
  ```ts
@@ -144,7 +160,7 @@ payloadMultilang({
144
160
  })
145
161
  ```
146
162
 
147
- Read and update localized global values with `findGlobalByLanguage` and `updateGlobalByLanguage`.
163
+ Read and update localized global values with `findGlobalByLanguageWithPayload`, `updateGlobalByLanguageWithPayload`, or helpers from `createMultilangHelpers()`.
148
164
 
149
165
  ## Custom Field Names
150
166
 
@@ -190,3 +206,18 @@ payloadMultilang({
190
206
  ```
191
207
 
192
208
  When using custom field names, pass the same names to helpers that accept `fieldNames` or `fieldName`.
209
+
210
+ ## Slug Uniqueness
211
+
212
+ Because translations are separate documents, decide whether slugs are unique globally or per language.
213
+
214
+ If every translated document has a different slug, a normal `unique: true` slug field works. If translations may share the same slug across languages, do not use global `unique: true`; enforce uniqueness with a compound database index or a custom validation rule scoped to:
215
+
216
+ ```ts
217
+ {
218
+ _multilangLanguage: language,
219
+ slug,
220
+ }
221
+ ```
222
+
223
+ Use `localizedSlugQuery()` for common language + slug lookups in frontend routes.
package/docs/helpers.md CHANGED
@@ -2,33 +2,52 @@
2
2
 
3
3
  Import helpers from `@roxxel/payload-multilang`.
4
4
 
5
+ ## Request-Scoped Helpers
6
+
7
+ Use `WithPayload` helpers in endpoints, hooks, route handlers, and code running on behalf of a user.
8
+
5
9
  ```ts
6
10
  import {
7
- findGlobalByLanguage,
8
11
  findGlobalByLanguageWithPayload,
9
- getDocumentTranslation,
10
- getDocumentTranslations,
11
- getLanguages,
12
- updateGlobalByLanguage,
12
+ getDocumentTranslationWithPayload,
13
+ getDocumentTranslationsWithPayload,
14
+ getGroupTranslationsWithPayload,
13
15
  updateGlobalByLanguageWithPayload,
14
- withLanguage,
15
16
  } from '@roxxel/payload-multilang'
17
+
18
+ const state = await getDocumentTranslationsWithPayload({
19
+ collection: 'posts',
20
+ id: post.id,
21
+ payload: req.payload,
22
+ req,
23
+ overrideAccess: false,
24
+ })
16
25
  ```
17
26
 
18
- ## `getLanguages`
27
+ `overrideAccess` defaults to `true`, matching Payload Local API defaults. Set it to `false` and pass `req` when enforcing request user access.
28
+
29
+ ## `createMultilangHelpers`
19
30
 
20
- Returns the languages configured for the plugin.
31
+ Use a tiny app-owned module for convenience helpers.
21
32
 
22
33
  ```ts
23
- const languages = await getLanguages()
34
+ import { createMultilangHelpers } from '@roxxel/payload-multilang'
35
+ import { getPayload } from 'payload'
36
+ import config from '@payload-config'
37
+
38
+ export const multilang = createMultilangHelpers({
39
+ getPayload: () => getPayload({ config }),
40
+ })
24
41
  ```
25
42
 
26
- You can also pass known languages or a Payload config-like object.
43
+ The factory returns:
27
44
 
28
45
  ```ts
29
- const languages = await getLanguages({
30
- config: payload.config,
31
- })
46
+ multilang.getLanguages()
47
+ multilang.getDocumentTranslations(args)
48
+ multilang.getDocumentTranslation(args)
49
+ multilang.findGlobalByLanguage(args)
50
+ multilang.updateGlobalByLanguage(args)
32
51
  ```
33
52
 
34
53
  ## `withLanguage`
@@ -36,6 +55,8 @@ const languages = await getLanguages({
36
55
  Adds a language constraint to a Payload `where` query.
37
56
 
38
57
  ```ts
58
+ import { withLanguage } from '@roxxel/payload-multilang'
59
+
39
60
  const where = withLanguage({
40
61
  language: 'uk',
41
62
  where: {
@@ -65,37 +86,31 @@ Result:
65
86
  }
66
87
  ```
67
88
 
68
- Signature:
69
-
70
- ```ts
71
- withLanguage(args: {
72
- fieldName?: string
73
- language: string
74
- where?: Where
75
- }): Where
76
- ```
77
-
78
89
  Use `fieldName` if you configured a custom collection language field.
79
90
 
80
- ## `getDocumentTranslations`
91
+ ## Document Helpers
81
92
 
82
- Returns the source document and all linked translations for a localized collection document.
93
+ `getDocumentTranslationsWithPayload` returns the source document and all linked translations for a localized collection document.
83
94
 
84
95
  ```ts
85
- const state = await getDocumentTranslations<Post>({
96
+ const state = await getDocumentTranslationsWithPayload<Post>({
86
97
  collection: 'posts',
87
98
  id: post.id,
99
+ payload: req.payload,
100
+ req,
101
+ overrideAccess: false,
88
102
  })
89
103
  ```
90
104
 
91
- Signature:
105
+ `getDocumentTranslationWithPayload` returns one target-language document.
92
106
 
93
107
  ```ts
94
- getDocumentTranslations<TDoc>(args: {
95
- collection: CollectionSlug
96
- fieldNames?: MultilangFieldNames
97
- id: number | string
98
- }): Promise<TranslationState<TDoc>>
108
+ const ukrainianPost = await getDocumentTranslationWithPayload<Post>({
109
+ collection: 'posts',
110
+ id: post.id,
111
+ language: 'uk',
112
+ payload: req.payload,
113
+ })
99
114
  ```
100
115
 
101
116
  Return shape:
@@ -109,123 +124,82 @@ type TranslationState<TDoc = Record<string, unknown>> = {
109
124
  }
110
125
  ```
111
126
 
112
- ## `getDocumentTranslation`
127
+ ## Global Helpers
113
128
 
114
- Returns one target-language document from the source document's translation group.
129
+ Localized globals store one singleton document with one top-level tab per language. These helpers read or update one tab and return flat data for that language.
115
130
 
116
131
  ```ts
117
- const ukrainianPost = await getDocumentTranslation<Post>({
118
- collection: 'posts',
119
- id: post.id,
132
+ const settings = await findGlobalByLanguageWithPayload<SiteSettings>({
133
+ slug: 'site-settings',
120
134
  language: 'uk',
135
+ payload: req.payload,
136
+ req,
137
+ overrideAccess: false,
121
138
  })
122
- ```
123
-
124
- Signature:
125
-
126
- ```ts
127
- getDocumentTranslation<TDoc>(args: {
128
- collection: CollectionSlug
129
- fieldNames?: MultilangFieldNames
130
- id: number | string
131
- language: string
132
- }): Promise<TDoc | undefined>
133
- ```
134
-
135
- ## `findGlobalByLanguage`
136
-
137
- Reads one language from a localized global and returns flat data for that language.
138
139
 
139
- ```ts
140
- const settings = await findGlobalByLanguage<SiteSettings>({
140
+ await updateGlobalByLanguageWithPayload<SiteSettings>({
141
141
  slug: 'site-settings',
142
142
  language: 'uk',
143
+ payload: req.payload,
144
+ data: {
145
+ title: 'Updated title',
146
+ },
143
147
  })
144
148
  ```
145
149
 
146
- Signature:
147
-
148
- ```ts
149
- findGlobalByLanguage<TDoc>(args: {
150
- depth?: number
151
- language: string
152
- slug: GlobalSlug
153
- }): Promise<TDoc>
154
- ```
155
-
156
- ## `updateGlobalByLanguage`
150
+ ## Constants
157
151
 
158
- Updates one language in a localized global.
152
+ Default metadata field names are exported so apps do not need to hardcode them.
159
153
 
160
154
  ```ts
161
- const settings = await updateGlobalByLanguage<SiteSettings>({
162
- slug: 'site-settings',
163
- language: 'uk',
164
- data: {
165
- title: 'Ukrainian title',
166
- },
167
- })
155
+ import {
156
+ DEFAULT_FIELD_NAMES,
157
+ MULTILANG_GROUP_FIELD,
158
+ MULTILANG_LANGUAGE_FIELD,
159
+ MULTILANG_META_FIELD,
160
+ } from '@roxxel/payload-multilang'
168
161
  ```
169
162
 
170
- Signature:
163
+ Values:
171
164
 
172
165
  ```ts
173
- updateGlobalByLanguage<TDoc>(args: {
174
- data: Record<string, unknown>
175
- depth?: number
176
- language: string
177
- slug: GlobalSlug
178
- }): Promise<TDoc>
166
+ {
167
+ language: '_multilangLanguage',
168
+ group: '_multilangGroup',
169
+ meta: '_multilangMeta',
170
+ }
179
171
  ```
180
172
 
181
- The helper merges `data` into the selected language and preserves existing values in other languages.
182
-
183
- ## Global Helpers With Payload
173
+ ## Preview Language Helper
184
174
 
185
- Use these variants when you already have a Payload instance or need request-aware access control.
175
+ Use `getMultilangDocumentLanguage` in admin preview or livePreview URL builders. It checks unsaved form data first, then the persisted document.
186
176
 
187
177
  ```ts
188
- const settings = await findGlobalByLanguageWithPayload<SiteSettings>({
189
- slug: 'site-settings',
190
- language: 'uk',
191
- overrideAccess: false,
192
- payload: req.payload,
193
- req,
194
- })
178
+ import { getMultilangDocumentLanguage } from '@roxxel/payload-multilang'
195
179
 
196
- await updateGlobalByLanguageWithPayload<SiteSettings>({
197
- slug: 'site-settings',
198
- language: 'uk',
199
- data: {
200
- title: 'Updated title',
201
- },
202
- overrideAccess: false,
203
- payload: req.payload,
204
- req,
180
+ const language = getMultilangDocumentLanguage({
181
+ data,
182
+ doc,
183
+ fallback: 'en',
205
184
  })
206
185
  ```
207
186
 
208
- Signatures:
187
+ ## Localized Slug Query
188
+
189
+ `localizedSlugQuery` builds the common Local API query for language + slug + status.
209
190
 
210
191
  ```ts
211
- findGlobalByLanguageWithPayload<TDoc>(args: {
212
- depth?: number
213
- language: string
214
- overrideAccess?: boolean
215
- payload: Payload
216
- req?: PayloadRequest
217
- slug: GlobalSlug
218
- }): Promise<TDoc>
219
-
220
- updateGlobalByLanguageWithPayload<TDoc>(args: {
221
- data: Record<string, unknown>
222
- depth?: number
223
- language: string
224
- overrideAccess?: boolean
225
- payload: Payload
226
- req?: PayloadRequest
227
- slug: GlobalSlug
228
- }): Promise<TDoc>
192
+ import { localizedSlugQuery } from '@roxxel/payload-multilang'
193
+
194
+ const { docs } = await payload.find({
195
+ collection: 'posts',
196
+ limit: 1,
197
+ ...localizedSlugQuery({
198
+ slug,
199
+ language: 'uk',
200
+ status: 'published',
201
+ }),
202
+ })
229
203
  ```
230
204
 
231
- `overrideAccess` defaults to `true`. Set it to `false` when operating on behalf of a user.
205
+ Use `status: 'draft'` to include `draft: true` and `_status = draft`, or `status: 'any'` to omit `_status`.
package/docs/usage.md CHANGED
@@ -27,14 +27,69 @@ payloadMultilang({
27
27
 
28
28
  Editors can then choose the document language in the sidebar and create or connect translations from the collection list, document sidebar, or `Translations` edit tab.
29
29
 
30
+ ## App-Level Convenience Helpers
31
+
32
+ Create one app-owned helper module when you want short helper calls in RSCs, route handlers, or other trusted server code.
33
+
34
+ ```ts
35
+ // src/lib/multilang.ts
36
+ import { createMultilangHelpers } from '@roxxel/payload-multilang'
37
+ import { getPayload } from 'payload'
38
+ import config from '@payload-config'
39
+
40
+ export const multilang = createMultilangHelpers({
41
+ getPayload: () => getPayload({ config }),
42
+ })
43
+ ```
44
+
45
+ Then use it from your app:
46
+
47
+ ```tsx
48
+ import { multilang } from '@/lib/multilang'
49
+
50
+ export const LanguageSwitcher = async ({ id }: { id: string }) => {
51
+ const state = await multilang.getDocumentTranslations<{ id: string; slug?: string }>({
52
+ collection: 'posts',
53
+ id,
54
+ })
55
+
56
+ return (
57
+ <nav>
58
+ {Object.entries(state.translations).map(([language, doc]) => (
59
+ <a href={`/${language}/posts/${doc.slug || doc.id}`} key={language}>
60
+ {language.toUpperCase()}
61
+ </a>
62
+ ))}
63
+ </nav>
64
+ )
65
+ }
66
+ ```
67
+
68
+ ## Request-Scoped Code
69
+
70
+ Use `WithPayload` helpers when you already have `req.payload` or need user-scoped access control.
71
+
72
+ ```ts
73
+ import { getDocumentTranslationsWithPayload } from '@roxxel/payload-multilang'
74
+
75
+ export const getPostTranslations = async (req: PayloadRequest, id: string) =>
76
+ getDocumentTranslationsWithPayload({
77
+ collection: 'posts',
78
+ id,
79
+ payload: req.payload,
80
+ req,
81
+ overrideAccess: false,
82
+ })
83
+ ```
84
+
30
85
  ## Query Documents for One Language
31
86
 
32
87
  Use `withLanguage()` with the Local API.
33
88
 
34
89
  ```ts
90
+ import { withLanguage } from '@roxxel/payload-multilang'
35
91
  import { getPayload } from 'payload'
36
92
  import config from '@payload-config'
37
- import { withLanguage } from '@roxxel/payload-multilang'
38
93
 
39
94
  export const getPosts = async (language: string) => {
40
95
  const payload = await getPayload({ config })
@@ -73,62 +128,64 @@ const where = withLanguage({
73
128
  })
74
129
  ```
75
130
 
76
- ## Get Translations for a Document
131
+ ## Localized Slug Lookups
77
132
 
78
- Use `getDocumentTranslations()` when you have a document ID and need every linked translation.
133
+ Use `localizedSlugQuery()` for the common language + slug + status lookup.
79
134
 
80
135
  ```ts
81
- import { getDocumentTranslations } from '@roxxel/payload-multilang'
136
+ import { localizedSlugQuery } from '@roxxel/payload-multilang'
82
137
 
83
- const state = await getDocumentTranslations({
138
+ const { docs } = await payload.find({
84
139
  collection: 'posts',
85
- id: post.id,
140
+ limit: 1,
141
+ ...localizedSlugQuery({
142
+ slug: 'hello-world',
143
+ language: 'uk',
144
+ status: 'published',
145
+ }),
86
146
  })
87
-
88
- const english = state.translations.en
89
- const ukrainian = state.translations.uk
90
147
  ```
91
148
 
92
- The returned object is keyed by language code:
149
+ For draft previews:
93
150
 
94
151
  ```ts
95
- type TranslationState<TDoc> = {
96
- group?: string
97
- language?: string
98
- source?: TDoc
99
- translations: Record<string, TDoc>
100
- }
101
- ```
102
-
103
- Use `getDocumentTranslation()` when you only need one target language.
104
-
105
- ```ts
106
- import { getDocumentTranslation } from '@roxxel/payload-multilang'
152
+ const query = localizedSlugQuery({
153
+ slug,
154
+ language,
155
+ status: 'draft',
156
+ })
107
157
 
108
- const ukrainianPost = await getDocumentTranslation({
158
+ await payload.find({
109
159
  collection: 'posts',
110
- id: post.id,
111
- language: 'uk',
160
+ limit: 1,
161
+ ...query,
112
162
  })
113
163
  ```
114
164
 
115
- ## Access Control in Server Code
165
+ ## Admin Preview URLs
116
166
 
117
- The document translation helpers resolve Payload from `@payload-config` and use the default Local API access behavior. When you need user-scoped collection reads, query with Payload directly and pass `overrideAccess: false`.
167
+ Use `getMultilangDocumentLanguage()` to derive the selected language from unsaved form data first, then the saved document.
118
168
 
119
169
  ```ts
120
- const { docs } = await req.payload.find({
121
- collection: 'posts',
122
- overrideAccess: false,
123
- req,
124
- where: withLanguage({
125
- language: 'uk',
126
- }),
127
- })
170
+ import { getMultilangDocumentLanguage } from '@roxxel/payload-multilang'
171
+
172
+ export const Posts = {
173
+ slug: 'posts',
174
+ admin: {
175
+ preview: ({ data, doc }) => {
176
+ const language = getMultilangDocumentLanguage({
177
+ data,
178
+ doc,
179
+ fallback: 'en',
180
+ })
181
+
182
+ return `/${language}/posts/${data?.slug || doc?.slug}`
183
+ },
184
+ },
185
+ fields: [],
186
+ }
128
187
  ```
129
188
 
130
- Use the global `WithPayload` helpers when you need request-aware access control for localized globals.
131
-
132
189
  ## Enable a Global
133
190
 
134
191
  Add the global slug to the plugin config.
@@ -142,44 +199,38 @@ payloadMultilang({
142
199
  })
143
200
  ```
144
201
 
145
- Then read one language as flat data:
202
+ Read one language as flat data:
146
203
 
147
204
  ```ts
148
- import { findGlobalByLanguage } from '@roxxel/payload-multilang'
149
-
150
- const settings = await findGlobalByLanguage({
205
+ const settings = await multilang.findGlobalByLanguage({
151
206
  slug: 'site-settings',
152
207
  language: 'uk',
153
208
  })
154
-
155
- console.log(settings.title)
156
209
  ```
157
210
 
158
- Update one language without replacing other language values:
211
+ In request-scoped code:
159
212
 
160
213
  ```ts
161
- import { updateGlobalByLanguage } from '@roxxel/payload-multilang'
214
+ import { findGlobalByLanguageWithPayload } from '@roxxel/payload-multilang'
162
215
 
163
- await updateGlobalByLanguage({
216
+ const settings = await findGlobalByLanguageWithPayload({
164
217
  slug: 'site-settings',
165
218
  language: 'uk',
166
- data: {
167
- title: 'Ukrainian title',
168
- },
219
+ payload: req.payload,
220
+ req,
221
+ overrideAccess: false,
169
222
  })
170
223
  ```
171
224
 
172
- In request-scoped code:
225
+ Update one language without replacing other language values:
173
226
 
174
227
  ```ts
175
- import { findGlobalByLanguageWithPayload } from '@roxxel/payload-multilang'
176
-
177
- const settings = await findGlobalByLanguageWithPayload({
228
+ await multilang.updateGlobalByLanguage({
178
229
  slug: 'site-settings',
179
230
  language: 'uk',
180
- overrideAccess: false,
181
- payload: req.payload,
182
- req,
231
+ data: {
232
+ title: 'Ukrainian title',
233
+ },
183
234
  })
184
235
  ```
185
236
 
@@ -228,38 +279,6 @@ payloadMultilang({
228
279
  })
229
280
  ```
230
281
 
231
- ## Build Language Switchers
232
-
233
- Use `getDocumentTranslations()` to render links to available translations.
234
-
235
- ```tsx
236
- import { getDocumentTranslations } from '@roxxel/payload-multilang'
237
-
238
- export const LanguageSwitcher = async ({ collection, id }: { collection: 'posts'; id: string }) => {
239
- type PostLink = {
240
- id?: number | string
241
- slug?: string
242
- }
243
-
244
- const state = await getDocumentTranslations<PostLink>({
245
- collection,
246
- id,
247
- })
248
-
249
- return (
250
- <nav>
251
- {Object.entries(state.translations).map(([language, doc]) => (
252
- <a href={`/${language}/posts/${doc.slug || doc.id}`} key={language}>
253
- {language.toUpperCase()}
254
- </a>
255
- ))}
256
- </nav>
257
- )
258
- }
259
- ```
260
-
261
- Adjust the URL shape to match your frontend routing.
262
-
263
282
  ## Generate Types
264
283
 
265
284
  After enabling collections, globals, or changing field names, regenerate Payload types.
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@roxxel/payload-multilang",
3
- "version": "0.0.2",
3
+ "version": "0.0.5",
4
4
  "description": "Polylang-style document localization for Payload CMS",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/roxxel/payload-multilang.git"
9
+ },
6
10
  "type": "module",
7
11
  "exports": {
8
12
  ".": {
@@ -1,2 +0,0 @@
1
-
2
- //# sourceMappingURL=payload-config.d.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/payload-config.d.ts"],"names":[],"mappings":""}