@jahia/agentic 0.2.0 → 0.3.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +28 -0
  3. package/dist/claude/.claude/skills/jahia/SKILL.md +18 -10
  4. package/dist/claude/.claude/skills/jahia-content/SKILL.md +102 -84
  5. package/dist/claude/.claude/skills/jahia-content-create-content/SKILL.md +255 -280
  6. package/dist/claude/.claude/skills/jahia-content-explore-structure/SKILL.md +187 -96
  7. package/dist/claude/.claude/skills/jahia-content-media-upload/SKILL.md +197 -0
  8. package/dist/claude/.claude/skills/jahia-content-move-content/SKILL.md +160 -165
  9. package/dist/claude/.claude/skills/jahia-content-organize/SKILL.md +209 -0
  10. package/dist/claude/.claude/skills/jahia-content-publish/SKILL.md +181 -0
  11. package/dist/claude/.claude/skills/jahia-content-query-content/SKILL.md +122 -92
  12. package/dist/claude/.claude/skills/jahia-content-translate-content/SKILL.md +154 -225
  13. package/dist/claude/.claude/skills/jahia-dev-cypress/SKILL.md +150 -330
  14. package/dist/claude/.claude/skills/jahia-dev-query-content/SKILL.md +93 -296
  15. package/dist/claude/.claude/skills/jahia-jcr-sql2/SKILL.md +258 -0
  16. package/dist/claude/CLAUDE.md +1 -7
  17. package/dist/codex/.agents/skills/jahia/SKILL.md +18 -10
  18. package/dist/codex/.agents/skills/jahia-content/SKILL.md +102 -84
  19. package/dist/codex/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  20. package/dist/codex/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  21. package/dist/codex/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  22. package/dist/codex/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  23. package/dist/codex/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  24. package/dist/codex/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  25. package/dist/codex/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  26. package/dist/codex/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  27. package/dist/codex/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  28. package/dist/codex/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  29. package/dist/codex/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  30. package/dist/codex/AGENTS.md +2 -4
  31. package/dist/copilot/.agents/skills/jahia/SKILL.md +18 -10
  32. package/dist/copilot/.agents/skills/jahia-content/SKILL.md +102 -84
  33. package/dist/copilot/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  34. package/dist/copilot/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  35. package/dist/copilot/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  36. package/dist/copilot/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  37. package/dist/copilot/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  38. package/dist/copilot/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  39. package/dist/copilot/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  40. package/dist/copilot/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  41. package/dist/copilot/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  42. package/dist/copilot/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  43. package/dist/copilot/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  44. package/dist/copilot/AGENTS.md +2 -4
  45. package/dist/cursor/.agents/skills/jahia/SKILL.md +18 -10
  46. package/dist/cursor/.agents/skills/jahia-content/SKILL.md +102 -84
  47. package/dist/cursor/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  48. package/dist/cursor/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  49. package/dist/cursor/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  50. package/dist/cursor/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  51. package/dist/cursor/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  52. package/dist/cursor/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  53. package/dist/cursor/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  54. package/dist/cursor/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  55. package/dist/cursor/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  56. package/dist/cursor/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  57. package/dist/cursor/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  58. package/dist/gemini/.agents/skills/jahia/SKILL.md +18 -10
  59. package/dist/gemini/.agents/skills/jahia-content/SKILL.md +102 -84
  60. package/dist/gemini/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  61. package/dist/gemini/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  62. package/dist/gemini/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  63. package/dist/gemini/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  64. package/dist/gemini/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  65. package/dist/gemini/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  66. package/dist/gemini/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  67. package/dist/gemini/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  68. package/dist/gemini/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  69. package/dist/gemini/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  70. package/dist/gemini/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  71. package/dist/gemini/AGENTS.md +2 -4
  72. package/dist/gemini/GEMINI.md +2 -2
  73. package/dist/opencode/.agents/skills/jahia/SKILL.md +18 -10
  74. package/dist/opencode/.agents/skills/jahia-content/SKILL.md +102 -84
  75. package/dist/opencode/.agents/skills/jahia-content-create-content/SKILL.md +255 -280
  76. package/dist/opencode/.agents/skills/jahia-content-explore-structure/SKILL.md +187 -96
  77. package/dist/opencode/.agents/skills/jahia-content-media-upload/SKILL.md +197 -0
  78. package/dist/opencode/.agents/skills/jahia-content-move-content/SKILL.md +160 -165
  79. package/dist/opencode/.agents/skills/jahia-content-organize/SKILL.md +209 -0
  80. package/dist/opencode/.agents/skills/jahia-content-publish/SKILL.md +181 -0
  81. package/dist/opencode/.agents/skills/jahia-content-query-content/SKILL.md +122 -92
  82. package/dist/opencode/.agents/skills/jahia-content-translate-content/SKILL.md +154 -225
  83. package/dist/opencode/.agents/skills/jahia-dev-cypress/SKILL.md +150 -330
  84. package/dist/opencode/.agents/skills/jahia-dev-query-content/SKILL.md +93 -296
  85. package/dist/opencode/.agents/skills/jahia-jcr-sql2/SKILL.md +258 -0
  86. package/dist/opencode/AGENTS.md +2 -4
  87. package/dist/windsurf/.windsurf/skills/jahia/SKILL.md +18 -10
  88. package/dist/windsurf/.windsurf/skills/jahia-content/SKILL.md +102 -84
  89. package/dist/windsurf/.windsurf/skills/jahia-content-create-content/SKILL.md +255 -280
  90. package/dist/windsurf/.windsurf/skills/jahia-content-explore-structure/SKILL.md +187 -96
  91. package/dist/windsurf/.windsurf/skills/jahia-content-media-upload/SKILL.md +197 -0
  92. package/dist/windsurf/.windsurf/skills/jahia-content-move-content/SKILL.md +160 -165
  93. package/dist/windsurf/.windsurf/skills/jahia-content-organize/SKILL.md +209 -0
  94. package/dist/windsurf/.windsurf/skills/jahia-content-publish/SKILL.md +181 -0
  95. package/dist/windsurf/.windsurf/skills/jahia-content-query-content/SKILL.md +122 -92
  96. package/dist/windsurf/.windsurf/skills/jahia-content-translate-content/SKILL.md +154 -225
  97. package/dist/windsurf/.windsurf/skills/jahia-dev-cypress/SKILL.md +150 -330
  98. package/dist/windsurf/.windsurf/skills/jahia-dev-query-content/SKILL.md +93 -296
  99. package/dist/windsurf/.windsurf/skills/jahia-jcr-sql2/SKILL.md +258 -0
  100. package/dist/windsurf/AGENTS.md +2 -4
  101. package/package.json +3 -3
@@ -1,273 +1,208 @@
1
1
  ---
2
2
  name: jahia-content-translate-content
3
- description: Adds language support to a Jahia site and translates existing content nodes. Use when asked to add a new language, fill in missing translations, or audit which content lacks i18n values.
3
+ description: Adds site languages and translates existing Jahia content using MCP tools. Use when asked to enable a locale, fill in missing translations, or publish translated pages and content.
4
4
  ---
5
5
 
6
6
  # Skill: jahia-content-translate-content
7
7
 
8
- Adds languages to a Jahia site and populates i18n properties on existing content nodes via the GraphQL API.
8
+ Translates existing content and manages multi-language support using MCP tools via the `jahia` MCP server.
9
+
10
+ > **Never call Jahia's GraphQL API directly.** Use only MCP tools. If a capability is missing, report it — do not work around with curl/GraphQL.
9
11
 
10
12
  ---
11
13
 
12
14
  ## Prerequisites
13
15
 
14
- - Jahia running at `http://localhost:8080`
15
- - Credentials: `root` / `root1234` (default)
16
- - GraphQL endpoint: `http://localhost:8080/modules/graphql`
17
-
18
- **Always include both auth flags:**
19
- ```bash
20
- curl -s -u root:root1234 \
21
- -H "Content-Type: application/json" \
22
- -H "Origin: http://localhost:8080" \
23
- -X POST http://localhost:8080/modules/graphql \
24
- -d '{"query": "..."}'
25
- ```
26
-
27
- > ⚠️ The `Origin` header is **required** — omitting it returns `Permission denied`.
16
+ - MCP server `jahia` connected with a valid API token
17
+ - Know the target `siteKey` (call `site.list` if unsure)
18
+ - Content already exists in a source locale
28
19
 
