Skip to content

Instantly share code, notes, and snippets.

@Yuyz0112
Created April 25, 2021 04:45
Show Gist options
  • Save Yuyz0112/3fdeef823d01baa17a43c378d4104c52 to your computer and use it in GitHub Desktop.
Save Yuyz0112/3fdeef823d01baa17a43c378d4104c52 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name i18n editor
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @include /?i18n-editor/
// @grant none
// @require https://cdn.jsdelivr.net/npm/jszip@3.6.0/dist/jszip.min.js
// @require https://raw.githubusercontent.com/eligrey/FileSaver.js/master/dist/FileSaver.js
// ==/UserScript==
(function () {
const { i18next, resources } = __cloudtower_i18n__;
const resourcesCopy = JSON.parse(JSON.stringify(resources));
function initEditor() {
console.log("init i18n editor");
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
iframe.style.position = "absolute";
iframe.style.top = "0";
iframe.style.right = "0";
iframe.style.height = "100%";
iframe.style.width = "240px";
iframe.style.zIndex = "999999";
iframe.style.boxShadow = "1px 1px 8px rgb(0 0 0 / 10%)";
iframe.srcdoc = `
<style>
body {
background: white;
}
.i18n-editor-tabs {
display: flex;
}
.i18n-editor-tab {
padding: 4px;
border-bottom: 2px solid lightgrey;
cursor: pointer;
}
.i18n-editor-tab.active {
border-color: black;
}
.i18n-editor-ns-title {
padding: 4px;
background: lightgrey;
cursor: pointer;
margin-bottom: 2px;
}
.i18n-editor-list {
margin: 0;
padding-inline-start: 0;
margin-top: 10px;
}
.i18n-editor-ns-list {
padding-inline-start: 0;
}
.i18n-editor-ns-list-item {
list-style: none;
width: 100%;
word-break: break-all;
}
</style>
<div id="app">
<div class="i18n-editor-controls">
<button @click="align('left')">&lt</button>
<button @click="align('right')">&gt</button>
<button @click="exportResources">export</button>
</div>
<div class="i18n-editor-tabs">
<div
class="i18n-editor-tab"
v-for="lang of langs"
:key="lang"
:class="{ active: activeLang === lang }"
@click="activeLang = lang"
>
{{ lang }}
</div>
</div>
<input type="text" v-model="search" />
<ul class="i18n-editor-list">
<ul
class="i18n-editor-ns-list"
v-for="(values, ns) in resources[activeLang]"
>
<template v-if="nsHitSearch(ns)">
<div
class="i18n-editor-ns-title"
@click="activeNs === ns ? activeNs = '' : activeNs = ns"
>
{{ ns }}
</div>
<template v-if="activeNs === ns || this.search">
<li
class="i18n-editor-ns-list-item"
v-for="(value, key) in filterValues(values)"
>
{{ key }}:<input
type="text"
:value="value"
@input="evt => handleChange(ns, key, evt.currentTarget.value)"
/>
</li>
</template>
</template>
</ul>
</ul>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
function init(resources, activeLang) {
const langs = Object.keys(resources);
const App = {
data() {
return {
resources,
langs,
activeLang,
activeNs: "",
search: "",
};
},
methods: {
nsHitSearch(ns) {
if (!this.search) {
return true;
}
const values = resources[this.activeLang][ns];
if (
Object.values(values).some((value) => value.includes(this.search))
) {
return true;
}
return false;
},
filterValues(values) {
const clone = {};
for (const key in values) {
const value = values[key];
if (value.includes(this.search)) {
clone[key] = value;
}
}
return clone;
},
handleChange(ns, key, value) {
this.resources[this.activeLang][ns][key] = value;
window.parent.postMessage({ type: 'i18n-editor-change', ns, key, value });
},
align(direction) {
switch (direction) {
case 'left':
window.frameElement.style.left = '0';
window.frameElement.style.right = '';
break;
case 'right':
window.frameElement.style.right = '0';
window.frameElement.style.left = '';
break;
default:;
}
},
exportResources() {
window.parent.postMessage({ type: 'i18n-editor-export' });
}
},
};
Vue.createApp(App).mount("#app");
}
window.addEventListener("message", (evt) => {
if (evt.data.type === "init-i18n-editor") {
init(evt.data.resources, evt.data.activeLang);
}
});
</script>
`;
iframe.onload = () => {
iframe.contentWindow.postMessage({
type: "init-i18n-editor",
resources,
activeLang: i18next.language,
});
window.addEventListener("message", (evt) => {
switch (evt.data.type) {
case "i18n-editor-change": {
const { ns, key, value } = evt.data;
i18next.addResource(i18next.language, ns, key, value);
i18next.emit("addResource");
break;
}
case "i18n-editor-export": {
const { data } = i18next.store;
const patches = {};
for (const lang in data) {
for (const ns in data[lang]) {
for (const key in data[lang][ns]) {
const value = data[lang][ns][key];
const _value = resourcesCopy[lang][ns][key];
if (value !== _value) {
const patchKey = `${lang} ${ns}`;
if (!patches[patchKey]) {
patches[patchKey] = [];
}
patches[patchKey].push({ key, value });
}
}
}
}
const zip = new JSZip();
const langFolders = {};
for (const patchKey in patches) {
const [lang, ns] = patchKey.split(" ");
if (!langFolders[lang]) {
langFolders[lang] = zip.folder(lang);
}
const copy = {} // JSON.parse(JSON.stringify(resourcesCopy[lang][ns]));
for (const patch of patches[patchKey]) {
copy[patch.key] = patch.value;
}
langFolders[lang].file(
`${ns}.json`,
JSON.stringify(copy, null, 2)
);
}
zip.generateAsync({ type: "blob" }).then(function (content) {
saveAs(content, "locales.zip");
});
break;
}
default:
break;
}
});
};
}
initEditor();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment