Browse Source

Improve i18n editor.

- Add a one-click `download JSON` button.
- Add a `New language` button.

Add secondary editor InLang (remote service) to docs.

Closes #1266, #1267.
Kailash Nadh 2 năm trước cách đây
mục cha
commit
7db3d7da0f
4 tập tin đã thay đổi với 100 bổ sung47 xóa
  1. 13 5
      docs/docs/content/i18n.md
  2. 9 2
      docs/i18n/index.html
  3. 70 40
      docs/i18n/main.js
  4. 8 0
      docs/i18n/style.css

+ 13 - 5
docs/docs/content/i18n.md

@@ -17,9 +17,17 @@ To customize an existing language or to load a new language, put one or more `.j
 
 ## Contributing a new language
 
+### Using the basic editor
+
 - Visit [https://listmonk.app/i18n](https://listmonk.app/i18n)
-- To make changes to an existing language, use the "Load language" option on the top right.
-- To create a new language, use the "Load language" option on the top right and select "Default".
-- Translate the text in the input fields on the UI.
-- Once done, use the `Switch to raw JSON` and copy the JSON data and save it to a file named `xx.json`, where `xx` is the two letter code of the language.
-- Send a pull request to add the file to the [i18n directory on the GitHub repo](https://github.com/knadh/listmonk/tree/master/i18n). If you are not familiar with pull requests, share the file by creating a new "issue"  (comment) on the [GitHub issues page](https://github.com/knadh/listmonk/issues)
+- Click on `Createa new language`, or to make changes to an existing language, use `Load language`.
+- Translate the text in the text fields on the UI.
+- Once done, use the `Download raw JSON` to download the language file.
+- Send a pull request to add the file to the [i18n directory on the GitHub repo](https://github.com/knadh/listmonk/tree/master/i18n). 
+
+### Using InLang (external service)
+
+- Visit [https://inlang.com/editor/github.com/knadh/listmonk](https://inlang.com/editor/github.com/knadh/listmonk)
+- To make changes and push them, you need to log in to GitHub using OAuth and fork the project from the UI.
+- Translate the text in the input fields on the UI. You can use the filters to see only the necessary translations.
+- Once you're done, push the changes from the UI and click on "Open a pull request." This will take you to GitHub, where you can write a PR message.

+ 9 - 2
docs/i18n/index.html

@@ -13,7 +13,11 @@
 			<h1 class="title">{{ values["_.name"] }}</h1>
 			<div class="controls">
 				<div class="import block">
-					<a v-if="!isRawVisible" href="#" @click.prevent="onToggleRaw">Switch to raw JSON</a>
+					<a href="#" @click.prevent="onToggleRaw">
+						<template v-if="!isRawVisible">Switch to raw JSON</template>
+						<template v-else>Switch to editor</template>
+					</a>
+					<a href="#" @click.prevent="onDownloadJSON">Download raw JSON</a>
 					<a v-else href="#" @click.prevent="onToggleRaw">Switch to editor</a>
 				</div>
 
@@ -33,7 +37,7 @@
 				</div>
 
 				<div class="selector block">
-					Load language
+					Load existing language
 					<select v-model="loadLang" @change="onLoadLanguage">
 						<option value="en">Default (en)</option>
 						<option value="ca"> Català (ca) </option>
@@ -60,6 +64,9 @@
 						<option value="zh-CN"> 简体中文 (zh-CN) </option>
 						<option value="zh-TW"> 繁體中文(zh-TW) </option>
 					</select>
+
+					&nbsp;&nbsp;&nbsp;
+					<a href="#" @click.prevent="onNewLang">+ Create new language</a>
 				</div>
 			</div>
 		</header>

+ 70 - 40
docs/i18n/main.js

@@ -23,41 +23,41 @@ var app = new Vue({
 
 		loadBaseLang(url) {
 			return fetch(url).then(response => response.json()).then(data => {
-					// Retain the base values.
-					Object.assign(this.base, data);
-
-					// Get the sorted keys from the language map.
-					const keys = [];
-					const visibleKeys = {};
-					let head = null;
-					Object.entries(this.base).sort((a, b) => a[0].localeCompare(b[0])).forEach((v) => {
-						const h = v[0].split('.')[0];
-						keys.push({
-							"key": v[0],
-							"head": (head !== h ? h : null) // eg: campaigns on `campaigns.something.else`
-						});
-
-						visibleKeys[v[0]] = true;
-						head = h;
+				// Retain the base values.
+				Object.assign(this.base, data);
+
+				// Get the sorted keys from the language map.
+				const keys = [];
+				const visibleKeys = {};
+				let head = null;
+				Object.entries(this.base).sort((a, b) => a[0].localeCompare(b[0])).forEach((v) => {
+					const h = v[0].split('.')[0];
+					keys.push({
+						"key": v[0],
+						"head": (head !== h ? h : null) // eg: campaigns on `campaigns.something.else`
 					});
 
-					this.keys = keys;
-					this.visibleKeys = visibleKeys;
-					this.values = { ...this.base };
-
-					// Is there cached localStorage data?
-					if(localStorage.data) {
-						try {
-							this.loadData(JSON.parse(localStorage.data));
-						} catch(e) {
-							console.log("Bad JSON in localStorage: " + e.toString());
-						}
-						return;
+					visibleKeys[v[0]] = true;
+					head = h;
+				});
+
+				this.keys = keys;
+				this.visibleKeys = visibleKeys;
+				this.values = { ...this.base };
+
+				// Is there cached localStorage data?
+				if (localStorage.data) {
+					try {
+						this.populateData(JSON.parse(localStorage.data));
+					} catch (e) {
+						console.log("Bad JSON in localStorage: " + e.toString());
 					}
+					return;
+				}
 			});
 		},
 
-		loadData(data) {
+		populateData(data) {
 			// Filter out all keys from data except for the base ones
 			// in the base language.
 			const vals = this.keys.reduce((a, key) => {
@@ -69,6 +69,15 @@ var app = new Vue({
 			this.saveData();
 		},
 
+		loadLanguage(lang) {
+			return fetch(BASEURL + lang + ".json").then(response => response.json()).then(data => {
+				this.populateData(data);
+			}).catch((e) => {
+				console.log(e);
+				alert("error fetching file: " + e.toString());
+			});
+		},
+
 		saveData() {
 			localStorage.data = JSON.stringify(this.values);
 		},
@@ -87,7 +96,7 @@ var app = new Vue({
 				this.rawData = JSON.stringify(this.values, Object.keys(this.values).sort(), 4);
 			} else {
 				try {
-					this.loadData(JSON.parse(this.rawData));
+					this.populateData(JSON.parse(this.rawData));
 				} catch (e) {
 					alert("error parsing JSON: " + e.toString());
 					return false;
@@ -98,16 +107,37 @@ var app = new Vue({
 		},
 
 		onLoadLanguage() {
-			if(!confirm("Loading this language will overwrite your local changes. Continue?")) {
+			if (!confirm("Loading this language will overwrite your local changes. Continue?")) {
 				return false;
 			}
 
-			fetch(BASEURL + this.loadLang + ".json").then(response => response.json()).then(data => {
-				this.loadData(data);
-			}).catch((e) => {
-					console.log(e);
-					alert("error fetching file: " + e.toString());
-			});
+			this.loadLanguage(this.loadLang);
+		},
+
+		onNewLang() {
+			if (!confirm("Creating a new language will overwrite your local changes. Continue?")) {
+				return false;
+			}
+
+			let data = { ...this.base };
+			data["_.code"] = "iso-code-here"
+			data["_.name"] = "New language"
+			this.populateData(data);
+		},
+
+		onDownloadJSON() {
+			// Create a Blob using the content, mimeType, and optional encoding
+			const blob = new Blob([JSON.stringify(this.values, Object.keys(this.values).sort(), 4)], { type: "" });
+
+			// Create an anchor element with a download attribute
+			const link = document.createElement('a');
+			link.download = `${this.values["_.code"]}.json`;
+			link.href = URL.createObjectURL(blob);
+
+			// Append the link to the DOM, click it to start the download, and remove it
+			document.body.appendChild(link);
+			link.click();
+			document.body.removeChild(link);
 		}
 	},
 
@@ -128,10 +158,10 @@ var app = new Vue({
 				if (v === "pending") {
 					visible = !this.isDone(k.key);
 				} else if (v === "complete") {
-						visible = this.isDone(k.key);
+					visible = this.isDone(k.key);
 				}
 
-				if(visible) {
+				if (visible) {
 					visibleKeys[k.key] = true;
 				}
 			});
@@ -145,7 +175,7 @@ var app = new Vue({
 			let n = 0;
 
 			this.keys.forEach(k => {
-				if(this.values[k.key] !== this.base[k.key]) {
+				if (this.values[k.key] !== this.base[k.key]) {
 					n++;
 				}
 			});

+ 8 - 0
docs/i18n/style.css

@@ -12,6 +12,10 @@ h1, h2, h3, h4, h5 {
 	margin: 0 0 15px 0;
 }
 
+a {
+	color: #0055d4;
+}
+
 .container {
 	padding: 30px;
 }
@@ -20,6 +24,10 @@ h1, h2, h3, h4, h5 {
 	align-items: center;
 	margin-bottom: 30px;
 }
+	.header a {
+		display: inline-block;
+		margin-right: 15px;
+	}
 	.header .controls {
 		display: flex;
 	}