29
20
  ---
30
21
 
31
- ## Step 1 — Enable the new language on the site
32
-
33
- Before creating or querying translations, the language must be declared on the site node.
34
-
35
- ```bash
36
- # Check currently enabled languages
37
- curl -s -u root:root1234 \
38
- -H "Content-Type: application/json" \
39
- -H "Origin: http://localhost:8080" \
40
- -X POST http://localhost:8080/modules/graphql \
41
- -d '{
42
- "query": "{ jcr { nodeByPath(path: \"/sites/mySite\") { properties(names: [\"j:languages\",\"j:defaultLanguage\"]) { name values } } } }"
43
- }'
44
-
45
- # Add a language (e.g. "fr") — use setPropertiesBatch with the full new list
46
- curl -s -u root:root1234 \
47
- -H "Content-Type: application/json" \
48
- -H "Origin: http://localhost:8080" \
49
- -X POST http://localhost:8080/modules/graphql \
50
- -d '{
51
- "query": "mutation { jcr { mutateNode(pathOrId: \"/sites/mySite\") { setPropertiesBatch(properties: [{name: \"j:languages\", values: [\"en\", \"fr\"]}]) { path } } } }"
52
- }'
22
+ ## Step 1 — Check site languages
23
+
24
+ ```
25
+ tool: site.get
26
+ args: { "siteKey": "SITE_KEY" }
53
27
  ```
54
28
 
55
- > ⚠️ `j:languages` is a multi-valued property — always pass the **complete** list of languages, not just the new one. Passing only `["fr"]` would remove `"en"`.
29
+ Check `languages` and `defaultLanguage`.
56
30
 
57
- ---
31
+ - If the target locale is already present, continue.
32
+ - If the target locale is missing, add it to the site first before writing translations.
58
33
 
59
- ## Step 2 — Audit content missing translations
34
+ ---
60
35
 
61
- Query all i18n-bearing nodes and inspect which ones have empty values for the target language:
36
+ ## Step 2 Read the source content
62
37
 
63
- ```bash
64
- # Find all content nodes under /sites/mySite/contents
65
- curl -s -u root:root1234 \
66
- -H "Content-Type: application/json" \
67
- -H "Origin: http://localhost:8080" \
68
- -X POST http://localhost:8080/modules/graphql \
69
- -d '{
70
- "query": "{ jcr { nodesByQuery(query: \"SELECT * FROM [jmix:i18n] WHERE ISDESCENDANTNODE(\u0027/sites/mySite/contents\u0027) ORDER BY [jcr:path] ASC\", queryLanguage: SQL2) { nodes { path primaryNodeType { name } properties(language: \"fr\") { name value } } } } }"
71
- }'
38
+ ```
39
+ tool: content.get
40
+ args: {
41
+ "path": "/sites/SITE_KEY/home/about/main/intro-text",
42
+ "locale": "en"
43
+ }
72
44
  ```
73
45
 
74
- Or query a specific content type:
46
+ Note the properties that actually contain human-readable text.
75
47
 
