🐣

手書きのHTMLを爆速で多言語化する

2023/04/21に公開

ちょろっと作った手書きのウェブページをサクッと多言語化したいことはありませんか?
翻訳用のデータを作るほどでもなく、従来の自動翻訳では満足できない時に使えそうなスクリプトができたので供養します。

https://github.com/nyatla/MachML

MachML

このスクリプトを使うと、HTMLタグに翻訳文をその場で併記することができます。
こんなかんじに。

<ml-text
	en="This is English"
	ja="日本語です"
	ja-kyoto="京都どすえ"
	neko="にゃーん"
></ml-text>

ブロック要素もできます。

<ml-block >
    <p data-lang="en">
	The language is specified by language-locale, and the default value is based on the browser's settings.
    </p>
    <p data-lang="ja">
	表示言語は、言語-ロケールで指定します。初期値はブラウザの規定値です。</br>
    </p>
    <span data-lang="neko">にゃにゃにゃにゃーん、<span style="font-size: 2em;">にゃーん</span>にゃにゃにゃにゃーん</span>                
</ml-block>

言語を選択するための簡易ボタン貼り付けられます。
(Javascriptから操作することもできます。)

<ml-select langs="en ja-jp ja-kyoto neko"></ml-select>

ライブデモ

全ての機能を使ったサンプルはここにあります。
https://nyatla.jp/machml/test/tag.html

(https://nyatla.jp/machml/test/tag.html)

せつめい

MachMLには、文章併記のための2つの拡張タグ、簡易UIのための2つのタグ、そして1つのAPIがあります。簡単な言語選択と翻訳だけならJavascriptをロードするだけでOKです。

ml-text

ロケールごとの文字列を併記するためのタグです。属性にロケール名、値に表示する文章を書きます。

<ml-text
    en="This is English"
    ja="日本語です"
    ja-kyoto="京都どすえ"
></ml-text>

最初に定義した属性がデフォルト値になります。指定したロケールが存在しない場合、デフォルト値を表示します。

ml-block

ロケールごとのHTMLを併記するためのタグです。直下のタグのうち、data-lang属性を持つタグの中からロケール名に一致する要素だけを表示します。

<ml-block >
    <p data-lang="en"><i>Hello World</i></p>
    <p data-lang="ja">こんにちは<b>世界</b></p>
</ml-block>

最初に定義した属性がデフォルト値になります。指定したロケールが存在しない場合、デフォルト値を表示します。

ml-select

簡易的な言語選択ボタンを表示するには、ml-selectタグを使います。

<ml-select langs="en ja-jp ja-kyoto"></ml-select>

langs属性にロケールの選択肢を設定します。

API

このスクリプトは自動で言語とロケールをブラウザの環境変数から判定しますが、手動で言語を切り替えたい場合は、MachMLオブジェクトを使います。

次のコードは、表示言語を'en'に切り替えます。

MachML.setLocale('en');

htmlへの埋め込み

scriptタグでロードする場合は、ページの先頭で読み込んでください。

<script src="./machml.js"></script>

別にファイルを作りたくないときは、machml.min.jsをページの先頭にベタ書きしてください。

<script>
(()=>{let n=0,l=[],a=navigator.language.toLowerCase().replace("_","-");console.log(a);class e extends HTMLElement{constructor(){super(),l.push(this)}_update(){var e,t=a,s=[];for(e of this.attributes)s.push(e.name);if(0==s.length)this.innerText="";else{let e=s.indexOf(t);e<0&&(e=s.indexOf(t.split("-")[0]))<0&&(e=0),this.innerText=this.attributes[e].value}}connectedCallback(){this._update()}}customElements.define("ml-text",e);class t extends HTMLElement{constructor(){super(),l.push(this);let s=this;new MutationObserver(e=>{for(const t of e)if("childList"===t.type&&t.addedNodes.length){s._update();break}}).observe(this,{childList:!0}),this._cache=[],this._display=[],this._langs=[]}_update(){console.log("MachML update!");var l=[];for(let e=0;e<this.children.length;e++)this.children[e].hasAttribute("data-lang")&&l.push(this.children[e]);var n=this._cache,i=this._display,e=this._langs;for(let s=n.length-1;0<=s;s--){let t=!1;for(let e=0;e<l.length;e++)Object.is(l[e],n[s])&&(t=!0);t||(n.splice(s,1),i.splice(s,1),e.splice(s,1))}for(let s=0;s<l.length;s++){let t=!1;for(let e=0;e<n.length;e++)n[e]===l[s]&&(t=!0);t||(n.push(l[s]),i.push(""),e.push(l[s].attributes["data-lang"].value))}var s=a;if(!(n.length<=1)){let t=e.indexOf(s);t<0&&(t=e.indexOf(s.split("-")[0]))<0&&(t=0);for(let e=0;e<n.length;e++)n[e].style.display=e==t?i[e]:"none"}}}customElements.define("ml-block",t);class s extends HTMLElement{connectedCallback(){var e="_MLSelector-"+n++,t=this.attributes.langs.value.split(" ");let s=`<select id="${e}">`;for(let e=0;e<t.length;e++)s+=`<option value="${t[e]}">${t[e]}</option>`;s+="</select>",this.innerHTML=s;let l=document.getElementById(e);l.selectedIndex=t.indexOf(a),l.addEventListener("change",()=>{MachML.setLocale(l.options[l.selectedIndex].text)})}}customElements.define("ml-select",s),MachML={version:"MachML/0.1.0",setLocale:e=>{a=e;for(var t of l)t._update()}}})();
</script>

https://github.com/nyatla/MachML/blob/main/src/machml.min.js

上記ページのコピーボタンからコピーできます。

見た目を四角く加工したスクリプトもあります。(120×17行)
https://github.com/nyatla/MachML/blob/main/src/machml.min.tile.js

仕組み

このスクリプトは、ブラウザのカスタム要素とMutationObserverの機能を使って実装しました。
DOM走査がないのでとても軽量です。

わからないことはChatGPTさんが全て教えてくれました。

Discussion