
Find more on AEM Content Fragments
Find more on links localization
In the previous article, we took a look at how to set up a translation project and correctly configure links rewriting during New Lang copy creation. Now we’re going to look at more cases you might encounter during links localization and how to approach a variety of situations.
Update All AEM Content Fragment Variations (Language Copy)
Before we can update a language copy (all variations), we have to:
- Select your CF
- Click References → Language copies
- Check locales to update
- Select Add to existing translation project
- Then select our Site-com-translation-project created before
- Select Update All variations
- Click Start
AEM’s ‘’DAM Update Language Copy” workflow is being triggered. We’ll have to introduce a new step for this WF to localize the links correctly.
ContentFragmentUpdateLanguageCopyProcess.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
package com.wcm.site.workflow; import com.adobe.acs.commons.util.WorkflowHelper; import com.adobe.acs.commons.util.visitors.TraversalException; import com.adobe.acs.commons.util.visitors.TreeFilteringResourceVisitor; import com.adobe.granite.workflow.WorkflowException; import com.adobe.granite.workflow.WorkflowSession; import com.adobe.granite.workflow.exec.WorkItem; import com.adobe.granite.workflow.exec.WorkflowData; import com.adobe.granite.workflow.exec.WorkflowProcess; import com.adobe.granite.workflow.metadata.MetaDataMap; import com.day.cq.dam.commons.util.DamLanguageUtil; import com.day.cq.wcm.api.PageManagerFactory; import com.day.cq.wcm.msm.api.LiveRelationshipManager; import com.wcm.site.util.AssetUtils; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.Session; import java.util.Arrays; import static com.wcm.site.util.CFMUtils.isContentFragment; import static com.wcm.site.workflow.WorkflowProcessBase.WORKFLOW_PROCESS_LABEL; @Component(immediate = true, service = WorkflowProcess.class, property = { WORKFLOW_PROCESS_LABEL + "=Site.com Content Fragment Update Language Copy" }) public class ContentFragmentUpdateLanguageCopyProcess extends WorkflowProcessBase implements WorkflowProcess { private static final Logger LOG = LoggerFactory.getLogger(ContentFragmentUpdateLanguageCopyProcess.class); private static final String TRANSLATE_LANGUAGES_KEY_NAME = "translateLanguages"; @Reference private WorkflowHelper workflowHelper; @Reference private PageManagerFactory pageManagerFactory; @Reference private LiveRelationshipManager liveRelationshipManager; @Override public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaData) throws WorkflowException { ResourceResolver resourceResolver = getResourceResolver(workflowHelper, workflowSession); String sourcePath = getTargetPath(workItem, true); Resource source = resourceResolver.getResource(sourcePath); WorkflowData data = workItem.getWorkflowData(); MetaDataMap metaDataMap = data.getMetaDataMap(); if (AssetUtils.isFolder(source)) { processFolder(workItem, workflowSession, source, metaDataMap); workflowSession.terminateWorkflow(workItem.getWorkflow()); return; } if (source == null || !isContentFragment(source)) { LOG.error("Resource not found or not content fragment {}", sourcePath); return; } String updateLanguages = metaDataMap.get(TRANSLATE_LANGUAGES_KEY_NAME, StringUtils.EMPTY); if (StringUtils.isEmpty(updateLanguages)) { LOG.error("Update languages list is empty, exiting"); return; } String[] updateLanguagesArr = StringUtils.split(updateLanguages, ";"); Arrays.stream(updateLanguagesArr).forEach(language -> DamLanguageUtil.replaceUpdatedAsset(sourcePath, getTargetLanguageCopyPath(sourcePath, language, resourceResolver, null), resourceResolver.adaptTo(Session.class), pageManagerFactory, resourceResolver ) ); } private void processFolder(WorkItem workItem, WorkflowSession workflowSession, Resource source, MetaDataMap metaData) { try { TreeFilteringResourceVisitor cfVisitor = new TreeFilteringResourceVisitor(); cfVisitor.setTraversalFilter(resource -> cfVisitor.isFolder(resource) || isContentFragment(resource)); cfVisitor.setResourceVisitor((resource, level) -> { if (isContentFragment(resource)) { startWorkflow(workflowSession, workItem.getWorkflow().getWorkflowModel().getId(), resource.getPath(), metaData); } }); cfVisitor.accept(source); } catch (TraversalException e) { LOG.error("Exception", e); } } public static Resource getTargetLanguageCopyResource(String sourcePath, String language, ResourceResolver resolver) { return Optional.ofNullable(DamLanguageUtil.getLanguageCopy(sourcePath, language, resolver)) .map(asset -> asset.adaptTo(Resource.class)) .orElse(null); } public static String getTargetLanguageCopyPath(String sourcePath, String language, ResourceResolver resolver, String fallback) { if (isFolder(resolver.getResource(sourcePath))) { PathContext context = new PathContext(sourcePath); String targetPath = StringUtils.removeEnd(context.getPathWithLocale(language), "/"); return resolver.getResource(targetPath) != null ? targetPath : fallback; } return Optional.ofNullable(getTargetLanguageCopyResource(sourcePath, language, resolver)) .map(Resource::getPath) .orElse(fallback); } } |
And the workflow itself
/var/workflow/models/dam/dam-update-language-copy.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primaryType="cq:WorkflowModel" sling:resourceType="cq/workflow/components/model" description="This workflow updates language copies for assets" title="DAM Update Language Copy"> <metaData cq:generatingPage="/conf/global/settings/workflow/models/dam/dam-update-language-copy/jcr:content" cq:lastModified="{Long}1610816334147" cq:lastModifiedBy="admin" jcr:primaryType="nt:unstructured"/> <nodes jcr:primaryType="nt:unstructured"> <node0 jcr:primaryType="cq:WorkflowNode" title="Start" type="START"> <metaData jcr:primaryType="nt:unstructured"/> </node0> <node1 jcr:primaryType="cq:WorkflowNode" description="Updates language copy of assets" title="Update Language Copy" type="PROCESS"> <metaData jcr:primaryType="nt:unstructured" PROCESS="com.day.cq.dam.core.impl.process.UpdateAssetLanguageCopyProcess" PROCESS_AUTO_ADVANCE="true"/> </node1> <node2 jcr:primaryType="cq:WorkflowNode" title="Site.com Content Fragment Update Language Copy" type="PROCESS"> <metaData jcr:primaryType="nt:unstructured" PROCESS="com.wcm.site.workflow.ContentFragmentUpdateLanguageCopyProcess" PROCESS_AUTO_ADVANCE="true"/> </node2> <node3 jcr:primaryType="cq:WorkflowNode" title="Content Fragment Localisation WFs Starter" type="PROCESS"> <metaData jcr:primaryType="nt:unstructured" PROCESS="com.wcm.site.workflow.ContentFragmentLocalisationWFStarterProcess" PROCESS_ARGS="modelId:/var/workflow/models/site/cf-localisation-workflow" PROCESS_AUTO_ADVANCE="true"/> </node3> <node4 jcr:primaryType="cq:WorkflowNode" title="End" type="END"> <metaData jcr:primaryType="nt:unstructured"/> </node4> </nodes> <transitions jcr:primaryType="nt:unstructured"> <node0_x0023_node1 jcr:primaryType="cq:WorkflowTransition" from="node0" rule="" to="node1"> <metaData jcr:primaryType="nt:unstructured"/> </node0_x0023_node1> <node1_x0023_node2 jcr:primaryType="cq:WorkflowTransition" from="node1" rule="" to="node2"> <metaData jcr:primaryType="nt:unstructured"/> </node1_x0023_node2> <node2_x0023_node3 jcr:primaryType="cq:WorkflowTransition" from="node2" rule="" to="node3"> <metaData jcr:primaryType="nt:unstructured"/> </node2_x0023_node3> <node3_x0023_node4 jcr:primaryType="cq:WorkflowTransition" from="node3" rule="" to="node4"> <metaData jcr:primaryType="nt:unstructured"/> </node3_x0023_node4> </transitions> </jcr:root> |
Update AEM Content Fragment Single Variation
If we want to update one particular content fragment variation rather than all, a different AEM WF is triggered, so we have to make some adjustments.
ContentFragmentUpdateVariationLanguageCopyProcess.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
package com.wcm.site.workflow; import com.adobe.acs.commons.util.WorkflowHelper; import com.adobe.cq.dam.cfm.ContentFragment; import com.adobe.granite.workflow.WorkflowSession; import com.adobe.granite.workflow.exec.WorkItem; import com.adobe.granite.workflow.exec.WorkflowData; import com.adobe.granite.workflow.exec.WorkflowProcess; import com.adobe.granite.workflow.metadata.MetaDataMap; import com.day.cq.wcm.api.PageManagerFactory; import com.day.cq.wcm.msm.api.LiveRelationshipManager; import com.wcm.site.localization.PathContext; import com.wcm.site.util.CFMUtils; import com.wcm.site.util.ResourceUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.resource.*; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.Calendar; import java.util.Optional; import static com.day.cq.commons.jcr.JcrConstants.*; import static com.wcm.site.util.CFMUtils.getCFVariationPath; import static com.wcm.site.util.CFMUtils.isContentFragment; import static com.wcm.site.util.LinkUtils.joinAsPath; import static com.wcm.site.workflow.WorkflowProcessBase.WORKFLOW_PROCESS_LABEL; @Component(immediate = true, service = WorkflowProcess.class, property = { WORKFLOW_PROCESS_LABEL + "=Site.com Content Fragment Update Variation Language Copy" }) public class ContentFragmentUpdateVariationLanguageCopyProcess extends WorkflowProcessBase implements WorkflowProcess { private static final Logger LOG = LoggerFactory.getLogger(ContentFragmentUpdateVariationLanguageCopyProcess.class); private static final String VARIATION_KEY_NAME = "variation"; private static final String MASTER_VARIATION_NAME = "master"; @Reference private WorkflowHelper workflowHelper; @Reference private PageManagerFactory pageManagerFactory; @Reference private LiveRelationshipManager liveRelationshipManager; @Override public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaData) { try { ResourceResolver resourceResolver = getResourceResolver(workflowHelper, workflowSession); String sourcePath = getTargetPath(workItem, true); Resource source = resourceResolver.getResource(sourcePath); if (source == null || !isContentFragment(source)) { LOG.error("Resource not found or not content fragment {}", sourcePath); return; } WorkflowData data = workItem.getWorkflowData(); MetaDataMap metaDataMap = data.getMetaDataMap(); String updateLanguages = metaDataMap.get(TRANSLATE_LANGUAGES_KEY_NAME, StringUtils.EMPTY); String variationName = metaDataMap.get(VARIATION_KEY_NAME, StringUtils.EMPTY); if (StringUtils.isAnyEmpty(updateLanguages, variationName)) { LOG.error("Update languages list or variation is empty, exiting"); return; } String[] updateLanguagesArr = StringUtils.split(updateLanguages, ";"); Arrays.stream(updateLanguagesArr).forEach(language -> copyOrReplaceVariation(resourceResolver, sourcePath, variationName, language)); if (resourceResolver.hasChanges()) { resourceResolver.commit(); } } catch (Exception e) { LOG.error("Exception", e); } } private void copyOrReplaceVariation(ResourceResolver resourceResolver, String sourcePath, String variationName, String language) { PathContext context = new PathContext(sourcePath); try { Resource targetLanguageCopy = getTargetLanguageCopyResource(sourcePath, language, resourceResolver); Optional.ofNullable(targetLanguageCopy) .map(resource -> resource.adaptTo(ContentFragment.class)) .ifPresent(CFMUtils::createVersion); Resource destinationData = resourceResolver.getResource(StringUtils.removeEnd(getCFVariationPath(context.getPathWithLocale(language), StringUtils.EMPTY), "/")); Resource destinationModel = ResourceUtil.getOrCreateResource(resourceResolver, StringUtils.removeEnd(getCFModelVariationPath(context.getPathWithLocale(language), StringUtils.EMPTY), "/"), NT_UNSTRUCTURED, NT_UNSTRUCTURED, true); if (!ObjectUtils.allNotNull(destinationData, destinationModel)) { LOG.error("destination is null"); return; } Resource variationDataResource = destinationData.getChild(variationName); Resource variationModelResource = destinationModel.getChild(variationName); ResourceUtils.delete(resourceResolver, variationDataResource); ResourceUtils.delete(resourceResolver, variationModelResource); if (resourceResolver.hasChanges()) { resourceResolver.commit(); } resourceResolver.copy(getCFVariationPath(sourcePath, variationName), destinationData.getPath()); if (!MASTER_VARIATION_NAME.equalsIgnoreCase(variationName)) { resourceResolver.copy(getCFModelVariationPath(sourcePath, variationName), destinationModel.getPath()); } Optional.ofNullable(targetLanguageCopy) .map(resource -> resource.getChild(JCR_CONTENT)) .map(resource -> resource.adaptTo(ModifiableValueMap.class)) .ifPresent(valueMap -> { valueMap.put(JCR_LASTMODIFIED, Calendar.getInstance()); valueMap.put(JCR_LAST_MODIFIED_BY, resourceResolver.getUserID()); }); } catch (PersistenceException e) { LOG.error("Exception", e); } } private String getCFModelVariationPath(String cfPath, String variation) { return joinAsPath(cfPath, JCR_CONTENT, "model/variations", variation); } } |
And a workflow config to change
/var/workflow/models/dam/dam-update-variation-language-copy.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:isCheckedOut="{Boolean}false" jcr:primaryType="cq:WorkflowModel" jcr:uuid="6eee1ae2-6cf2-457a-a0f8-d6fbc0c96b38" sling:resourceType="cq/workflow/components/model" description="This workflow updates language copies variations for assets" title="DAM Update Variation Language Copy"> <metaData cq:generatingPage="/conf/global/settings/workflow/models/dam/dam-update-variation-language-copy/jcr:content" cq:lastModified="{Long}1612297043045" cq:lastModifiedBy="admin" jcr:primaryType="nt:unstructured"/> <nodes jcr:primaryType="nt:unstructured"> <node0 jcr:primaryType="cq:WorkflowNode" title="Start" type="START"> <metaData jcr:primaryType="nt:unstructured"/> </node0> <node1 jcr:primaryType="cq:WorkflowNode" description="Updates language copy of assets" title="Update Language Copy Variation" type="PROCESS"> <metaData jcr:primaryType="nt:unstructured" PROCESS="com.wcm.site.workflow.ContentFragmentUpdateVariationLanguageCopyProcess" PROCESS_AUTO_ADVANCE="true"/> </node1> <node2 jcr:primaryType="cq:WorkflowNode" title="Content Fragment Localisation WFs Starter" type="PROCESS"> <metaData jcr:primaryType="nt:unstructured" PROCESS="com.wcm.site.workflow.ContentFragmentLocalisationWFStarterProcess" PROCESS_ARGS="modelId:/var/workflow/models/site/cf-localisation-workflow" PROCESS_AUTO_ADVANCE="true"/> </node2> <node3 jcr:primaryType="cq:WorkflowNode" title="End" type="END"> <metaData jcr:primaryType="nt:unstructured"/> </node3> </nodes> <transitions jcr:primaryType="nt:unstructured"> <node0_x0023_node1 jcr:primaryType="cq:WorkflowTransition" from="node0" rule="" to="node1"> <metaData jcr:primaryType="nt:unstructured"/> </node0_x0023_node1> <node1_x0023_node2 jcr:primaryType="cq:WorkflowTransition" from="node1" rule="" to="node2"> <metaData jcr:primaryType="nt:unstructured"/> </node1_x0023_node2> <node2_x0023_node3 jcr:primaryType="cq:WorkflowTransition" from="node2" to="node3"> <metaData jcr:primaryType="nt:unstructured"/> </node2_x0023_node3> </transitions> </jcr:root> |
That’s pretty much it! We’ve covered all the possible cases you’ll encounter with AEM Content Fragment link localization, so you’re all ready to go!
Author: Iryna Ason