76
- ```bash
77
- curl -s -u root:root1234 \
78
- -H "Content-Type: application/json" \
79
- -H "Origin: http://localhost:8080" \
80
- -X POST http://localhost:8080/modules/graphql \
81
- -d '{
82
- "query": "{ jcr { nodesByQuery(query: \"SELECT * FROM [namespace:article] WHERE ISDESCENDANTNODE(\u0027/sites/mySite/contents\u0027)\", queryLanguage: SQL2) { nodes { path properties(language: \"fr\") { name value } } } } }"
83
- }'
84
- ```
48
+ To find a batch of nodes to translate:
85
49
 
86
- Look for nodes where i18n fields (`jcr:title`, `body`, etc.) have empty or null `value`.
50
+ ```
51
+ tool: content.search
52
+ args: {
53
+ "siteKey": "SITE_KEY",
54
+ "nodeType": "jmix:droppableContent",
55
+ "locale": "en",
56
+ "limit": 50
57
+ }
58
+ ```
87
59
 
88
60
  ---
89
61
 
90
- ## Step 3 — Set i18n properties
91
-
92
- ### Single node
62
+ ## Step 3 — Identify which properties are i18n
93
63
 
94
- ```bash
95
- curl -s -u root:root1234 \
96
- -H "Content-Type: application/json" \
97
- -H "Origin: http://localhost:8080" \
98
- -X POST http://localhost:8080/modules/graphql \
99
- -d '{
100
- "query": "mutation { jcr { mutateNode(pathOrId: \"/sites/mySite/contents/articles/my-article\") { setPropertiesBatch(properties: [{name: \"jcr:title\", value: \"Mon article\", language: \"fr\"}, {name: \"body\", value: \"<p>Contenu ici</p>\", language: \"fr\"}]) { path } } } }"
101
- }'
64
+ ```
65
+ tool: content.type
66
+ args: { "name": "jnt:bigText" }
102
67
  ```
103
68
 
104
- > ⚠️ Use `setPropertiesBatch` (plural), not `setProperty` (singular — does not exist in the Jahia GraphQL API).
69
+ Translate only properties marked as internationalized.
105
70
 
106
- ### Critical ordering rule
71
+ Common i18n properties:
72
+ - `jcr:title`
73
+ - `text`
74
+ - `body`
75
+ - `description`
76
+ - `subtitle`
107
77
 
108
- When a content type has **mandatory** i18n fields (other than `jcr:title`), set those **before** `jcr:title`. Setting `jcr:title` first can trigger a constraint check before all mandatory fields are present.
78
+ Common non-i18n properties:
79
+ - `j:view`
80
+ - `j:templateName`
81
+ - `jcr:primaryType`
82
+ - references such as `j:node`
83
+ - numeric or technical settings
109
84
 
110
- The safe pattern — set all mandatory i18n fields in a single `setPropertiesBatch` call:
85
+ ---
111
86
 
112
- ```bash
113
- curl -s -u root:root1234 \
114
- -H "Content-Type: application/json" \
115
- -H "Origin: http://localhost:8080" \
116
- -X POST http://localhost:8080/modules/graphql \
117
- -d '{
118
- "query": "mutation { jcr { mutateNode(pathOrId: \"/sites/mySite/contents/articles/my-article\") { setPropertiesBatch(properties: [{name: \"body\", value: \"<p>Contenu ici</p>\", language: \"fr\"}, {name: \"jcr:title\", value: \"Mon article\", language: \"fr\"}]) { path } } } }"
119
- }'
120
- ```
87
+ ## Step 4 — Write translated properties
121
88
 
122
- ---
89
+ Use `content.update` with the target locale:
123
90
 
124
- ## Step 4 — Bulk translation with Python
91
+ ```
92
+ tool: content.update
93
+ args: {
94
+ "path": "/sites/SITE_KEY/home/about/main/intro-text",
95
+ "locale": "fr",
96
+ "properties": {
97
+ "jcr:title": "À propos",
98
+ "text": "<p>Bienvenue sur notre site.</p>"
99
+ }
100
+ }
101
+ ```
125
102
 
126
- For translating many nodes at once:
103
+ Key rules:
104
+ - Set all mandatory i18n properties in the same call.
105
+ - Translate only i18n properties.
106
+ - Preserve the HTML structure for rich text.
107
+ - Do not translate technical choicelist values.
127
108
 
128
- ```python
129
- import json
130
- from urllib.request import Request, urlopen
109
+ ---
131
110
 
