@focus-reactive/payload-plugin-translator 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -8
- package/dist/client/entities/translation/api/mutations/useTranslateField.d.ts +22 -0
- package/dist/client/entities/translation/api/mutations/useTranslateField.js +31 -0
- package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.d.ts +3 -3
- package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.js +15 -13
- package/dist/client/features/collection-translation-popup/ui/CollectionTranslationPopup.d.ts +2 -2
- package/dist/client/features/collection-translation-popup/ui/CollectionTranslationPopup.js +10 -10
- package/dist/client/features/open-document-translation-popup/ui/OpenDocumentTranslationPopup.d.ts +1 -1
- package/dist/client/features/open-document-translation-popup/ui/OpenDocumentTranslationPopup.js +9 -9
- package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.d.ts +2 -2
- package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.js +10 -8
- package/dist/client/shared/lib/assets/icons/SendIcon.d.ts +1 -0
- package/dist/client/shared/lib/assets/icons/SendIcon.js +17 -0
- package/dist/client/shared/ui/Button/Button.d.ts +2 -1
- package/dist/client/shared/ui/Button/Button.js +1 -1
- package/dist/client/shared/ui/Button/styles.module.scss +26 -3
- package/dist/client/widgets/translate-field-control/index.d.ts +1 -0
- package/dist/client/widgets/translate-field-control/index.js +3 -0
- package/dist/client/widgets/translate-field-control/ui/TranslateFieldControl.d.ts +26 -0
- package/dist/client/widgets/translate-field-control/ui/TranslateFieldControl.export.d.ts +13 -0
- package/dist/client/widgets/translate-field-control/ui/TranslateFieldControl.export.js +14 -0
- package/dist/client/widgets/translate-field-control/ui/TranslateFieldControl.js +204 -0
- package/dist/client/widgets/translate-field-control/ui/styles.module.scss +48 -0
- package/dist/field-actions.d.ts +25 -0
- package/dist/field-actions.js +23 -0
- package/dist/field-config.d.ts +26 -26
- package/dist/field-config.js +14 -37
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +3 -1
- package/dist/server/features/translate-field/handler.d.ts +17 -0
- package/dist/server/features/translate-field/handler.js +85 -0
- package/dist/server/features/translate-field/index.d.ts +6 -0
- package/dist/server/features/translate-field/index.js +5 -0
- package/dist/server/features/translate-field/model.d.ts +62 -0
- package/dist/server/features/translate-field/model.js +26 -0
- package/dist/server/features/translate-field/resolveFieldSubtree.d.ts +15 -7
- package/dist/server/features/translate-field/resolveFieldSubtree.js +14 -8
- package/dist/server/features/translate-field/route.d.ts +12 -0
- package/dist/server/features/translate-field/route.js +18 -0
- package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +6 -0
- package/dist/server/modules/translation-levels/PluginConfigBuilder.js +4 -0
- package/dist/server/modules/translation-levels/fieldLevel.d.ts +23 -0
- package/dist/server/modules/translation-levels/fieldLevel.js +37 -0
- package/dist/server/modules/translation-levels/index.d.ts +1 -0
- package/dist/server/modules/translation-levels/index.js +1 -0
- package/dist/server/modules/translation-levels/types.d.ts +6 -1
- package/dist/server/modules/translation-levels/types.js +0 -1
- package/dist/server/modules/translation-pipeline/stages/data-reconciler/DataReconciler.js +10 -4
- package/dist/server/modules/translation-pipeline/stages/data-reconciler/DataReconciler.stage.d.ts +1 -1
- package/dist/server/modules/translation-pipeline/stages/data-reconciler/DataReconciler.stage.js +1 -1
- package/dist/server/modules/translation-pipeline/stages/field-collector/FieldChunkCollector.d.ts +5 -0
- package/dist/server/modules/translation-pipeline/stages/field-collector/FieldChunkCollector.js +13 -4
- package/dist/server/modules/translation-providers/OpenAITranslation.provider.d.ts +17 -0
- package/dist/server/modules/translation-providers/OpenAITranslation.provider.js +8 -2
- package/dist/server/shared/field-config/types.js +1 -1
- package/dist/server/shared/field-traversal/findFieldByPath.d.ts +25 -10
- package/dist/server/shared/field-traversal/findFieldByPath.js +59 -14
- package/dist/server/shared/field-traversal/index.d.ts +1 -1
- package/dist/server/shared/field-traversal/index.js +1 -1
- package/dist/server/shared/field-traversal/kernel.d.ts +22 -0
- package/dist/server/shared/field-traversal/kernel.js +27 -0
- package/package.json +2 -2
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
import { classifyField, tabScopes } from "./kernel";
|
|
1
|
+
import { classifyField, resolveBlockFields, tabScopes } from "./kernel";
|
|
2
|
+
const isIndexSegment = (segment)=>/^\d+$/u.test(segment);
|
|
3
|
+
/** Safely read `key` off an object/array; `undefined` for non-objects (arrays index by string key). */ const childData = (data, key)=>data != null && typeof data === "object" ? data[key] : undefined;
|
|
2
4
|
/**
|
|
3
5
|
* Navigate a field schema by a path of segment NAMES, descending one matching branch at a time
|
|
4
6
|
* with early-exit (targeted navigation, not an exhaustive walk). Presentational containers
|
|
5
7
|
* (`row`/`collapsible`/unnamed `group`) and unnamed tabs are transparent — searched in the same
|
|
6
|
-
* path scope. Built on {@link classifyField} / {@link tabScopes}
|
|
7
|
-
* in one place.
|
|
8
|
+
* path scope. Built on {@link classifyField} / {@link tabScopes} / {@link resolveBlockFields} so
|
|
9
|
+
* the structural dispatch lives in one place.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
+
* **Array indices and `blocks`.** Array element configs are shared, so a numeric segment after an
|
|
12
|
+
* array name selects the data item but not the schema (the schema continues into `array.fields`).
|
|
13
|
+
* A `blocks` field is polymorphic — which block (and thus which fields) sits at an index lives in
|
|
14
|
+
* the *data*, not the schema. So to descend through `blocks` the caller must pass the document
|
|
15
|
+
* `data` and keep the element index in the path: this function reads `data[name][index].blockType`
|
|
16
|
+
* and resolves the matching block via {@link resolveBlockFields}. Without `data` (or with a
|
|
17
|
+
* non-indexed path), descending through `blocks` returns `inside-blocks`.
|
|
11
18
|
*
|
|
12
19
|
* @param fields - The schema level to search.
|
|
13
|
-
* @param segments - Remaining path segments
|
|
20
|
+
* @param segments - Remaining path segments to match, head-first. Keep array/block element indices
|
|
21
|
+
* in the path when navigating with `data`.
|
|
22
|
+
* @param data - The document (sub)tree aligned with `fields`, used only to disambiguate `blocks`.
|
|
23
|
+
* Omit for schema-only navigation.
|
|
14
24
|
* @returns A {@link FieldPathResult}.
|
|
15
25
|
* @public
|
|
16
|
-
*/ export function findFieldByPath(fields, segments) {
|
|
26
|
+
*/ export function findFieldByPath(fields, segments, data) {
|
|
17
27
|
const [head, ...rest] = segments;
|
|
18
28
|
if (head === undefined) return {
|
|
19
29
|
status: "not-found"
|
|
@@ -28,10 +38,10 @@ import { classifyField, tabScopes } from "./kernel";
|
|
|
28
38
|
if (scope.tab.name === head) {
|
|
29
39
|
return rest.length === 0 ? {
|
|
30
40
|
status: "container"
|
|
31
|
-
} : findFieldByPath(scope.tab.fields, rest);
|
|
41
|
+
} : findFieldByPath(scope.tab.fields, rest, childData(data, head));
|
|
32
42
|
}
|
|
33
43
|
} else {
|
|
34
|
-
const found = findFieldByPath(scope.fields, segments); // unnamed tab → same path scope
|
|
44
|
+
const found = findFieldByPath(scope.fields, segments, data); // unnamed tab → same path + data scope
|
|
35
45
|
if (found.status !== "not-found") return found;
|
|
36
46
|
}
|
|
37
47
|
}
|
|
@@ -39,28 +49,63 @@ import { classifyField, tabScopes } from "./kernel";
|
|
|
39
49
|
}
|
|
40
50
|
case "transparent":
|
|
41
51
|
{
|
|
42
|
-
const found = findFieldByPath(structure.fields, segments); // row/collapsible/unnamed group → same scope
|
|
52
|
+
const found = findFieldByPath(structure.fields, segments, data); // row/collapsible/unnamed group → same scope
|
|
43
53
|
if (found.status !== "not-found") return found;
|
|
44
54
|
break;
|
|
45
55
|
}
|
|
46
56
|
case "presentational":
|
|
47
57
|
break;
|
|
48
58
|
case "group":
|
|
49
|
-
case "array":
|
|
50
59
|
{
|
|
51
60
|
if (structure.name !== head) break;
|
|
52
61
|
return rest.length === 0 ? {
|
|
53
62
|
status: "container"
|
|
54
|
-
} : findFieldByPath(structure.fields, rest);
|
|
63
|
+
} : findFieldByPath(structure.fields, rest, childData(data, head));
|
|
64
|
+
}
|
|
65
|
+
case "array":
|
|
66
|
+
{
|
|
67
|
+
if (structure.name !== head) break;
|
|
68
|
+
if (rest.length === 0) return {
|
|
69
|
+
status: "container"
|
|
70
|
+
};
|
|
71
|
+
// A localized array is an independent per-locale structure — its element index is not a
|
|
72
|
+
// stable identity across locales, so refuse to navigate THROUGH it by path.
|
|
73
|
+
if (structure.field.localized) return {
|
|
74
|
+
status: "localized-list-ancestor"
|
|
75
|
+
};
|
|
76
|
+
// A numeric segment selects the data element; the schema is shared across elements.
|
|
77
|
+
const [next, ...tail] = rest;
|
|
78
|
+
if (isIndexSegment(next)) {
|
|
79
|
+
return tail.length === 0 ? {
|
|
80
|
+
status: "container"
|
|
81
|
+
} : findFieldByPath(structure.fields, tail, childData(childData(data, head), next));
|
|
82
|
+
}
|
|
83
|
+
return findFieldByPath(structure.fields, rest, childData(data, head));
|
|
55
84
|
}
|
|
56
85
|
case "blocks":
|
|
57
86
|
{
|
|
58
87
|
if (structure.name !== head) break;
|
|
59
|
-
|
|
88
|
+
if (rest.length === 0) return {
|
|
60
89
|
status: "container"
|
|
61
|
-
}
|
|
90
|
+
};
|
|
91
|
+
// A localized blocks field is an independent per-locale structure — its element index is
|
|
92
|
+
// not a stable identity across locales, so refuse to navigate THROUGH it by path.
|
|
93
|
+
if (structure.field.localized) return {
|
|
94
|
+
status: "localized-list-ancestor"
|
|
95
|
+
};
|
|
96
|
+
// Polymorphic: need the element index + its data to pick the block schema by `blockType`.
|
|
97
|
+
const [next, ...tail] = rest;
|
|
98
|
+
if (!isIndexSegment(next)) return {
|
|
99
|
+
status: "inside-blocks"
|
|
100
|
+
};
|
|
101
|
+
const item = childData(childData(data, head), next);
|
|
102
|
+
const blockFields = resolveBlockFields(structure.field, item);
|
|
103
|
+
if (!blockFields) return {
|
|
62
104
|
status: "inside-blocks"
|
|
63
105
|
};
|
|
106
|
+
return tail.length === 0 ? {
|
|
107
|
+
status: "container"
|
|
108
|
+
} : findFieldByPath(blockFields, tail, item);
|
|
64
109
|
}
|
|
65
110
|
case "leaf":
|
|
66
111
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { findFieldByPath } from "./findFieldByPath";
|
|
2
2
|
export type { FieldPathResult } from "./findFieldByPath";
|
|
3
|
-
export { classifyField, resolveBlockFields, tabScopes } from "./kernel";
|
|
3
|
+
export { classifyField, matchElementById, resolveBlockFields, tabScopes } from "./kernel";
|
|
4
4
|
export type { ChildCursor, ChildOutput, ContainerInfo, FieldStructure, FieldWalker, LeafField, TabScope, WalkSignal } from "./types";
|
|
5
5
|
export { walkFields } from "./walkFields";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { findFieldByPath } from "./findFieldByPath";
|
|
2
|
-
export { classifyField, resolveBlockFields, tabScopes } from "./kernel";
|
|
2
|
+
export { classifyField, matchElementById, resolveBlockFields, tabScopes } from "./kernel";
|
|
3
3
|
export { walkFields } from "./walkFields";
|
|
4
4
|
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -83,3 +83,25 @@ export declare function tabScopes(field: TabsField): TabScope[];
|
|
|
83
83
|
* @public
|
|
84
84
|
*/
|
|
85
85
|
export declare function resolveBlockFields(field: BlocksField, item: unknown): Field[] | null;
|
|
86
|
+
/**
|
|
87
|
+
* Find the element of a parallel data array that corresponds to `refItem` by `id` — not by
|
|
88
|
+
* position.
|
|
89
|
+
*
|
|
90
|
+
* Localized `blocks`/`array` fields are stored independently per locale, so the locales can be
|
|
91
|
+
* reordered or differ entirely; pairing two parallel trees by position then cross-contaminates
|
|
92
|
+
* unrelated elements ("blocks don't line up across locales"). Matching by `id` is correct in both
|
|
93
|
+
* regimes: a non-localized field is one shared row with the same `id` across locales (so id-match
|
|
94
|
+
* equals the old positional match), while independent localized content has diverging ids (no
|
|
95
|
+
* match → the caller falls back to `refItem`'s own values, never another element's). For blocks the
|
|
96
|
+
* `blockType` must also match, so an id collision across types can't graft mismatched fields.
|
|
97
|
+
*
|
|
98
|
+
* Shared by the translation pipeline's two multi-tree walkers — reconcile and collect — to pair
|
|
99
|
+
* their `target` array against the source/reference element currently being visited.
|
|
100
|
+
*
|
|
101
|
+
* @param arr - The array to search (e.g. the target-locale elements).
|
|
102
|
+
* @param refItem - The element being matched, carrying the canonical `id` (and `blockType`).
|
|
103
|
+
* @param isBlocks - Whether the field is a `blocks` field (then `blockType` must also match).
|
|
104
|
+
* @returns The matched element, or `{}` when there is no counterpart (or `refItem` carries no `id`).
|
|
105
|
+
* @public
|
|
106
|
+
*/
|
|
107
|
+
export declare function matchElementById(arr: unknown[], refItem: Record<string, unknown>, isBlocks: boolean): Record<string, unknown>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fieldAffectsData, fieldIsArrayType, fieldIsBlockType, fieldIsGroupType, tabHasName } from "payload/shared";
|
|
2
2
|
import { hasFields, isBlockItem, isTabsField } from "../guards";
|
|
3
|
+
import { isObject } from "../utils";
|
|
3
4
|
/**
|
|
4
5
|
* Classify a single Payload {@link Field} into a discriminated {@link FieldStructure} — the
|
|
5
6
|
* one place that encodes how Payload field types map onto data boundaries.
|
|
@@ -141,5 +142,31 @@ import { hasFields, isBlockItem, isTabsField } from "../guards";
|
|
|
141
142
|
const block = field.blocks.find((candidate)=>candidate.slug === item.blockType);
|
|
142
143
|
return block ? block.fields : null;
|
|
143
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Find the element of a parallel data array that corresponds to `refItem` by `id` — not by
|
|
147
|
+
* position.
|
|
148
|
+
*
|
|
149
|
+
* Localized `blocks`/`array` fields are stored independently per locale, so the locales can be
|
|
150
|
+
* reordered or differ entirely; pairing two parallel trees by position then cross-contaminates
|
|
151
|
+
* unrelated elements ("blocks don't line up across locales"). Matching by `id` is correct in both
|
|
152
|
+
* regimes: a non-localized field is one shared row with the same `id` across locales (so id-match
|
|
153
|
+
* equals the old positional match), while independent localized content has diverging ids (no
|
|
154
|
+
* match → the caller falls back to `refItem`'s own values, never another element's). For blocks the
|
|
155
|
+
* `blockType` must also match, so an id collision across types can't graft mismatched fields.
|
|
156
|
+
*
|
|
157
|
+
* Shared by the translation pipeline's two multi-tree walkers — reconcile and collect — to pair
|
|
158
|
+
* their `target` array against the source/reference element currently being visited.
|
|
159
|
+
*
|
|
160
|
+
* @param arr - The array to search (e.g. the target-locale elements).
|
|
161
|
+
* @param refItem - The element being matched, carrying the canonical `id` (and `blockType`).
|
|
162
|
+
* @param isBlocks - Whether the field is a `blocks` field (then `blockType` must also match).
|
|
163
|
+
* @returns The matched element, or `{}` when there is no counterpart (or `refItem` carries no `id`).
|
|
164
|
+
* @public
|
|
165
|
+
*/ export function matchElementById(arr, refItem, isBlocks) {
|
|
166
|
+
const id = refItem.id;
|
|
167
|
+
if (id === undefined || id === null) return {};
|
|
168
|
+
const match = arr.find((candidate)=>isObject(candidate) && candidate.id === id && (!isBlocks || candidate.blockType === refItem.blockType));
|
|
169
|
+
return isObject(match) ? match : {};
|
|
170
|
+
}
|
|
144
171
|
|
|
145
172
|
//# sourceMappingURL=kernel.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@focus-reactive/payload-plugin-translator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Translation plugin for Payload CMS 3.x. Automatically translate your localized content using any translation provider.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
51
51
|
"copyfiles": "copyfiles -u 1 \"src/**/*.scss\" dist",
|
|
52
52
|
"clean": "rm -rf dist",
|
|
53
|
-
"dev": "bun run build:swc -- --watch",
|
|
53
|
+
"dev": "bun run build:swc -- --watch --copy-files",
|
|
54
54
|
"lint": "ultracite check",
|
|
55
55
|
"lint:fix": "ultracite fix",
|
|
56
56
|
"test": "vitest run",
|