2023-03-26 03:21:20 +08:00
|
|
|
const BASEURL = "https://raw.githubusercontent.com/knadh/listmonk/master/i18n/";
|
|
|
|
const BASELANG = "en";
|
|
|
|
|
|
|
|
var app = new Vue({
|
|
|
|
el: "#app",
|
|
|
|
data: {
|
|
|
|
base: {},
|
|
|
|
keys: [],
|
|
|
|
visibleKeys: {},
|
|
|
|
values: {},
|
|
|
|
view: "all",
|
|
|
|
loadLang: BASELANG,
|
|
|
|
|
|
|
|
isRawVisible: false,
|
|
|
|
rawData: "{}"
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
init() {
|
|
|
|
document.querySelector("#app").style.display = 'block';
|
|
|
|
document.querySelector("#loading").remove();
|
|
|
|
},
|
|
|
|
|
|
|
|
loadBaseLang(url) {
|
|
|
|
return fetch(url).then(response => response.json()).then(data => {
|
2023-04-06 14:27:57 +08:00
|
|
|
// 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`
|
2023-03-26 03:21:20 +08:00
|
|
|
});
|
|
|
|
|
2023-04-06 14:27:57 +08:00
|
|
|
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());
|
2023-03-26 03:21:20 +08:00
|
|
|
}
|
2023-04-06 14:27:57 +08:00
|
|
|
return;
|
|
|
|
}
|
2023-03-26 03:21:20 +08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2023-04-06 14:27:57 +08:00
|
|
|
populateData(data) {
|
2023-03-26 03:21:20 +08:00
|
|
|
// Filter out all keys from data except for the base ones
|
|
|
|
// in the base language.
|
|
|
|
const vals = this.keys.reduce((a, key) => {
|
|
|
|
a[key.key] = data.hasOwnProperty(key.key) ? data[key.key] : this.base[key.key];
|
|
|
|
return a;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
this.values = vals;
|
|
|
|
this.saveData();
|
|
|
|
},
|
|
|
|
|
2023-04-06 14:27:57 +08:00
|
|
|
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());
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2023-03-26 03:21:20 +08:00
|
|
|
saveData() {
|
|
|
|
localStorage.data = JSON.stringify(this.values);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Has a key been translated (changed from the base)?
|
|
|
|
isDone(key) {
|
|
|
|
return this.values[key] && this.base[key] !== this.values[key];
|
|
|
|
},
|
|
|
|
|
|
|
|
isItemVisible(key) {
|
|
|
|
return this.visibleKeys[key];
|
|
|
|
},
|
|
|
|
|
|
|
|
onToggleRaw() {
|
|
|
|
if (!this.isRawVisible) {
|
|
|
|
this.rawData = JSON.stringify(this.values, Object.keys(this.values).sort(), 4);
|
|
|
|
} else {
|
|
|
|
try {
|
2023-04-06 14:27:57 +08:00
|
|
|
this.populateData(JSON.parse(this.rawData));
|
2023-03-26 03:21:20 +08:00
|
|
|
} catch (e) {
|
|
|
|
alert("error parsing JSON: " + e.toString());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isRawVisible = !this.isRawVisible;
|
|
|
|
},
|
|
|
|
|
|
|
|
onLoadLanguage() {
|
2023-04-06 14:27:57 +08:00
|
|
|
if (!confirm("Loading this language will overwrite your local changes. Continue?")) {
|
2023-03-26 03:21:20 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-04-06 14:27:57 +08:00
|
|
|
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);
|
2023-03-26 03:21:20 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
mounted() {
|
|
|
|
this.loadBaseLang(BASEURL + BASELANG + ".json").then(() => this.init());
|
|
|
|
},
|
|
|
|
|
|
|
|
watch: {
|
|
|
|
view(v) {
|
|
|
|
// When the view changes, create a copy of the items to be filtered
|
|
|
|
// by and filter the view based on that. Otherwise, the moment the value
|
|
|
|
// in the input changes, the list re-renders making items disappear.
|
|
|
|
|
|
|
|
const visibleKeys = {};
|
|
|
|
this.keys.forEach((k) => {
|
|
|
|
let visible = true;
|
|
|
|
|
|
|
|
if (v === "pending") {
|
|
|
|
visible = !this.isDone(k.key);
|
|
|
|
} else if (v === "complete") {
|
2023-04-06 14:27:57 +08:00
|
|
|
visible = this.isDone(k.key);
|
2023-03-26 03:21:20 +08:00
|
|
|
}
|
|
|
|
|
2023-04-06 14:27:57 +08:00
|
|
|
if (visible) {
|
2023-03-26 03:21:20 +08:00
|
|
|
visibleKeys[k.key] = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.visibleKeys = visibleKeys;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
completed() {
|
|
|
|
let n = 0;
|
|
|
|
|
|
|
|
this.keys.forEach(k => {
|
2023-04-06 14:27:57 +08:00
|
|
|
if (this.values[k.key] !== this.base[k.key]) {
|
2023-03-26 03:21:20 +08:00
|
|
|
n++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|