132
- JAHIA = "http://localhost:8080"
133
- AUTH = ("root", "root1234")
111
+ ## Step 5 — Translate page titles
134
112
 
135
- import base64
136
- token = base64.b64encode(f"{AUTH[0]}:{AUTH[1]}".encode()).decode()
137
- HEADERS = {
138
- "Content-Type": "application/json",
139
- "Origin": JAHIA,
140
- "Authorization": f"Basic {token}",
141
- }
113
+ Page titles are also i18n:
142
114
 
143
- def gql(query):
144
- body = json.dumps({"query": query}).encode()
145
- req = Request(f"{JAHIA}/modules/graphql", data=body, headers=HEADERS, method="POST")
146
- with urlopen(req) as r:
147
- d = json.loads(r.read())
148
- if "errors" in d:
149
- print("ERR:", d["errors"][0]["message"][:120])
150
- return d
151
-
152
- # 1. List all articles missing French title
153
- result = gql(
154
- '{ jcr { nodesByQuery(query: "SELECT * FROM [namespace:article] '
155
- "WHERE ISDESCENDANTNODE('/sites/mySite/contents') "
156
- 'ORDER BY [jcr:path] ASC", queryLanguage: SQL2) '
157
- '{ nodes { path properties(language: "fr") { name value } } } } }'
158
- )
159
-
160
- nodes = result["data"]["jcr"]["nodesByQuery"]["nodes"]
161
- missing_fr = [
162
- n["path"]
163
- for n in nodes
164
- if not any(p["name"] == "jcr:title" and p["value"] for p in n["properties"])
165
- ]
166
-
167
- # 2. Set French translations (replace with actual translated values)
168
- translations = {
169
- "/sites/mySite/contents/articles/article-1": ("Titre FR 1", "<p>Corps FR 1</p>"),
170
- "/sites/mySite/contents/articles/article-2": ("Titre FR 2", "<p>Corps FR 2</p>"),
115
+ ```
116
+ tool: content.update
117
+ args: {
118
+ "path": "/sites/SITE_KEY/home/about",
119
+ "locale": "fr",
120
+ "properties": {
121
+ "jcr:title": "À propos"
122
+ }
171
123
  }
172
-
173
- for path, (title, body) in translations.items():
174
- r = gql(
175
- f'mutation {{ jcr {{ mutateNode(pathOrId: "{path}") {{'
176
- f' setPropertiesBatch(properties: ['
177
- f' {{name: "body", value: {json.dumps(body)}, language: "fr"}},'
178
- f' {{name: "jcr:title", value: {json.dumps(title)}, language: "fr"}}'
179
- f' ]) {{ path }} }} }} }}'
180
- )
181
- ok = "errors" not in r
182
- print(f" {'✓' if ok else '✗'} {path.split('/')[-1]}")
183
124
  ```
184
125
 
185
126
  ---
186
127
 
187
- ## Step 5Choicelist fields and view-level translations
128
+ ## Step 6Verify translations
188
129
 
189
- ### Choicelist fields should NOT be i18n
130
+ Check the translated locale directly:
190
131
 
191
- If a CND property uses a choicelist (e.g. `category`, `status`), its stored values are language-agnostic keys like `"featured"` or `"draft"`. The display label is translated **in the view**, not in the JCR.
132
+ ```
133
+ tool: content.get
134
+ args: {
135
+ "path": "/sites/SITE_KEY/home/about/main/intro-text",
136
+ "locale": "fr"
137
+ }
138
+ ```
192
139
 
193
- ```tsx
194
- // In the view component — translate the stored key to a display label
195
- const STATUS_LABELS: Record<string, Record<string, string>> = {
196
- en: { featured: "Featured", draft: "Draft" },
197
- fr: { featured: "En vedette", draft: "Brouillon" },
198
- };
140
+ Or search in the target locale:
199
141
 
200
- const lang = renderContext.getMainResourceLocale().getLanguage();
201
- const statusKey = properties.status as string;
202
- const statusLabel = STATUS_LABELS[lang]?.[statusKey] ?? statusKey;
142
+ ```
143
+ tool: content.search
144
+ args: {
145
+ "siteKey": "SITE_KEY",
146
+ "nodeType": "jmix:droppableContent",
147
+ "locale": "fr",
148
+ "limit": 20,
149
+ "projectProperties": ["jcr:title"]
150
+ }
203
151
  ```
204
152
 
205
- Do **not** add `i18n` to the CND property for choicelists — the key should be the same in all languages.
206
-
207
- ### Hardcoded UI strings
153
+ ---
208
154
 
209
- View-level strings that are not stored in JCR (button labels, headings, placeholder text) need locale-keyed label maps in the component:
155
+ ## Step 7 Publish the translated locale
210
156
 
211
- ```tsx
212
- const LABELS = {
213
- en: { readMore: "Read more", by: "By" },
214
- fr: { readMore: "Lire la suite", by: "Par" },
215
- };
157
+ Publish the page or subtree in the target language:
216
158
 
217
- const t = LABELS[renderContext.getMainResourceLocale().getLanguage()] ?? LABELS.en;
218
- // Usage: <a>{t.readMore}</a>
219
159
  ```
220
-
221
- ---
222
-
223
- ## Step 6 — Clean up orphaned translation nodes
224
-
225
- When you remove the `i18n` flag from a CND property (or delete a language from the site), orphaned `j:translation_XX` child nodes may remain. Inspect and remove them if needed:
226
-
227
- ```bash
228
- # Find translation sub-nodes for a content node
229
- curl -s -u root:root1234 \
230
- -H "Content-Type: application/json" \
231
- -H "Origin: http://localhost:8080" \
232
- -X POST http://localhost:8080/modules/graphql \
233
- -d '{
234
- "query": "{ jcr { nodeByPath(path: \"/sites/mySite/contents/articles/my-article\") { children { nodes { name primaryNodeType { name } } } } } }"
235
- }'
236
-
237
- # Delete an orphaned translation node
238
- curl -s -u root:root1234 \
239
- -H "Content-Type: application/json" \
240
- -H "Origin: http://localhost:8080" \
241
- -X POST http://localhost:8080/modules/graphql \
242
- -d '{
243
- "query": "mutation { jcr { deleteNode(pathOrId: \"/sites/mySite/contents/articles/my-article/j:translation_fr\") }"
244
- }'
160
+ tool: publication.publish
161
+ args: {
162
+ "path": "/sites/SITE_KEY/home/about",
163
+ "languages": ["fr"]
164
+ }
245
165
  ```
246
166
 
167
+ If the translation touches both a page and child content, publish the page path so the subtree is included.
168
+
247
169
  ---
248
170
 
249
- ## Step 7 — Publish translations
250
-
251
- After setting i18n properties, republish to make them live:
252
-
253
- ```bash
254
- # Publish a single node for a specific language
255
- curl -s -u root:root1234 \
256
- -H "Content-Type: application/json" \
257
- -H "Origin: http://localhost:8080" \
258
- -X POST http://localhost:8080/modules/graphql \
259
- -d '{
260
- "query": "mutation { jcr { mutateNode(pathOrId: \"/sites/mySite/contents/articles/my-article\") { publish(languages: [\"fr\"]) } } }"
261
- }'
262
-
263
- # Publish all articles in both languages
264
- curl -s -u root:root1234 \
265
- -H "Content-Type: application/json" \
266
- -H "Origin: http://localhost:8080" \
267
- -X POST http://localhost:8080/modules/graphql \
268
- -d '{
269
- "query": "mutation { jcr { mutateNodesByQuery(query: \"SELECT * FROM [namespace:article] WHERE ISDESCENDANTNODE(\u0027/sites/mySite/contents\u0027)\", queryLanguage: SQL2) { publish(languages: [\"en\", \"fr\"]) } } }"
270
- }'
171
+ ## Common patterns
172
+
173
+ ### Translate all content under a page
174
+
175
+ 1. Discover the page structure:
176
+ ```
177
+ tool: page.structure
178
+ args: { "path": "/sites/SITE_KEY/home/about" }
179
+ ```
180
+ 2. Read each child node in the source locale:
181
+ ```
182
+ tool: content.get
183
+ args: { "path": "CHILD_PATH", "locale": "en" }
184
+ ```
185
+ 3. Write translated values to the target locale:
186
+ ```
187
+ tool: content.update
188
+ args: {
189
+ "path": "CHILD_PATH",
190
+ "locale": "fr",
191
+ "properties": { "jcr:title": "...", "text": "..." }
192
+ }
193
+ ```
194
+ 4. Publish the page in the new language.
195
+
196
+ ### Audit whether a page is publish-ready in the new locale
197
+
198
+ ```
199
+ tool: publication.status
200
+ args: {
201
+ "path": "/sites/SITE_KEY/home/about",
202
+ "language": "fr",
203
+ "subNodes": true,
204
+ "references": true
205
+ }
271
206
  ```
272
207
 
273
208
  ---
@@ -276,22 +211,16 @@ curl -s -u root:root1234 \
276
211
 
277
212
  | Error | Cause | Fix |
278
213
  |-------|-------|-----|
279
- | `Permission denied` | Missing `Origin` header | Add `-H "Origin: http://localhost:8080"` |
280
- | i18n property returned empty after set | Missing `language:` in `properties()` query | Add `language: "fr"` to read call |
281
- | `ConstraintViolationException` on title set | Mandatory i18n field not set first | Use `setPropertiesBatch` with all mandatory fields in one call |
282
- | Language not appearing in site | `j:languages` mutation only had the new language | Pass the full list: `["en", "fr"]` |
283
- | Choicelist key changed per language | Property incorrectly declared `i18n` in CND | Remove `i18n` from the CND property; translate keys in the view |
214
+ | `MANDATORY_PROPERTY_MISSING` | Not all required i18n properties were set | Set all mandatory translated properties in one `content.update` call |
215
+ | Language not available | Locale not enabled on the site | Add the locale to the site first |
216
+ | Properties appear empty | Wrong locale was used | Verify configured locales with `site.get` |
217
+ | Translation not visible on the public site | It is still only in EDIT | Publish with `publication.publish` |
284
218
 
285
219
  ---
286
220
 
287
- ## Workflow summary
221
+ ## Related skills
222
+
223
+ - `/jahia-content-explore-structure` — find the right pages, nodes, and types first
224
+ - `/jahia-content-publish` — publish translated content and inspect language-scoped status
225
+ - `/jahia-content-create-content` — create new pages or content that will later need translation
288
226
 
289
- ```
290
- 1. Enable language → mutateNode j:languages with full list
291
- 2. Audit → nodesByQuery with properties(language: "XX") to find gaps
292
- 3. Translate → setPropertiesBatch with language: "XX" for each i18n field
293
- 4. View strings → add locale-keyed label maps in .server.tsx
294
- 5. Choicelists → translate keys in the view, not the JCR
295
- 6. Publish → publish(languages: ["XX"]) for all affected nodes
296
- 7. Verify → query back with language: "XX" to confirm values
297
- ```