You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2018/02/02 22:36:29 UTC

[sling-whiteboard] branch master updated: Adding an i18n concept

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 2aec688  Adding an i18n concept
2aec688 is described below

commit 2aec688845bd047790dee33e363837b81ee09037
Author: Dan Klco <da...@gmail.com>
AuthorDate: Fri Feb 2 17:36:22 2018 -0500

    Adding an i18n concept
---
 .../apache/sling/cms/core/models/i18nHelper.java   |  62 ++++++++++++
 cms/ui/src/main/frontend/src/js/scripts.js         |  22 +++--
 .../resources/jcr_root/etc/i18n/jcr:content.json   |   4 +
 .../sling-cms/components/cms/i18ntable/config.json |   3 +
 .../components/cms/i18ntable/i18ntable.jsp         |  77 +++++++++++++++
 .../components/editor/slingform/slingform.jsp      |   2 +-
 .../libs/sling-cms/content/i18n/dictionaries.json  | 104 +++++++++++++++++++++
 .../libs/sling-cms/content/i18n/dictionary.json    |  94 +++++++++++++++++++
 .../libs/sling-cms/content/i18n/entry/create.json  |  54 +++++++++++
 .../sling-cms/content/i18n/language/create.json    |  81 ++++++++++++++++
 .../libs/sling-cms/content/i18n/language/edit.json |  41 ++++++++
 .../jcr_root/libs/sling-cms/content/start.json     |   7 +-
 12 files changed, 539 insertions(+), 12 deletions(-)

diff --git a/cms/core/src/main/java/org/apache/sling/cms/core/models/i18nHelper.java b/cms/core/src/main/java/org/apache/sling/cms/core/models/i18nHelper.java
new file mode 100644
index 0000000..655305d
--- /dev/null
+++ b/cms/core/src/main/java/org/apache/sling/cms/core/models/i18nHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.cms.core.models;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.Set;
+
+import javax.jcr.query.Query;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+
+/**
+ * A model retrieving all of the keys for a i18n dictionary
+ */
+@Model(adaptables = Resource.class)
+public class i18nHelper {
+
+	private Resource resource;
+
+	private Set<String> keys = new HashSet<String>();
+
+	private Random rand = new Random();
+
+	public i18nHelper(Resource resource) {
+		this.resource = resource;
+	}
+
+	public Set<String> getKeys() {
+		if (keys.isEmpty()) {
+			Iterator<Resource> messageEntries = resource.getResourceResolver().findResources(
+					"SELECT * FROM [sling:MessageEntry] AS s WHERE ISDESCENDANTNODE([" + resource.getPath() + "])",
+					Query.JCR_SQL2);
+			while (messageEntries.hasNext()) {
+				Resource entry = messageEntries.next();
+				keys.add(entry.getValueMap().get("sling:key", String.class));
+			}
+		}
+		return keys;
+	}
+
+	public String getRandom() {
+		return String.valueOf(rand.nextInt());
+	}
+
+}
diff --git a/cms/ui/src/main/frontend/src/js/scripts.js b/cms/ui/src/main/frontend/src/js/scripts.js
index 08c9264..0f1acd2 100644
--- a/cms/ui/src/main/frontend/src/js/scripts.js
+++ b/cms/ui/src/main/frontend/src/js/scripts.js
@@ -108,16 +108,18 @@ Sling.CMS = {
 						jcrcontent = true;
 					}
 				});
-				if(jcrcontent){
-					$form.append('<input type="hidden" name="jcr:content/jcr:lastModified" />');
-					$form.append('<input type="hidden" name="jcr:content/jcr:lastModifiedBy" />');
-					$form.append('<input type="hidden" name="jcr:content/jcr:created" />');
-					$form.append('<input type="hidden" name="jcr:content/jcr:createdBy" />');
-				} else {
-					$form.append('<input type="hidden" name="jcr:lastModified" />');
-					$form.append('<input type="hidden" name="jcr:lastModifiedBy" />');
-					$form.append('<input type="hidden" name="jcr:created" />');
-					$form.append('<input type="hidden" name="jcr:createdBy" />');
+				if($form.data('addDate') && $form.find('input[name="jcr:content/jcr:lastModified"]').length == 0){
+					if(jcrcontent){
+						$form.append('<input type="hidden" name="jcr:content/jcr:lastModified" />');
+						$form.append('<input type="hidden" name="jcr:content/jcr:lastModifiedBy" />');
+						$form.append('<input type="hidden" name="jcr:content/jcr:created" />');
+						$form.append('<input type="hidden" name="jcr:content/jcr:createdBy" />');
+					} else {
+						$form.append('<input type="hidden" name="jcr:lastModified" />');
+						$form.append('<input type="hidden" name="jcr:lastModifiedBy" />');
+						$form.append('<input type="hidden" name="jcr:created" />');
+						$form.append('<input type="hidden" name="jcr:createdBy" />');
+					}
 				}
 				$.ajax({
 					url: $form.attr('action'),
diff --git a/cms/ui/src/main/resources/jcr_root/etc/i18n/jcr:content.json b/cms/ui/src/main/resources/jcr_root/etc/i18n/jcr:content.json
new file mode 100644
index 0000000..567db79
--- /dev/null
+++ b/cms/ui/src/main/resources/jcr_root/etc/i18n/jcr:content.json
@@ -0,0 +1,4 @@
+{
+	"jcr:primaryType": "nt:unstructured",
+	"jcr:title": "i18n"
+}
\ No newline at end of file
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/config.json b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/config.json
new file mode 100644
index 0000000..87a0fc2
--- /dev/null
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/config.json
@@ -0,0 +1,3 @@
+{
+	"jcr:primaryType": "sling:Component"
+}
\ No newline at end of file
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/i18ntable.jsp b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/i18ntable.jsp
new file mode 100644
index 0000000..4f574d7
--- /dev/null
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/cms/i18ntable/i18ntable.jsp
@@ -0,0 +1,77 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+
+<c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
+	<c:if test="${firstChild == null && not empty language.valueMap['jcr:language']}">
+		<c:set var="firstChild" value="${language}" />
+	</c:if>
+</c:forEach>
+<a class="Button Fetch-Modal" data-title="Add Entry" data-path=".Main-Content form" href="/cms/i18n/entry/create.html${firstChild.path}">+ Entry</a>
+<form method="post" action="${slingRequest.requestPathInfo.suffix}" enctype="multipart/form-data" class="Form-Ajax" data-add-date="false">
+	<table>
+		<thead>
+			<tr>
+				<th class="Column-key">
+					Key
+				</th>
+				<c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
+					<c:if test="${not empty language.valueMap['jcr:language']}">
+						<th class="Column-${language.valueMap['jcr:language']}">
+							<sling:encode value="${language.valueMap['jcr:content/jcr:title']}" mode="HTML" />
+							<br/>
+							<small>(<sling:encode value="${language.valueMap['jcr:language']}" mode="HTML" />)</small>
+						</th>
+					</c:if>
+				</c:forEach>
+			</tr>
+		</thead>
+		<tbody>
+			<sling:adaptTo var="helper" adaptable="${slingRequest.requestPathInfo.suffixResource}" adaptTo="org.apache.sling.cms.core.models.i18nHelper" />
+			<c:forEach var="key" items="${helper.keys}">
+				<tr>
+					<td>
+						<sling:encode value="${key}" mode="HTML" />
+					</td>
+					<c:forEach var="language" items="${sling:listChildren(slingRequest.requestPathInfo.suffixResource)}">
+						<c:if test="${not empty language.valueMap['jcr:language']}">
+							<td>
+								<c:set var="keyfound" value="false" />
+								<c:forEach var="entry" items="${sling:listChildren(language)}">
+									<c:if test="${entry.valueMap['sling:key'] == key}">
+										<c:set var="keyfound" value="true" />
+										<input name="${language.name}/${entry.name}/sling:message" type="text" value="${entry.valueMap['sling:message']}" />
+										<input name="${language.name}/${entry.name}/sling:key" type="hidden" value="${key}" />
+									</c:if>
+								</c:forEach>
+								<c:if test="${keyfound == 'false'}">
+									<c:set var="rand" value="${helper.random}" />
+									<input name="${language.name}/entry_${rand}/sling:message" type="text" value="" />
+									<input name="${language.name}/entry_${rand}/sling:key" type="hidden" value="${key}" />
+									<input name="${language.name}/entry_${rand}/jcr:primaryType" type="hidden" value="sling:MessageEntry" />
+								</c:if>
+							</td>
+						</c:if>
+					</c:forEach>
+				</tr>
+			</c:forEach>
+		</tbody>
+	</table>
+	<button>Save i18n Dictionary</button>
+</form>
\ No newline at end of file
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/slingform/slingform.jsp b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/slingform/slingform.jsp
index bf52b01..8e58d38 100644
--- a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/slingform/slingform.jsp
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/components/editor/slingform/slingform.jsp
@@ -17,7 +17,7 @@
  * under the License.
  */ --%>
  <%@include file="/libs/sling-cms/global.jsp"%>
-<form method="post" action="${slingRequest.requestPathInfo.suffix}${properties.actionSuffix}" enctype="multipart/form-data" class="Form-Ajax" data-success-prepend="">
+<form method="post" action="${slingRequest.requestPathInfo.suffix}${properties.actionSuffix}" enctype="multipart/form-data" class="Form-Ajax" data-add-date="${properties.addDate != false}">
 	<sling:include path="fields" resourceType="sling-cms/components/general/container" />
 	<div class="Field-Group">
 		<button type="submit" class="btn btn-success" title="<sling:encode value="${properties.button}" mode="HTML_ATTR" />">
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionaries.json b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionaries.json
new file mode 100644
index 0000000..7ce89c1
--- /dev/null
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionaries.json
@@ -0,0 +1,104 @@
+{
+	"jcr:primaryType": "sling:Page",
+	"jcr:content": {
+		"sling:resourceType": "sling-cms/components/pages/base",
+		"jcr:title": "i18n Dictionarties",
+		"jcr:primaryType": "nt:unstructured",
+		"container": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/general/container",
+			"richtext": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/general/richtext",
+				"text": "<h3>i18n Dictionarties</h3>"
+			},
+			"contentactions": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/cms/contentactions",
+				"actions": {
+					"dictionary": {
+						"jcr:primaryType": "nt:unstructured",
+						"label": "i18n Dictionary",
+						"prefix": "/cms/folder/create.html"
+					}
+				}
+			},
+			"contenttable": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/cms/contenttable",
+				"columns": {
+					"jcr:primaryType": "nt:unstructured",
+					"name": {
+						"jcr:primaryType": "nt:unstructured",
+						"title": "Name"
+					},
+					"title": {
+						"jcr:primaryType": "nt:unstructured",
+						"title": "Title"
+					},
+					"lastModified": {
+						"jcr:primaryType": "nt:unstructured",
+						"title": "Last Modified"
+					},
+					"lastModifiedBy": {
+						"jcr:primaryType": "nt:unstructured",
+						"title": "Last Modified By"
+					},
+					"actions": {
+						"jcr:primaryType": "nt:unstructured",
+						"title": "Actions"
+					}
+				},
+				"types": {
+					"jcr:primaryType": "nt:unstructured",
+					"sling:OrderedFolder": {
+						"jcr:primaryType": "nt:unstructured",
+						"columns": {
+							"jcr:primaryType": "nt:unstructured",
+							"name": {
+								"jcr:primaryType": "nt:unstructured",
+								"link": true,
+								"type": "Name",
+								"prefix": "/cms/i18n/dictionary.html"
+							},
+							"title": {
+								"jcr:primaryType": "nt:unstructured",
+								"property": "jcr:content/jcr:title",
+								"type": "String"
+							},
+							"lastModified": {
+								"jcr:primaryType": "nt:unstructured",
+								"property": "jcr:content/jcr:lastModified",
+								"type": "Date"
+							},
+							"lastModifiedBy": {
+								"jcr:primaryType": "nt:unstructured",
+								"property": "jcr:content/jcr:lastModifiedBy",
+								"type": "String"
+							},
+							"actions": {
+								"jcr:primaryType": "nt:unstructured",
+								"type": "Actions",
+								"edit": {
+									"jcr:primaryType": "nt:unstructured",
+									"modal": true,
+									"new": false,
+									"title": "Edit Dictionary",
+									"text": "&#x270f;",
+									"prefix": "/cms/folder/edit.html"
+								},
+								"delete": {
+									"jcr:primaryType": "nt:unstructured",
+									"modal": true,
+									"title": "Delete i18n Dictionary",
+									"text": "&times;",
+									"prefix": "/cms/shared/delete.html"
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionary.json b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionary.json
new file mode 100644
index 0000000..7198e29
--- /dev/null
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/dictionary.json
@@ -0,0 +1,94 @@
+{
+	"jcr:primaryType": "sling:Page",
+	"jcr:content": {
+		"sling:resourceType": "sling-cms/components/pages/base",
+		"jcr:title": "i18n Dictionarties",
+		"jcr:primaryType": "nt:unstructured",
+		"container": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/general/container",
+			"richtext": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/general/richtext",
+				"text": "<h3>i18n Dictionary</h3>"
+			},
+			"contentactions": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/cms/contentactions",
+				"actions": {
+					"language": {
+						"jcr:primaryType": "nt:unstructured",
+						"label": "Language",
+						"prefix": "/cms/i18n/language/create.html"
+					}
+				}
+			},
+			"contenttable": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/cms/contenttable",
+				"columns": {
+					"jcr:primaryType": "nt:unstructured",
+					"title": {
+						"jcr:primaryType": "nt:unstructured",
+						"title": "Title"
+					},
+					"language": {
+						"jcr:primaryType": "nt:unstructured",
+						"title": "Language"
+					},
+					"actions": {
+						"jcr:primaryType": "nt:unstructured",
+						"title": "Actions"
+					}
+				},
+				"types": {
+					"jcr:primaryType": "nt:unstructured",
+					"sling:Folder": {
+						"jcr:primaryType": "nt:unstructured",
+						"columns": {
+							"jcr:primaryType": "nt:unstructured",
+							"title": {
+								"jcr:primaryType": "nt:unstructured",
+								"property": "jcr:content/jcr:title",
+								"type": "String"
+							},
+							"Language": {
+								"jcr:primaryType": "nt:unstructured",
+								"property": "jcr:language",
+								"type": "String"
+							},
+							"actions": {
+								"jcr:primaryType": "nt:unstructured",
+								"type": "Actions",
+								"edit": {
+									"jcr:primaryType": "nt:unstructured",
+									"modal": true,
+									"new": false,
+									"title": "Edit Language",
+									"text": "&#x270f;",
+									"prefix": "/cms/i18n/language/edit.html"
+								},
+								"delete": {
+									"jcr:primaryType": "nt:unstructured",
+									"modal": true,
+									"title": "Delete i18n Dictionary",
+									"text": "&times;",
+									"prefix": "/cms/shared/delete.html"
+								}
+							}
+						}
+					}
+				}
+			},
+			"richtext_1": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/general/richtext",
+				"text": "<h3>i18n Dictionary Entries</h3>"
+			},
+			"i18ntable": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/cms/i18ntable"
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/entry/create.json b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/entry/create.json
new file mode 100644
index 0000000..cc1d08c
--- /dev/null
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/entry/create.json
@@ -0,0 +1,54 @@
+{
+	"jcr:primaryType": "sling:Page",
+	"jcr:content": {
+		"sling:resourceType": "sling-cms/components/pages/base",
+		"jcr:title": "Create i18n Entry",
+		"jcr:primaryType": "nt:unstructured",
+		"container": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/general/container",
+			"richtext": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/general/richtext",
+				"text": "<h3>Create Entry</h3>"
+			},
+			"slingform": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/editor/slingform",
+				"actionSuffix": "/*",
+				"addDate": false,
+				"button": "Create Language",
+				"successPrepend": "/libs/sling-cms/content/site/content.html",
+				"fields": {
+					"jcr:primaryType": "nt:unstructured",
+					"sling:resourceType": "sling-cms/components/general/container",
+					"key": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/text",
+						"label": "Key",
+						"name": "sling:key",
+						"required": true
+					},
+					"value": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/hidden",
+						"name": "sling:message",
+						"value": ""
+					},
+					"nameHint": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/hidden",
+						"value": "entry",
+						"name": ":nameHint"
+					},
+					"primaryType": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/hidden",
+						"name": "jcr:primaryType",
+						"value": "sling:MessageEntry"
+					}
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/language/create.json b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/language/create.json
new file mode 100644
index 0000000..e010b62
--- /dev/null
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/language/create.json
@@ -0,0 +1,81 @@
+{
+	"jcr:primaryType": "sling:Page",
+	"jcr:content": {
+		"sling:resourceType": "sling-cms/components/pages/base",
+		"jcr:title": "Create Language",
+		"jcr:primaryType": "nt:unstructured",
+		"container": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/general/container",
+			"richtext": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/general/richtext",
+				"text": "<h3>Create Language</h3>"
+			},
+			"slingform": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/editor/slingform",
+				"actionSuffix": "/*",
+				"button": "Create Language",
+				"successPrepend": "/libs/sling-cms/content/site/content.html",
+				"fields": {
+					"jcr:primaryType": "nt:unstructured",
+					"sling:resourceType": "sling-cms/components/general/container",
+					"title": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/text",
+						"label": "Title",
+						"name": "jcr:content/jcr:title",
+						"required": true
+					},
+					"language": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/text",
+						"label": "Language",
+						"name": "jcr:language",
+						"required": true
+					},
+					"name": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/text",
+						"label": "Name",
+						"name": ":name",
+						"required": true
+					},
+					"primaryType": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/hidden",
+						"name": "jcr:primaryType",
+						"value": "sling:Folder"
+					},
+					"contentPrimaryType": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/hidden",
+						"name": "jcr:content/jcr:primaryType",
+						"value": "nt:unstructured"
+					},
+					"contentResourceType": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/hidden",
+						"name": "jcr:content/sling:resourceType",
+						"value": "sling-cms/components/cms/blank"
+					},
+					"mixin": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/hidden",
+						"name": "jcr:mixinTypes",
+						"value": "mix:language"
+					},
+					"mixinTypeHint": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/hidden",
+						"name": "jcr:mixinTypes@TypeHint",
+						"value": [
+							"Name[]"
+						]
+					}
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/language/edit.json b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/language/edit.json
new file mode 100644
index 0000000..169b3f9
--- /dev/null
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/i18n/language/edit.json
@@ -0,0 +1,41 @@
+{
+	"jcr:primaryType": "sling:Page",
+	"jcr:content": {
+		"sling:resourceType": "sling-cms/components/pages/base",
+		"jcr:title": "Edit Language",
+		"jcr:primaryType": "nt:unstructured",
+		"container": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/general/container",
+			"richtext": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/general/richtext",
+				"text": "<h3>Edit Language</h3>"
+			},
+			"slingform": {
+				"jcr:primaryType": "nt:unstructured",
+				"sling:resourceType": "sling-cms/components/editor/slingform",
+				"button": "Save Language",
+				"successPrepend":"/libs/sling-cms/content/site/content.html",
+				"fields": {
+					"jcr:primaryType": "nt:unstructured",
+					"sling:resourceType": "sling-cms/components/general/container",
+					"title": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/text",
+						"label": "Title",
+						"name": "jcr:content/jcr:title",
+						"required": true
+					},
+					"language": {
+						"jcr:primaryType": "nt:unstructured",
+						"sling:resourceType": "sling-cms/components/editor/fields/text",
+						"label": "Language",
+						"name": "jcr:language",
+						"required": true
+					}
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
index a7c9544..35c9044 100644
--- a/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
+++ b/cms/ui/src/main/resources/jcr_root/libs/sling-cms/content/start.json
@@ -52,6 +52,11 @@
 						"link": "/cms/file/editors.html/etc/fileeditors",
 						"text": "File Editors"
 					},
+					"i18n": {
+						"jcr:primaryType": "nt:unstructured",
+						"link": "/cms/i18n/dictionaries.html/etc/i18n",
+						"text": "Internationalization (i18n)"
+					},
 					"nodebrowser": {
 						"jcr:primaryType": "nt:unstructured",
 						"link": "/bin/browser.html",
@@ -60,7 +65,7 @@
 					"siteconfig": {
 						"jcr:primaryType": "nt:unstructured",
 						"link": "/cms/config/list.html/etc/config",
-						"text": "Site Configs"
+						"text": "Site Configurations"
 					},
 					"systemconsole": {
 						"jcr:primaryType": "nt:unstructured",

-- 
To stop receiving notification emails like this one, please contact
dklco@apache.org.