Parcourir la source

feat(login): add locale support for login page (#197)

* feat(login): add locale support for login page

* refactor: make login locale self-contained

---------

Co-authored-by: pi-dal <pi-dal@pi-dals-MacBook-Air.local>
pi-dal il y a 1 semaine
Parent
commit
0842d24e7c
3 fichiers modifiés avec 309 ajouts et 23 suppressions
  1. 180 0
      src/web/locale/login.json
  2. 128 23
      src/web/login.system
  3. 1 0
      src/web/script/locale/login.js

+ 180 - 0
src/web/locale/login.json

@@ -0,0 +1,180 @@
+{
+    "author": "tobychui",
+    "version": "1.0",
+    "keys": {
+        "zh-tw": {
+            "name": "繁體中文(台灣)",
+            "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
+            "strings": {
+                "page/title": "ArozOS - 登入",
+                "login/description": ",使用您的使用者名稱及密碼",
+                "login/signinButton": "登入",
+                "login/oauthButton": "透過 OAuth 2.0 登入",
+                "login/ldapButton": "透過 LDAP 登入",
+                "login/rememberMe": "記住我",
+                "login/signUp": "註冊",
+                "login/forgotPassword": "忘記密碼",
+                "login/createNewSession": "建立新工作階段",
+                "login/resumableSession": "可恢復的工作階段",
+                "login/errorIncorrect": "錯誤。使用者名稱或密碼不正確。",
+                "login/requestTimestamp": "請求時間戳記",
+                "locale/language-default-text": "語言",
+                "locale/browser-default": "瀏覽器預設",
+                "login/introPrefix": "登入 ",
+                "login/introSuffix": "",
+                "login/redirectMessage": "5 秒後導向組織登入頁面...",
+                "login/cancel": "取消",
+                "page/titleSuffix": "登入"
+            },
+            "placeholder": {
+                "Username": "使用者名稱",
+                "Password": "密碼"
+            }
+        },
+        "zh-hk": {
+            "name": "繁體中文(香港)",
+            "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
+            "strings": {
+                "page/title": "ArozOS - 登入",
+                "login/description": ",使用您的使用者名稱及密碼",
+                "login/signinButton": "登入",
+                "login/oauthButton": "透過 OAuth 2.0 登入",
+                "login/ldapButton": "透過 LDAP 登入",
+                "login/rememberMe": "記住我",
+                "login/signUp": "註冊",
+                "login/forgotPassword": "忘記密碼",
+                "login/createNewSession": "建立新工作階段",
+                "login/resumableSession": "可恢復的工作階段",
+                "login/errorIncorrect": "錯誤。使用者名稱或密碼不正確。",
+                "login/requestTimestamp": "請求時間戳記",
+                "locale/language-default-text": "語言",
+                "locale/browser-default": "瀏覽器預設",
+                "login/introPrefix": "登入 ",
+                "login/introSuffix": "",
+                "login/redirectMessage": "5 秒後重新導向至組織登入頁面...",
+                "login/cancel": "取消",
+                "page/titleSuffix": "登入"
+            },
+            "placeholder": {
+                "Username": "使用者名稱",
+                "Password": "密碼"
+            }
+        },
+        "zh-cn": {
+            "name": "简体中文",
+            "fontFamily": "\"Microsoft YaHei\", \"SimHei\", \"STHeiti\"",
+            "strings": {
+                "page/title": "ArozOS - 登录",
+                "login/description": ",使用您的用户名和密码",
+                "login/signinButton": "登录",
+                "login/oauthButton": "通过 OAuth 2.0 登录",
+                "login/ldapButton": "通过 LDAP 登录",
+                "login/rememberMe": "记住我",
+                "login/signUp": "注册",
+                "login/forgotPassword": "忘记密码",
+                "login/createNewSession": "创建新会话",
+                "login/resumableSession": "可恢复的会话",
+                "login/errorIncorrect": "错误。用户名或密码不正确。",
+                "login/requestTimestamp": "请求时间戳",
+                "locale/language-default-text": "语言",
+                "locale/browser-default": "浏览器默认",
+                "login/introPrefix": "登录 ",
+                "login/introSuffix": "",
+                "login/redirectMessage": "5 秒后重定向到组织登录页面...",
+                "login/cancel": "取消",
+                "page/titleSuffix": "登录"
+            },
+            "placeholder": {
+                "Username": "用户名",
+                "Password": "密码"
+            }
+        },
+        "en-us": {
+            "name": "English (US)",
+            "fontFamily": "\"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif",
+            "strings": {
+                "page/title": "ArozOS - Login",
+                "login/description": "with your username and password",
+                "login/signinButton": "Sign In",
+                "login/oauthButton": "Sign In via OAuth 2.0",
+                "login/ldapButton": "Sign In via LDAP",
+                "login/rememberMe": "Remember Me",
+                "login/signUp": "Sign Up",
+                "login/forgotPassword": "Forgot Password",
+                "login/createNewSession": "Create New Session",
+                "login/resumableSession": "Resumable Session",
+                "login/errorIncorrect": "Error. Incorrect username or password.",
+                "login/requestTimestamp": "Request Timestamp",
+                "locale/language-default-text": "Language",
+                "locale/browser-default": "Browser Default",
+                "login/introPrefix": "Sign in to ",
+                "login/introSuffix": " ",
+                "login/redirectMessage": "Redirecting to organization sign-in page in 5 seconds...",
+                "login/cancel": "Cancel",
+                "page/titleSuffix": "Sign In"
+            },
+            "placeholder": {
+                "Username": "Username",
+                "Password": "Password"
+            }
+        },
+        "ja-jp": {
+            "name": "日本語",
+            "fontFamily": "\"Hiragino Sans\", \"Hiragino Kaku Gothic ProN\", \"Noto Sans JP\", sans-serif",
+            "strings": {
+                "page/title": "ArozOS - ログイン",
+                "login/description": "(ユーザー名とパスワードが必要です)",
+                "login/signinButton": "ログイン",
+                "login/oauthButton": "OAuth 2.0でログイン",
+                "login/ldapButton": "LDAPでログイン",
+                "login/rememberMe": "ログイン状態を保持",
+                "login/signUp": "新規登録",
+                "login/forgotPassword": "パスワードを忘れた場合",
+                "login/createNewSession": "新しいセッションを作成",
+                "login/resumableSession": "復元可能なセッション",
+                "login/errorIncorrect": "エラー。ユーザー名またはパスワードが正しくありません。",
+                "login/requestTimestamp": "リクエストタイムスタンプ",
+                "locale/language-default-text": "言語",
+                "locale/browser-default": "ブラウザデフォルト",
+                "login/introPrefix": "",
+                "login/introSuffix": " にログイン",
+                "login/redirectMessage": "5 秒後に組織のサインインページへリダイレクトします...",
+                "login/cancel": "キャンセル",
+                "page/titleSuffix": "ログイン"
+            },
+            "placeholder": {
+                "Username": "ユーザー名",
+                "Password": "パスワード"
+            }
+        },
+        "ko-kr": {
+            "name": "한국어",
+            "fontFamily": "\"Apple SD Gothic Neo\", \"Malgun Gothic\", \"Noto Sans KR\", sans-serif",
+            "strings": {
+                "page/title": "ArozOS - 로그인",
+                "login/description": " (사용자 이름과 비밀번호가 필요합니다)",
+                "login/signinButton": "로그인",
+                "login/oauthButton": "OAuth 2.0으로 로그인",
+                "login/ldapButton": "LDAP으로 로그인",
+                "login/rememberMe": "로그인 상태 유지",
+                "login/signUp": "회원가입",
+                "login/forgotPassword": "비밀번호 찾기",
+                "login/createNewSession": "새 세션 만들기",
+                "login/resumableSession": "복원 가능한 세션",
+                "login/errorIncorrect": "오류. 사용자 이름 또는 비밀번호가 올바르지 않습니다.",
+                "login/requestTimestamp": "요청 타임스탬프",
+                "locale/language-default-text": "언어",
+                "locale/browser-default": "브라우저 기본값",
+                "login/introPrefix": "",
+                "login/introSuffix": "에 로그인",
+                "login/redirectMessage": "5초 후 조직 로그인 페이지로 이동합니다...",
+                "login/cancel": "취소",
+                "page/titleSuffix": "로그인"
+            },
+            "placeholder": {
+                "Username": "사용자 이름",
+                "Password": "비밀번호"
+            }
+        }
+    }
+}

+ 128 - 23
src/web/login.system

@@ -5,11 +5,12 @@
     <meta name="robots" content="noindex" />
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <link rel="author" href="humans.txt"/>
-    <title>ArozOS - Login</title>
+    <title locale="page/title">ArozOS - Login</title>
     <link rel="stylesheet" href="script/semantic/semantic.min.css">
     <link rel="stylesheet" href="script/ao.css">
     <script type="application/javascript" src="script/jquery.min.js"></script>
     <script type="application/javascript" src="script/semantic/semantic.min.js"></script>
+    <script type="text/javascript" src="script/locale/login.js"></script>
     
     <style>
     @media only screen and (max-height: 1000px) {
@@ -158,6 +159,32 @@
         margin-top: 4em;
     }
 
+    .languageSelector{
+        position: absolute;
+        top: 1em;
+        right: 1em;
+        z-index: 1000;
+    }
+
+    .languageSelector .ui.dropdown{
+        min-width: 120px;
+        background: rgba(255, 255, 255, 0.9);
+        border: 1px solid rgba(0, 0, 0, 0.1);
+        border-radius: 0.3em;
+    }
+
+    .languageSelector .ui.dropdown .menu{
+        min-width: 140px;
+    }
+
+    @media (max-width: 600px) {
+        .languageSelector {
+            position: fixed;
+            top: 0.5em;
+            right: 0.5em;
+        }
+    }
+
     @media (orientation: landscape) and (max-height: 765px) {
         .rightLoginFrame{
             width: 500px !important;
@@ -180,33 +207,45 @@
         <div class="leftPictureFrame">
             
         </div>
+        
+        <!-- Language Selector -->
+        <div class="languageSelector">
+            <div class="ui selection dropdown">
+                <input id="language" type="hidden" name="language">
+                <i class="dropdown icon"></i>
+                <div class="default text" locale="locale/language-default-text">Language</div>
+                <div class="menu" id="langlist">
+                    <div class="item" data-value="default" locale="locale/browser-default">Browser Default</div>
+                </div>
+            </div>
+        </div>
         <div id="loginInterface" class="rightLoginFrame">
             <img class="ui medium image loginLogo" src="data:image/png;base64, {{service_logo}}">
 
             <div class="ui borderless basic segment">
-                <p>Sign in to <span class="hostname">ArozOS</span> with your username and password</p>
+                <p><span locale="login/introPrefix">Sign in to </span><span class="hostname">ArozOS</span><span locale="login/introSuffix"> </span><span locale="login/description">with your username and password</span></p>
                 
                
                 
                 <div class="ui fluid input textbox">
-                    <input id="username" type="text" placeholder="Username">
+                    <input id="username" type="text" placeholder="Username" locale="username">
                 </div>
                 <div class="ui fluid input textbox">
-                    <input id="magic" type="password" placeholder="Password">
+                    <input id="magic" type="password" placeholder="Password" locale="password">
                 </div>
 
                 <div class="ui checkbox">
                     <input id="rmbme" type="checkbox">
-                    <label for="rmbme">Remember Me</label>
+                    <label for="rmbme" locale="login/rememberMe">Remember Me</label>
                 </div>
                 
                 <br><br>
-                <button id="loginbtn" class="ui button loginbtn themecolor" style="display:inline-block;">Sign In</button>
+                <button id="loginbtn" class="ui button loginbtn themecolor" style="display:inline-block;" locale="login/signinButton">Sign In</button>
                 <div class="oauthonly" style="display:inline-block;">
-                    <a class="ui button oauthbtn subthemecolor" href="system/auth/oauth/login">Sign In via OAuth 2.0</a><br>
+                    <a class="ui button oauthbtn subthemecolor" href="system/auth/oauth/login" locale="login/oauthButton">Sign In via OAuth 2.0</a><br>
                 </div>
                 <div class="ldaponly" style="display:inline-block;">
-                    <a class="ui button oauthbtn subthemecolor" href="ldapLogin.system">Sign In via LDAP</a><br>
+                    <a class="ui button oauthbtn subthemecolor" href="ldapLogin.system" locale="login/ldapButton">Sign In via LDAP</a><br>
                 </div>
                 <div class="resumableOnly" style="display:none;">
                     <br>
@@ -216,32 +255,44 @@
                                 <img class="usericon ui circular image" src="img/public/user.svg">
                                 <div class="content" style="font-size: 95% !important;">
                                     <span class="username"><i class="ui loading spinner icon"></i></span> 
-                                    <div class="sub header usergroup"><i class="ui green check circle icon" style="margin-right: 0px;"></i> Resumable Session</div>
+                                    <div class="sub header usergroup"><i class="ui green check circle icon" style="margin-right: 0px;"></i> <span locale="login/resumableSession">Resumable Session</span></div>
                                 </div>
                             </div>
                         </div>
                     </div>
                     <br>
-                    <button class="ui subthemecolor newResumableSession button" style="color: white; display:none;"><i class="ui add icon"></i> Create New Session</button>
+                    <button class="ui subthemecolor newResumableSession button" style="color: white; display:none;"><i class="ui add icon"></i> <span locale="login/createNewSession">Create New Session</span></button>
                 </div>
 
                 <br>
                 <div class="ui breadcrumb" style="margin-top:12px;">
-                    <a class="section signup" style="cursor:pointer; display:none;" href="public/register/register.system">Sign Up</a>
+                    <a class="section signup" style="cursor:pointer; display:none;" href="public/register/register.system" locale="login/signUp">Sign Up</a>
                     <div class="divider signup"> / </div>
-                    <a  id="forgetpw" class="section" style="cursor:pointer" href="reset.system">Forgot Password</a>
+                    <a  id="forgetpw" class="section" style="cursor:pointer" href="reset.system" locale="login/forgotPassword">Forgot Password</a>
                 </div>
-                <p style="margin-top:18px;color:#ff7a70; display:none;font-size:1.2em;"><i class="remove icon"></i><span id="errmsg">Error. Incorrect username or password.</span></p>
+                <p style="margin-top:18px;color:#ff7a70; display:none;font-size:1.2em;"><i class="remove icon"></i><span id="errmsg" locale="login/errorIncorrect">Error. Incorrect username or password.</span></p>
                
             </div>
            
             <div class="bottombar">
                 © <a href="https://arozos.com">ArozOS</a> 2017 - <span class="thisyear"></span><br>
-                <small class="inversehighlight" style="font-size: 80%">Request Timestamp: <span id="requestTime"></span> | <span id="requestHostCommonName"></span></small>
+                <small class="inversehighlight" style="font-size: 80%"><span locale="login/requestTimestamp">Request Timestamp</span>: <span id="requestTime"></span> | <span id="requestHostCommonName"></span></small>
             </div>
         </div>
         
     <script>
+        var localeManager = window.LoginLocale || null;
+        if (localeManager && typeof localeManager.init === "function") {
+            localeManager.init();
+        }
+
+        function localeGetString(key, fallback) {
+            if (localeManager && typeof localeManager.getString === "function") {
+                return localeManager.getString(key, fallback);
+            }
+            return fallback;
+        }
+
         var redirectionAddress = "{{redirection_addr}}";
         var loginAddress = "{{login_addr}}";
         var systemUserCount = "{{usercount}}" - 0; //Magic way to convert string to int :)
@@ -284,6 +335,59 @@
             $("#requestTime").text(datetime);
             $("#requestHostCommonName").text(location.hostname);
             $(".ui.checkbox").checkbox();
+            
+            // Initialize language dropdown using settings method
+            var storedLanguage = localStorage.getItem('global_language') || 'default';
+            $("#language").val(storedLanguage);
+            $('.selection.dropdown').dropdown({
+                onChange: function(value) {
+                    localStorage.setItem('global_language', value);
+                    location.reload();
+                }
+            });
+
+            function populateLanguageOptions() {
+                if (!localeManager || typeof localeManager.getAvailableLocales !== 'function') {
+                    return;
+                }
+                var locales = localeManager.getAvailableLocales();
+                for (var key in locales) {
+                    if (!Object.prototype.hasOwnProperty.call(locales, key)) {
+                        continue;
+                    }
+                    if (!$("#langlist .item[data-value='" + key + "']").length) {
+                        var value = locales[key];
+                        var langName = (value && value.name) ? value.name : key;
+                        $("#langlist").append('<div class="item" data-value="' + key + '">' + langName + '</div>');
+                    }
+                }
+                var currentLang = localStorage.getItem('global_language') || 'default';
+                $("#language").val(currentLang);
+                $('.selection.dropdown').dropdown('refresh');
+                $('.selection.dropdown').dropdown('set selected', currentLang);
+            }
+
+            function updateHostnameAndTitle() {
+                $.get("system/info/getArOZInfo", function(data){
+                    if (data && data.HostName){
+                        $(".hostname").text(data.HostName);
+                        var titleSuffix = localeGetString('page/titleSuffix', 'Sign In');
+                        document.title = data.HostName + " - " + titleSuffix;
+                    }
+                });
+            }
+
+            var runLocaleEnhancements = function () {
+                populateLanguageOptions();
+                updateHostnameAndTitle();
+            };
+
+            if (localeManager && typeof localeManager.onReady === 'function') {
+                localeManager.onReady(runLocaleEnhancements);
+            } else {
+                runLocaleEnhancements();
+            }
+            
             //Check if the user already logged in
             $.get("system/auth/checkLogin",function(data){
                 try{
@@ -333,7 +437,9 @@
                     if(document.referrer != window.location.origin + "/desktop.system" && document.referrer != window.location.origin + "/mobile.system" && path.origin + path.pathname !=  window.location.origin + "/system/auth/oauth/authorize"){
                         $(".ts.borderless.basic.segment").attr("style","display: none;");
                         $(".ts.borderless.basic.segment").attr("id","aoLogin");
-                        $(".ts.borderless.basic.segment").after('<div id="autoRedirectSegment" class="ui borderless basic segment"><p><i class="key icon"></i>Redirecting to organization sign-in page in 5 seconds...</p><br><a style="cursor: pointer;" onclick="stopAutoRedirect()">Cancel</a></div>');
+                        var redirectMessage = localeGetString('login/redirectMessage', 'Redirecting to organization sign-in page in 5 seconds...');
+                        var cancelText = localeGetString('login/cancel', 'Cancel');
+                        $(".ts.borderless.basic.segment").after('<div id="autoRedirectSegment" class="ui borderless basic segment"><p><i class="key icon"></i>' + redirectMessage + '</p><br><a style="cursor: pointer;" onclick="stopAutoRedirect()">' + cancelText + '</a></div>');
                         autoRedirectTimer = setTimeout(function(){
                             window.location.href = "system/auth/oauth/login?redirect=" + redirectionAddress;
                         }, 3000);
@@ -382,11 +488,6 @@
                 $(".section.signin").attr("href","system/auth/oauth/login?redirect=" + redirectionAddress);
             }
 
-            //Get the system hostname and replace the hostname fields
-            $.get("system/info/getArOZInfo", function(data){
-                document.title = data.HostName + " - Sign In"
-                $(".hostname").text(data.HostName);
-            });
         });
 
         //Event handlers for buttons
@@ -415,8 +516,12 @@
             $("input").addClass('disabled');
             $.post(loginAddress, {"username": username, "password": magic, "rmbme": rmbme}).done(function(data){
                 if (data.error !== undefined){
-                    //Something went wrong during the login
-                    $("#errmsg").text(data.error);
+                    //Something went wrong during the login  
+                    var errorMsg = data.error;
+                    if (data.error === "Error. Incorrect username or password.") {
+                        errorMsg = localeGetString('login/errorIncorrect', data.error);
+                    }
+                    $("#errmsg").text(errorMsg);
                     $("#errmsg").parent().stop().finish().slideDown('fast').delay(5000).slideUp('fast');
                 }else if(data.redirect !== undefined){
                     //LDAP Related Code
@@ -464,4 +569,4 @@
         });
     </script>
     </body>
-</html>
+</html>

Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/web/script/locale/login.js


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff