Created
April 25, 2021 04:45
-
-
Save Yuyz0112/3fdeef823d01baa17a43c378d4104c52 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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/[email protected]/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')"><</button> | |
<button @click="align('right')">></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