Explorar o código

A new faster, more modern Applocale method (#184)

* Rewrite applocale JS

* better fallback feedback

* Skip translating English
GT610 hai 1 mes
pai
achega
b2b1eccd0f

+ 6 - 0
src/web/SystemAO/locale/file_explorer.json

@@ -2,6 +2,12 @@
     "author": "tobychui",
     "version": "1.0",
     "keys": {
+        "en-us": {
+            "name": "English (US)",
+            "strings": {
+                "//": "This language is a placeholder for locale.html to read lang options properly."
+            }
+        },
         "zh-tw": {
             "name": "繁體中文(台灣)",
             "fontFamily":"\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",

+ 1 - 1
src/web/SystemAO/locale/system_settings/runtime.json

@@ -67,7 +67,7 @@
             }
         },
         "zh-cn": {
-            "name": "简体中文(简体)",
+            "name": "简体中文",
             "fwtitle" : "",
             "strings":{
                 "runtime/runtime_adjustment": "运行时配置调整",

+ 65 - 73
src/web/script/applocale.js

@@ -1,6 +1,7 @@
 /*
     Application Locale Module
     author: tobychui
+    contributor: GT610
 
     This module translate the current web application page 
     to the desired language specific by the browser.
@@ -16,104 +17,95 @@
     }
 */
 
-function NewAppLocale(){
+function NewAppLocale() {
     return {
         lang: (localStorage.getItem('global_language') == null || localStorage.getItem('global_language') == "default" ? navigator.language : localStorage.getItem('global_language')).toLowerCase(),
         localeFile: "",
         localData: {},
-        init: function(localeFile, callback = undefined) {
-            this.localeFile = localeFile;
+        _cache: new Map(), // Cache layer
+        _observer: null,  // MutationObserver
+
+        init: function(localeFile, callback) {
+            // Check memory cache
+            if (this._cache.has(localeFile)) {
+                this.localData = this._cache.get(localeFile);
+                if (callback) callback(this.localData);
+                return;
+            }
+
             let targetApplocaleObject = this;
             $.ajax({
                 dataType: "json",
                 url: localeFile,
                 success: function(data) {
+                    targetApplocaleObject._cache.set(localeFile, data); // Cache result
                     targetApplocaleObject.localData = data;
-                    if (callback != undefined) {
-                        callback(data);
-                    }
                     
-                    if (data.keys[targetApplocaleObject.lang] != undefined && data.keys[targetApplocaleObject.lang].fwtitle != undefined && data.keys[targetApplocaleObject.lang].fwtitle != "" && ao_module_virtualDesktop) {
-                        //Update the floatwindow title as well
+                    // Automatically observe DOM changes
+                    if (!targetApplocaleObject._observer) {
+                        targetApplocaleObject._observer = new MutationObserver(mutations => {
+                            mutations.forEach(mutation => {
+                                $(mutation.addedNodes).find('[locale]').addBack('[locale]').each(function() {
+                                    targetApplocaleObject._translateElement(this);
+                                });
+                            });
+                        });
+                        targetApplocaleObject._observer.observe(document, {
+                            subtree: true,
+                            childList: true
+                        });
+                    }
+
+                    if (data.keys[targetApplocaleObject.lang]?.fwtitle && ao_module_virtualDesktop) {
                         ao_module_setWindowTitle(data.keys[targetApplocaleObject.lang].fwtitle);
                     }
-    
-                    if (data.keys[targetApplocaleObject.lang] != undefined && data.keys[targetApplocaleObject.lang].fontFamily != undefined){
-                        //This language has a prefered font family. Inject it
-                        $("h1, h2, h3, p, span, div, a, button").css({
-                            "font-family":data.keys[targetApplocaleObject.lang].fontFamily
-                        });
-                        console.log("[Applocale] Updating font family to: ", data.keys[targetApplocaleObject.lang].fontFamily)
+                    
+                    if (data.keys[targetApplocaleObject.lang]?.fontFamily) {
+                        $("body").css("font-family", data.keys[targetApplocaleObject.lang].fontFamily);
                     }
-                  
+
+                    callback && callback(data);
                 }
             });
         },
+
         translate: function(targetLang = "") {
-            var targetLang = targetLang || this.lang;
-            //Check if the given locale exists
-            if (this.localData == undefined || this.localData.keys === undefined || this.localData.keys[targetLang] == undefined) {
-                console.log("[Applocale] This language is not supported. Using default")
-                return
-            }
-    
-            //Update the page content to fit the localization
-            let hasTitleLocale = (this.localData.keys[targetLang].titles !== undefined);
-            let hasStringLocale = (this.localData.keys[targetLang].strings !== undefined);
-            let hasPlaceHolderLocale = (this.localData.keys[targetLang].placeholder !== undefined);
-            let localizedDataset =  this.localData.keys[targetLang];
-            $("*").each(function() {
-                if ($(this).attr("title") != undefined && hasTitleLocale) {
-                    let targetString = localizedDataset.titles[$(this).attr("title")];
-                    if (targetString != undefined) {
-                        $(this).attr("title", targetString);
-                    }
-    
-                }
-    
-                if ($(this).attr("locale") != undefined && hasStringLocale) {
-                    let targetString = localizedDataset.strings[$(this).attr("locale")];
-                    if (targetString != undefined) {
-                        $(this).html(targetString);
-                    }
-                }
-    
-                if ($(this).attr("placeholder") != undefined && hasPlaceHolderLocale) {
-                    let targetString = localizedDataset.placeholder[$(this).attr("placeholder")];
-                    if (targetString != undefined) {
-                        $(this).attr("placeholder", targetString);
-                    }
-                }
-            })
-    
-            if (this.localData.keys[this.lang] != undefined && this.localData.keys[this.lang].fontFamily != undefined){
-                //This language has a prefered font family. Inject it
-                $("h1, h2, h3, h4, h5, p, span, div, a").css({
-                    "font-family":this.localData.keys[this.lang].fontFamily
-                });
+            const lang = targetLang || this.lang;
+            if (lang === 'en-us') return; // Don't translate English
+            if (!this.localData.keys?.[lang]) {
+                console.warn(`[Applocale] failed to load language ${lang}, falling back to default`);
+                return;
             }
+
+            // Optimize selector performance
+            const elements = document.querySelectorAll('[locale], [data-i18n]');
+            elements.forEach(el => this._translateElement(el));
         },
-        getString: function(key, original, type = "strings") {
-            var targetLang = this.lang;
-            if (this.localData.keys === undefined || this.localData.keys[targetLang] == undefined) {
-                return original;
+
+        // Use private method to translate element
+        _translateElement: function(el) {
+            const localized = this.localData.keys[this.lang];
+            const attr = el.getAttribute('locale') || el.getAttribute('data-i18n');
+            
+            if (el.hasAttribute('title') && localized?.titles?.[attr]) {
+                el.title = localized.titles[attr];
             }
-            let targetString = this.localData.keys[targetLang].strings[key];
-            if (targetString != undefined) {
-                return targetString
-            } else {
-                return original
+            if (localized?.strings?.[attr]) {
+                el.textContent = localized.strings[attr];
+            }
+            if (el.placeholder && localized?.placeholder?.[attr]) {
+                el.placeholder = localized.placeholder[attr];
             }
         },
-        applyFontStyle: function(target){
-            $(target).css({
-                "font-family":applocale.localData.keys[applocale.lang].fontFamily
-            })
+
+        // API
+        getString: function(key, original) {
+            if (this.lang === 'en-us') return original; // Directly return original if English
+            return this.localData.keys[this.lang]?.strings?.[key] || original;
         }
-    }
+    };
 }
 
-if (applocale == undefined){
-    //Only allow once instance of global applocale obj
+if (typeof applocale === 'undefined') {
     var applocale = NewAppLocale();
 }