diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..20aa8bf18 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /src/main/resources/locales/language_zh-CN.json + translation: /src/main/resources/locales/language_%locale%.json diff --git a/src/main/java/com/rebuild/api/LoginToken.java b/src/main/java/com/rebuild/api/LoginToken.java index b3e5da258..4232c8ba2 100644 --- a/src/main/java/com/rebuild/api/LoginToken.java +++ b/src/main/java/com/rebuild/api/LoginToken.java @@ -24,6 +24,7 @@ import cn.devezhao.persist4j.engine.ID; import com.alibaba.fastjson.JSON; import com.rebuild.server.Application; import com.rebuild.server.helper.SysConfiguration; +import com.rebuild.server.helper.language.Languages; import com.rebuild.server.service.bizz.privileges.User; import com.rebuild.server.service.bizz.privileges.ZeroEntry; import com.rebuild.utils.JSONUtils; @@ -53,7 +54,7 @@ public class LoginToken extends BaseApi { String password = context.getParameterNotBlank("password"); if (COUNTER.counter(user).add().seconds(30).than(3)) { - return formatFailure("超出请求频率", ApiInvokeException.ERR_FREQUENCY); + return formatFailure("Request frequency exceeded", ApiInvokeException.ERR_FREQUENCY); } String hasError = checkUser(user, password); @@ -92,13 +93,13 @@ public class LoginToken extends BaseApi { */ public static String checkUser(String user, String password) { if (!Application.getUserStore().exists(user)) { - return "用户名或密码错误"; + return Languages.lang("InputWrong", "UsernameOrPassword"); } User loginUser = Application.getUserStore().getUser(user); if (!loginUser.isActive() || !Application.getSecurityManager().allowed(loginUser.getId(), ZeroEntry.AllowLogin)) { - return "用户未激活或不允许登录"; + return Languages.lang("UnactiveUserTip"); } Object[] foundUser = Application.createQueryNoFilter( @@ -108,7 +109,7 @@ public class LoginToken extends BaseApi { .unique(); if (foundUser == null || !foundUser[0].equals(EncryptUtils.toSHA256Hex(password))) { - return "用户名或密码错误"; + return Languages.lang("InputWrong", "UsernameOrPassword"); } return null; diff --git a/src/main/java/com/rebuild/server/helper/SMSender.java b/src/main/java/com/rebuild/server/helper/SMSender.java index 616fdc1e6..182741ab9 100644 --- a/src/main/java/com/rebuild/server/helper/SMSender.java +++ b/src/main/java/com/rebuild/server/helper/SMSender.java @@ -138,8 +138,8 @@ public class SMSender { * @throws IOException */ protected static Element getMailTemplate() throws IOException { - File temp = SysConfiguration.getFileOfRes("locales/mail-notify.html"); - Document html = Jsoup.parse(temp, "utf-8"); + File tmp = SysConfiguration.getFileOfRes("locales/email_zh-CN.html"); + Document html = Jsoup.parse(tmp, "utf-8"); return html.body(); } diff --git a/src/main/java/com/rebuild/server/helper/language/LanguageBundle.java b/src/main/java/com/rebuild/server/helper/language/LanguageBundle.java new file mode 100644 index 000000000..c4944e60c --- /dev/null +++ b/src/main/java/com/rebuild/server/helper/language/LanguageBundle.java @@ -0,0 +1,163 @@ +/* +rebuild - Building your business-systems freely. +Copyright (C) 2018-2019 devezhao + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package com.rebuild.server.helper.language; + +import cn.devezhao.commons.EncryptUtils; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.rebuild.utils.JSONable; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 语言包 + * + * @author ZHAO + * @since 2019/10/31 + */ +public class LanguageBundle implements JSONable { + + final private String locale; + final private JSONObject bundle; + private String bundleHash; + + final private Languages parent; + + /** + * @param locale + * @param bundle + * @param parent + */ + protected LanguageBundle(String locale, JSONObject bundle, Languages parent) { + this.locale = locale; + this.bundle = this.merge(bundle); + this.parent = parent; + } + + private static final Pattern VAR_PATTERN = Pattern.compile("\\{([0-9a-zA-Z]+)\\}"); + /** + * 合并语言中的变量 + * + * @param bundle + * @return + */ + private JSONObject merge(JSONObject bundle) { + String bundleString = bundle.toJSONString(); + Matcher matcher = VAR_PATTERN.matcher(bundleString); + while (matcher.find()) { + String var = matcher.group(1); + String lang = bundle.getString(var); + if (lang != null) { + bundleString = bundleString.replace("{" + var +"}", lang); + } + } + + this.bundleHash = EncryptUtils.toMD5Hex(bundleString); + return JSON.parseObject(bundleString); + } + + /** + * @return + */ + public String locale() { + return locale; + } + + /** + * @param key + * @return + */ + public String lang(String key) { + return lang(key, ArrayUtils.EMPTY_STRING_ARRAY); + } + + /** + * @param key + * @param indexKeys 替换语言中的占位符 {0} + * @return + */ + public String lang(String key, String... indexKeys) { + String lang = getLang(key); + if (indexKeys == null || indexKeys.length == 0) { + return lang; + } + + int index = 0; + for (String ik : indexKeys) { + String iLang = getLang(ik); + if (iLang != null) { + lang = lang.replace("{" + index++ + "}", iLang); + } + } + return lang; + } + + /** + * @param key + * @return + */ + private String getLang(String key) { + String lang = bundle.getString(key); + if (lang == null) { + String d = String.format("[%s]", key.toUpperCase()); + if (parent != null) { + return parent.getDefaultBundle().getLang(key, d); + } else { + return d; + } + } + return lang; + } + + /** + * @param key + * @param defaultLang + * @return + */ + private String getLang(String key, String defaultLang) { + return StringUtils.defaultIfEmpty(bundle.getString(key), defaultLang); + } + + /** + * Hash for bundle + * + * @return + */ + public String getBundleHash() { + return bundleHash; + } + + @Override + public JSON toJSON() { + return bundle; + } + + @Override + public JSON toJSON(String... special) { + return toJSON(); + } + + @Override + public String toString() { + return super.toString() + "#" + locale() + ":" + bundle.size(); + } +} diff --git a/src/main/java/com/rebuild/server/helper/language/Languages.java b/src/main/java/com/rebuild/server/helper/language/Languages.java new file mode 100644 index 000000000..bc8db1a6c --- /dev/null +++ b/src/main/java/com/rebuild/server/helper/language/Languages.java @@ -0,0 +1,146 @@ +/* +rebuild - Building your business-systems freely. +Copyright (C) 2018-2019 devezhao + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package com.rebuild.server.helper.language; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.rebuild.server.Application; +import com.rebuild.server.RebuildException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** + * 多语言 + * + * @author ZHAO + * @since 2019/10/31 + */ +public class Languages { + + private static final Log LOG = LogFactory.getLog(Languages.class); + + public static final Languages instance = new Languages(); + private Languages() { + this.reset(); + } + + /** + * 默认语言 + */ + public static final String DEFAULT_LOCALE = "zh-CN"; + /** + * 语言文件前缀 + */ + private static final String LB_PREFIX = "language_"; + + private Map bundleMap = new HashMap<>(); + + /** + */ + public void reset() { + try { + File[] files = ResourceUtils.getFile("classpath:locales/") + .listFiles((dir, name) -> name.startsWith(LB_PREFIX) && name.endsWith(".json")); + for (File file : Objects.requireNonNull(files)) { + String locale = file.getName().substring(LB_PREFIX.length()); + locale = locale.split("\\.")[0]; + + try (InputStream is = new FileInputStream(file)) { + LOG.info("Loading language bundle : " + locale); + JSONObject o = JSON.parseObject(is, null); + bundleMap.remove(locale); + bundleMap.put(locale, new LanguageBundle(locale, o, this)); + } + } + } catch (Exception ex) { + throw new RebuildException("Load language bundle failure!", ex); + } + } + + /** + * @param locale + * @return + */ + public LanguageBundle getBundle(Locale locale) { + return getBundle(locale == null ? null : locale.toString()); + } + + /** + * @param locale + * @return + */ + public LanguageBundle getBundle(String locale) { + if (locale != null) { + locale = locale.replace("_", "-"); + } + if (locale != null && bundleMap.containsKey(locale)) { + return bundleMap.get(locale); + } else { + return getDefaultBundle(); + } + } + + /** + * 默认语言包 + * + * @return + */ + public LanguageBundle getDefaultBundle() { + return bundleMap.get(DEFAULT_LOCALE); + } + + /** + * 当前用户语言包 + * + * @return + */ + public LanguageBundle getCurrentBundle() { + return getBundle(Application.getSessionStore().getLocale()); + } + + /** + * 是否为可用语言 + * + * @param locale + * @return + */ + public boolean isAvailable(String locale) { + return bundleMap.containsKey(locale.replace("_", "-")); + } + + // -- Quick Methods + + /** + * @param key + * @param insideKeys + * @return + */ + public static String lang(String key, String...insideKeys) { + return instance.getCurrentBundle().lang(key, insideKeys); + } +} diff --git a/src/main/java/com/rebuild/utils/AppUtils.java b/src/main/java/com/rebuild/utils/AppUtils.java index 9cb603dc6..497277142 100644 --- a/src/main/java/com/rebuild/utils/AppUtils.java +++ b/src/main/java/com/rebuild/utils/AppUtils.java @@ -171,4 +171,17 @@ public class AppUtils { public static boolean allowed(HttpServletRequest request, ZeroEntry entry) { return Application.getSecurityManager().allowed(getRequestUser(request), entry); } + + public static final String SK_LOCALE = WebUtils.KEY_PREFIX + ".LOCALE"; + /** + * @param request + * @return + */ + public static String getLocale(HttpServletRequest request) { + String locale = (String) ServletUtils.getSessionAttribute(request, SK_LOCALE); + if (locale == null) { + locale = request.getLocale().toString(); + } + return locale; + } } diff --git a/src/main/java/com/rebuild/web/BaseControll.java b/src/main/java/com/rebuild/web/BaseControll.java index 807ca60f0..c233ea040 100644 --- a/src/main/java/com/rebuild/web/BaseControll.java +++ b/src/main/java/com/rebuild/web/BaseControll.java @@ -22,6 +22,8 @@ import cn.devezhao.commons.web.ServletUtils; import cn.devezhao.persist4j.engine.ID; import com.alibaba.fastjson.JSON; import com.rebuild.api.Controll; +import com.rebuild.server.helper.language.LanguageBundle; +import com.rebuild.server.helper.language.Languages; import com.rebuild.utils.AppUtils; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; @@ -43,11 +45,20 @@ public abstract class BaseControll extends Controll { * @return */ protected ID getRequestUser(HttpServletRequest request) { - ID userId = AppUtils.getRequestUser(request); - if (userId == null) { + ID user = AppUtils.getRequestUser(request); + if (user == null) { throw new IllegalParameterException("无效请求用户"); } - return userId; + return user; + } + + /** + * @param request + * @return + */ + protected LanguageBundle getBundle(HttpServletRequest request) { + String locale = AppUtils.getLocale(request); + return Languages.instance.getBundle(locale); } /** diff --git a/src/main/java/com/rebuild/web/BasePageControll.java b/src/main/java/com/rebuild/web/BasePageControll.java index c54f1eea5..8754f434b 100644 --- a/src/main/java/com/rebuild/web/BasePageControll.java +++ b/src/main/java/com/rebuild/web/BasePageControll.java @@ -18,6 +18,9 @@ along with this program. If not, see . package com.rebuild.web; +import com.rebuild.server.Application; +import com.rebuild.server.helper.language.LanguageBundle; +import com.rebuild.server.helper.language.Languages; import org.springframework.web.servlet.ModelAndView; /** @@ -33,6 +36,12 @@ public abstract class BasePageControll extends BaseControll { * @return */ protected ModelAndView createModelAndView(String page) { - return new ModelAndView(page); + ModelAndView mv = new ModelAndView(page); + + // 语言包 + String locale = Application.getSessionStore().getLocale(); + LanguageBundle bundle = Languages.instance.getBundle(locale); + mv.getModel().put("bundle", bundle); + return mv; } } diff --git a/src/main/java/com/rebuild/web/OnlineSessionStore.java b/src/main/java/com/rebuild/web/OnlineSessionStore.java index 1fe98d9eb..2cf755dee 100644 --- a/src/main/java/com/rebuild/web/OnlineSessionStore.java +++ b/src/main/java/com/rebuild/web/OnlineSessionStore.java @@ -23,10 +23,12 @@ import cn.devezhao.commons.web.WebUtils; import cn.devezhao.persist4j.Record; import cn.devezhao.persist4j.engine.ID; import com.rebuild.server.Application; +import com.rebuild.server.helper.language.Languages; import com.rebuild.server.metadata.EntityHelper; import com.rebuild.server.service.bizz.CurrentCaller; import com.rebuild.server.service.bizz.UserService; import com.rebuild.web.user.signin.LoginControll; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; @@ -53,6 +55,8 @@ public class OnlineSessionStore extends CurrentCaller implements HttpSessionList private static final Set ONLINE_SESSIONS = new CopyOnWriteArraySet<>(); private static final Map ONLINE_USERS = new ConcurrentHashMap<>(); + + private static final ThreadLocal LOCALE = new ThreadLocal<>(); @Override public void sessionCreated(HttpSessionEvent event) { @@ -89,8 +93,6 @@ public class OnlineSessionStore extends CurrentCaller implements HttpSessionList } } - // -- - /** * 最近访问时间 */ @@ -126,9 +128,40 @@ public class OnlineSessionStore extends CurrentCaller implements HttpSessionList public void storeLoginSuccessed(HttpServletRequest request) { HttpSession s = request.getSession(); Object loginUser = s.getAttribute(WebUtils.CURRENT_USER); - Assert.notNull(loginUser, "No login user found"); + Assert.notNull(loginUser, "No login user found in session!"); ONLINE_SESSIONS.remove(s); ONLINE_USERS.put((ID) loginUser, s); } + + /** + * @param locale + */ + public void setLocale(String locale) { + LOCALE.set(locale); + } + + /** + * @return Returns default if unset + * @see Languages + */ + public String getLocale() { + return StringUtils.defaultIfEmpty(LOCALE.get(), Languages.DEFAULT_LOCALE); + } + + /** + * @param caller + * @param locale + * @see #set(ID) + */ + public void set(ID caller, String locale) { + super.set(caller); + this.setLocale(locale); + } + + @Override + public void clean() { + super.clean(); + LOCALE.remove(); + } } diff --git a/src/main/java/com/rebuild/web/RebuildExceptionResolver.java b/src/main/java/com/rebuild/web/RebuildExceptionResolver.java index 776efb078..f643d9781 100644 --- a/src/main/java/com/rebuild/web/RebuildExceptionResolver.java +++ b/src/main/java/com/rebuild/web/RebuildExceptionResolver.java @@ -39,7 +39,7 @@ public class RebuildExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - LOG.error("Handler - " + handler + "\nException - " + ex); + LOG.error("\nHandler : " + handler + "\nException : ", ex); return null; } } diff --git a/src/main/java/com/rebuild/web/RequestWatchHandler.java b/src/main/java/com/rebuild/web/RequestWatchHandler.java index 16b1177fb..fa22cc8fd 100644 --- a/src/main/java/com/rebuild/web/RequestWatchHandler.java +++ b/src/main/java/com/rebuild/web/RequestWatchHandler.java @@ -62,6 +62,10 @@ public class RequestWatchHandler extends HandlerInterceptorAdapter { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { response.setCharacterEncoding("utf-8"); + request.getSession(true); + + // for Language + Application.getSessionStore().setLocale(AppUtils.getLocale(request)); final String requestUrl = request.getRequestURI(); if (noCache && !(ServletUtils.isAjaxRequest(request) @@ -205,6 +209,6 @@ public class RequestWatchHandler extends HandlerInterceptorAdapter { reqUrl = reqUrl.replaceFirst(ServerListener.getContextPath(), ""); return reqUrl.startsWith("/gw/") || reqUrl.startsWith("/assets/") || reqUrl.startsWith("/error/") || reqUrl.startsWith("/t/") || reqUrl.startsWith("/s/") - || reqUrl.startsWith("/setup/"); + || reqUrl.startsWith("/setup/") || reqUrl.startsWith("/language/"); } } diff --git a/src/main/java/com/rebuild/web/common/LanguagesControll.java b/src/main/java/com/rebuild/web/common/LanguagesControll.java new file mode 100644 index 000000000..89d027172 --- /dev/null +++ b/src/main/java/com/rebuild/web/common/LanguagesControll.java @@ -0,0 +1,74 @@ +/* +rebuild - Building your business-systems freely. +Copyright (C) 2018-2019 devezhao + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package com.rebuild.web.common; + +import cn.devezhao.commons.CodecUtils; +import cn.devezhao.commons.web.ServletUtils; +import com.rebuild.server.helper.language.LanguageBundle; +import com.rebuild.server.helper.language.Languages; +import com.rebuild.utils.AppUtils; +import com.rebuild.web.BaseControll; +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static com.rebuild.utils.AppUtils.SK_LOCALE; + +/** + * 语言控制 + * + * @author devezhao + * @since 2019/11/29 + */ +@Controller +@RequestMapping("/language/") +public class LanguagesControll extends BaseControll { + + @RequestMapping(value = "bundle", method = RequestMethod.GET) + public void getLanguageBundle(HttpServletRequest request, HttpServletResponse response) throws IOException { + final LanguageBundle bundle = getBundle(request); + // HTTP Headers + response.addHeader("Cache-Control", "max-age=60, must-revalidate"); + response.addHeader("ETag", "W/" + bundle.getBundleHash()); + response.setContentType(ServletUtils.CT_JS); + + ServletUtils.write(response, "__LANGBUNDLE__ = " + bundle.toJSON().toJSONString()); + } + + @RequestMapping("select") + public void selectLanguage(HttpServletRequest request, HttpServletResponse response) throws IOException { + String locale = request.getParameter("locale"); + if (locale != null && Languages.instance.isAvailable(locale)) { + if (AppUtils.devMode()) Languages.instance.reset(); + ServletUtils.setSessionAttribute(request, SK_LOCALE, locale); + } + + if (ServletUtils.isAjaxRequest(request)) { + writeSuccess(response); + } else { + String nexturl = StringUtils.defaultIfBlank(request.getParameter("nexturl"), AppUtils.getContextPath()); + response.sendRedirect(CodecUtils.urlDecode(nexturl)); + } + } +} diff --git a/src/main/java/com/rebuild/web/user/signin/LoginControll.java b/src/main/java/com/rebuild/web/user/signin/LoginControll.java index 788292d4b..d0bc7aa9a 100644 --- a/src/main/java/com/rebuild/web/user/signin/LoginControll.java +++ b/src/main/java/com/rebuild/web/user/signin/LoginControll.java @@ -50,6 +50,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import static com.rebuild.server.helper.language.Languages.lang; + /** * @author zhaofang123@gmail.com * @since 07/25/2018 @@ -132,10 +134,10 @@ public class LoginControll extends BasePageControll { Boolean needVcode = (Boolean) ServletUtils.getSessionAttribute(request, NEED_VCODE); if (needVcode != null && needVcode && (StringUtils.isBlank(vcode) || !CaptchaUtil.ver(vcode, request))) { - writeFailure(response, "验证码错误"); + writeFailure(response, lang("InputWrong", "Captcha")); return; } - + final String user = getParameterNotNull(request, "user"); final String password = getParameterNotNull(request, "passwd"); @@ -248,23 +250,23 @@ public class LoginControll extends BasePageControll { @RequestMapping("user-forgot-passwd") public void userForgotPasswd(HttpServletRequest request, HttpServletResponse response) { if (!SMSender.availableMail()) { - writeFailure(response, "邮件服务账户未配置,请联系管理员配置"); + writeFailure(response, lang("EmailAccountUnset")); return; } - + String email = getParameterNotNull(request, "email"); if (!RegexUtils.isEMail(email) || !Application.getUserStore().existsEmail(email)) { - writeFailure(response, "无效邮箱"); + writeFailure(response, lang("InputInvalid", "Email")); return; } - + String vcode = VCode.generate(email, 2); - String content = "

你的重置密码验证码是 " + vcode + "

"; - String sentid = SMSender.sendMail(email, "重置密码", content); + String content = String.format(lang("YourVcodeForResetPassword"), vcode); + String sentid = SMSender.sendMail(email, lang("ResetPassword"), content); if (sentid != null) { writeSuccess(response); } else { - writeFailure(response, "无法发送验证码,请稍后重试"); + writeFailure(response); } } @@ -275,7 +277,7 @@ public class LoginControll extends BasePageControll { String email = data.getString("email"); String vcode = data.getString("vcode"); if (!VCode.verfiy(email, vcode, true)) { - writeFailure(response, "验证码无效"); + writeFailure(response, lang("InputInvalid", "Vcode")); return; } diff --git a/src/main/resources/locales/mail-notify.html b/src/main/resources/locales/email_zh-CN.html similarity index 95% rename from src/main/resources/locales/mail-notify.html rename to src/main/resources/locales/email_zh-CN.html index d986bc263..af9a95488 100644 --- a/src/main/resources/locales/mail-notify.html +++ b/src/main/resources/locales/email_zh-CN.html @@ -2,7 +2,7 @@ - Mail Notification Template + EMail Template

diff --git a/src/main/resources/locales/language_en-US.json b/src/main/resources/locales/language_en-US.json new file mode 100644 index 000000000..b38e43803 --- /dev/null +++ b/src/main/resources/locales/language_en-US.json @@ -0,0 +1,44 @@ +{ + "rebuild": "Building your business-systems freely", + "User": "User", + "Login": "Login", + "Username": "Username", + "Captcha": "Captcha", + "Vcode": "Code", + "Password": "Password", + "Email": "Email", + "Signup": "Sign Up", + "UsernameOrEmail": "Username (or email)", + "LoginPassword": "Password", + "InputCaptcha": "Enter captcha", + "ClickReload": "Click to reload", + "RememberMe": "Remember me", + "NoAccountYet": "Don't have an account yet?", + "SignupNow": "Sign Up", + "ForgotPassword": "Forgot password", + "ResetPassword": "Reset password", + "InputUserOrPasswordPls": "Please enter username and password", + "InputCaptchaPls": "Please enter captcha", + "InputWrong": "{0} was invalid", + "InputInvalid": "{0} was invalid", + "UsernameOrPassword": "Username or password", + "UnactiveUserTip": "User is not activated or is not allowed to login", + "ReturnHome": "Return to Home", + "ReportIssue": "Report an issue", + "SignupNotOpenTip": "Administrator has not opened public signup", + "UnsetEmailTip": "If you forgot or did not configure your email, please contact the administrator to reset your password", + "InputEmailPls": "Please enter email", + "InputNewPasswordPls": "Enter new password", + "PasswordNotMatch": "New passwords entered twice are inconsistent", + "NewPassword": "New password", + "RepeatNewPassword": "New password again", + "GetVcode": "Send code", + "GetVcode2": "Resend", + "VcodeEmailSent": "Verification code has been sent to your mailbox", + "ConfirmReset": "Confirm", + "ActionSuccess": "Operation succeeded", + "EmailAccountUnset": "Mail service account is not configured, please contact administrator to configure", + "YourVcodeForResetPassword": "

Your reset password verification code is %s

", + "InputVcodePls": "Please enter code" + +} \ No newline at end of file diff --git a/src/main/resources/locales/language_ja-JP.json b/src/main/resources/locales/language_ja-JP.json new file mode 100644 index 000000000..2c4677f78 --- /dev/null +++ b/src/main/resources/locales/language_ja-JP.json @@ -0,0 +1,3 @@ +{ + "rebuild": "高度にカスタマイズ可能なエンタープライズ管理システム" +} \ No newline at end of file diff --git a/src/main/resources/locales/language_zh-CN.json b/src/main/resources/locales/language_zh-CN.json new file mode 100644 index 000000000..cfd50c275 --- /dev/null +++ b/src/main/resources/locales/language_zh-CN.json @@ -0,0 +1,44 @@ +{ + "rebuild": "高度可定制化的企业管理系统", + "User": "用户", + "Login": "登录", + "Username": "用户名", + "Captcha": "验证码", + "Vcode": "验证码", + "Password": "密码", + "Email": "邮箱", + "Signup": "注册", + "UsernameOrEmail": "用户名 (或邮箱)", + "LoginPassword": "登录密码", + "InputCaptcha": "输入验证码", + "ClickReload": "点击刷新", + "RememberMe": "记住登录", + "NoAccountYet": "还没有账号?", + "SignupNow": "立即注册", + "ForgotPassword": "找回密码", + "ResetPassword": "重置密码", + "InputUserOrPasswordPls": "请输入用户名和密码", + "InputCaptchaPls": "请输入验证码", + "InputWrong": "{0}错误", + "InputInvalid": "{0}无效", + "UsernameOrPassword": "用户名或密码", + "UnactiveUserTip": "用户未激活或不允许登录", + "ReturnHome": "返回首页", + "ReportIssue": "报告此问题", + "SignupNotOpenTip": "管理员未开放公开注册", + "UnsetEmailTip": "如果你忘记或未配置邮箱,请联系管理员重置密码", + "InputEmailPls": "请输入邮箱", + "InputNewPasswordPls": "请输入新密码", + "PasswordNotMatch": "两次输入的新密码不一致", + "NewPassword": "新密码", + "RepeatNewPassword": "重复新密码", + "GetVcode": "获取验证码", + "GetVcode2": "重新获取", + "VcodeEmailSent": "验证码已发送至邮箱", + "ConfirmReset": "确认重置", + "ActionSuccess": "操作成功", + "EmailAccountUnset": "邮件服务账户未配置,请联系管理员配置", + "YourVcodeForResetPassword": "

你的重置密码验证码是 %s

", + "InputVcodePls": "请输入验证码" + +} \ No newline at end of file diff --git a/src/main/webapp/_include/Foot.jsp b/src/main/webapp/_include/Foot.jsp index aed38074f..ae030dec3 100644 --- a/src/main/webapp/_include/Foot.jsp +++ b/src/main/webapp/_include/Foot.jsp @@ -12,6 +12,7 @@ + diff --git a/src/main/webapp/assets/css/rb-base.css b/src/main/webapp/assets/css/rb-base.css index bb97616e4..8a953c330 100644 --- a/src/main/webapp/assets/css/rb-base.css +++ b/src/main/webapp/assets/css/rb-base.css @@ -22834,7 +22834,7 @@ select.form-control-xs:not([size]):not([multiple]) { } .login-forgot-password { - line-height: 2.1; + line-height: 1.92; text-align: right } diff --git a/src/main/webapp/assets/img/flag/en-US.png b/src/main/webapp/assets/img/flag/en-US.png new file mode 100644 index 000000000..10f451fe8 Binary files /dev/null and b/src/main/webapp/assets/img/flag/en-US.png differ diff --git a/src/main/webapp/assets/img/flag/ja-JP.png b/src/main/webapp/assets/img/flag/ja-JP.png new file mode 100644 index 000000000..325fbad3f Binary files /dev/null and b/src/main/webapp/assets/img/flag/ja-JP.png differ diff --git a/src/main/webapp/assets/img/flag/zh-CN.png b/src/main/webapp/assets/img/flag/zh-CN.png new file mode 100644 index 000000000..891441462 Binary files /dev/null and b/src/main/webapp/assets/img/flag/zh-CN.png differ diff --git a/src/main/webapp/assets/js/rb-base.js b/src/main/webapp/assets/js/rb-base.js index ad2f32d4a..83265c690 100644 --- a/src/main/webapp/assets/js/rb-base.js +++ b/src/main/webapp/assets/js/rb-base.js @@ -39,7 +39,7 @@ complete: function (xhr) { // eslint-disable-next-line no-empty if (xhr.status === 200 || xhr.status === 0) { } // That's OK - else if (xhr.status === 403 || xhr.status === 401) RbHighbar.error(xhr.responseText || '未授权访问') + else if (xhr.status === 403 || xhr.status === 401) RbHighbar.error(xhr.responseText || 'Unauthorized access') else { var error = xhr.responseText if (rb.env !== 'dev' && error && error.contains('Exception : ')) error = error.split('Exception : ')[1] @@ -313,4 +313,18 @@ var $stopEvent = function (e) { if (e && e.stopPropagation) e.stopPropagation() if (e && e.nativeEvent) e.nativeEvent.stopImmediatePropagation() return false +} + +/** + * 获取语言 + */ +var $lang = function (key, insideLangs) { + var lang = __getLang(key) + if (typeof insideLangs === 'object') { + for (var k in insideLangs) lang = lang.replace('{' + k + '}', insideLangs[k]) + } + return lang +} +var __getLang = function (key) { + return (window.__LANGBUNDLE__ || {})[key] || '[' + key.toUpperCase() + ']' } \ No newline at end of file diff --git a/src/main/webapp/assets/js/rb-components.jsx b/src/main/webapp/assets/js/rb-components.jsx index e6f02a318..665c35782 100644 --- a/src/main/webapp/assets/js/rb-components.jsx +++ b/src/main/webapp/assets/js/rb-components.jsx @@ -12,7 +12,7 @@ class RbModal extends React.Component {

-

{this.props.title || '无标题'}

+

{this.props.title || 'UNTITLED'}

@@ -183,7 +183,7 @@ class RbAlert extends React.Component { let type = this.props.type || 'primary' let content = this.props.htmlMessage ?
- :

{this.props.message || '提示内容'}

+ :

{this.props.message || 'INMESSAGE'}

let cancel = (this.props.cancel || this.hide).bind(this) let confirm = (this.props.confirm || this.hide).bind(this) diff --git a/src/main/webapp/user/forgot-passwd.jsp b/src/main/webapp/user/forgot-passwd.jsp index e497e9883..f78a81dc5 100644 --- a/src/main/webapp/user/forgot-passwd.jsp +++ b/src/main/webapp/user/forgot-passwd.jsp @@ -3,7 +3,7 @@ <%@ include file="/_include/Head.jsp"%> -找回密码 +${bundle.lang('ForgotPassword')}