Will done

This commit is contained in:
devezhao 2019-11-29 18:02:16 +08:00
parent 936298d4e4
commit 8d8a353751
27 changed files with 707 additions and 67 deletions

3
crowdin.yml Normal file
View file

@ -0,0 +1,3 @@
files:
- source: /src/main/resources/locales/language_zh-CN.json
translation: /src/main/resources/locales/language_%locale%.json

View file

@ -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;

View file

@ -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();
}

View file

@ -0,0 +1,163 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View file

@ -0,0 +1,146 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
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 <https://www.gnu.org/licenses/>.
*/
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<String, LanguageBundle> 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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
/**

View file

@ -18,6 +18,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
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;
}
}

View file

@ -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<HttpSession> ONLINE_SESSIONS = new CopyOnWriteArraySet<>();
private static final Map<ID, HttpSession> ONLINE_USERS = new ConcurrentHashMap<>();
private static final ThreadLocal<String> 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();
}
}

View file

@ -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;
}
}

View file

@ -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/");
}
}

View file

@ -0,0 +1,74 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
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 <https://www.gnu.org/licenses/>.
*/
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));
}
}
}

View file

@ -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 = "<p>你的重置密码验证码是 <b>" + vcode + "</b><p>";
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;
}

View file

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>Mail Notification Template</title>
<title>EMail Template</title>
</head>
<body>
<div style="background-color:#f5f5f5;margin:0;font-size:0.9rem;padding:20px 30px;color:#333;font-family:Roboto,Helvetica,'Microsoft YaHei','宋体',sans-serif;line-height:1.5">

View file

@ -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": "<p>Your reset password verification code is <b>%s</b><p>",
"InputVcodePls": "Please enter code"
}

View file

@ -0,0 +1,3 @@
{
"rebuild": "高度にカスタマイズ可能なエンタープライズ管理システム"
}

View file

@ -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": "<p>你的重置密码验证码是 <b>%s</b><p>",
"InputVcodePls": "请输入验证码"
}

View file

@ -12,6 +12,7 @@
<script src="${baseUrl}/assets/lib/react/babel.js?v=7.6.4"></script>
<script src="${baseUrl}/assets/lib/react/react.development.js?v=16.10.2"></script>
<script src="${baseUrl}/assets/lib/react/react-dom.development.js?v=16.10.2"></script>
<script src="${baseUrl}/language/bundle?v=1.7.0"></script>
<script src="${baseUrl}/assets/js/rb-components.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-base.js"></script>
<script src="${baseUrl}/assets/js/rb-page.js"></script>

View file

@ -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
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

View file

@ -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() + ']'
}

View file

@ -12,7 +12,7 @@ class RbModal extends React.Component {
<div className="modal-dialog" style={{ maxWidth: (this.props.width || 680) + 'px' }}>
<div className="modal-content">
<div className="modal-header modal-header-colored">
<h3 className="modal-title">{this.props.title || '无标题'}</h3>
<h3 className="modal-title">{this.props.title || 'UNTITLED'}</h3>
<button className="close" type="button" onClick={() => this.hide()}><span className="zmdi zmdi-close" /></button>
</div>
<div className={'modal-body' + (inFrame ? ' iframe rb-loading' : '') + (inFrame && this.state.frameLoad !== false ? ' rb-loading-active' : '')}>
@ -183,7 +183,7 @@ class RbAlert extends React.Component {
let type = this.props.type || 'primary'
let content = this.props.htmlMessage ?
<div className="mt-3" style={{ lineHeight: 1.8 }} dangerouslySetInnerHTML={{ __html: this.props.htmlMessage }} />
: <p>{this.props.message || '提示内容'}</p>
: <p>{this.props.message || 'INMESSAGE'}</p>
let cancel = (this.props.cancel || this.hide).bind(this)
let confirm = (this.props.confirm || this.hide).bind(this)

View file

@ -3,7 +3,7 @@
<html>
<head>
<%@ include file="/_include/Head.jsp"%>
<title>找回密码</title>
<title>${bundle.lang('ForgotPassword')}</title>
</head>
<body class="rb-splash-screen">
<div class="rb-wrapper rb-login">
@ -14,38 +14,41 @@
<div class="card-header"><a class="logo-img"></a></div>
<div class="card-body J_step1">
<div class="form-group">
<input class="form-control" id="email" type="text" placeholder="邮箱" autocomplete="off">
<p class="form-text">如果你忘记或未配置邮箱,请联系管理员重置密码</p>
<input class="form-control" id="email" type="text" placeholder="${bundle.lang('Email')}" autocomplete="off">
<p class="form-text">${bundle.lang('UnsetEmailTip')}</p>
</div>
<div class="form-group login-submit">
<button class="btn btn-primary btn-xl J_forgot-btn">重置密码</button>
<button class="btn btn-primary btn-xl J_forgot-btn">${bundle.lang('ResetPassword')}</button>
</div>
</div>
<div class="card-body J_step2 hide">
<div class="card-body J_step2 ">
<div class="alert alert-warning alert-icon alert-icon-border alert-sm">
<div class="icon"><span class="zmdi zmdi-info-outline"></span></div>
<div class="message"><p>${bundle.lang('VcodeEmailSent')} <b class="J_email ml-1"></b></p></div>
</div>
<div class="form-group">
<div class="row">
<div class="col-8">
<input class="form-control" id="vcode" type="text" placeholder="请输入验证码" autocomplete="off">
<div class="col-7">
<input class="form-control" id="vcode" type="text" placeholder="${bundle.lang('InputVcodePls')}" autocomplete="off">
</div>
<div class="col-4 pl-0">
<button type="button" class="btn btn-primary bordered J_vcode-resend" style="height:41px;width:100%">获取验证码</button>
<div class="col-5 pl-0">
<button type="button" class="btn btn-primary bordered J_vcode-resend" style="height:41px;width:100%">${bundle.lang('GetVcode')}</button>
</div>
</div>
<p class="form-text">验证码已发送至邮箱 <b class="J_email"></b></p>
</div>
<div class="form-group">
<input class="form-control" id="newpwd" type="password" placeholder="新密码" autocomplete="off">
<input class="form-control" id="newpwd" type="password" placeholder="${bundle.lang('NewPassword')}" autocomplete="off">
</div>
<div class="form-group">
<input class="form-control" id="newpwd2" type="password" placeholder="重复新密码" autocomplete="off">
<input class="form-control" id="newpwd2" type="password" placeholder="${bundle.lang('RepeatNewPassword')}" autocomplete="off">
</div>
<div class="form-group login-submit">
<button class="btn btn-primary btn-xl J_confirm-btn">确认重置</button>
<button class="btn btn-primary btn-xl J_confirm-btn">${bundle.lang('ConfirmReset')}</button>
</div>
</div>
</div>
<div class="splash-footer">
<span><a href="login">返回登录</a></span>
<span><a href="login">${bundle.lang('ReturnHome')}</a></span>
</div>
</div>
</div>
@ -57,7 +60,7 @@ $(document).ready(function() {
let email = null
$('.J_forgot-btn, .J_vcode-resend').click(function() {
email = $val('#email')
if (!email){ RbHighbar.create('请输入邮箱'); return }
if (!email){ RbHighbar.create($lang('InputEmailPls')); return }
$('.J_email').text(email)
let _btn = $(this).button('loading')
$.post(rb.baseUrl + '/user/user-forgot-passwd?email=' + $encode(email), function(res) {
@ -74,15 +77,15 @@ $(document).ready(function() {
let vcode = $val('#vcode')
let newpwd = $val('#newpwd')
let newpwd2 = $val('#newpwd2')
if (!vcode) { RbHighbar.create('请输入验证码'); return }
if (!newpwd) { RbHighbar.create('请输入新密码'); return }
if (newpwd !== newpwd2) { RbHighbar.create('两次输入的新密码不一致'); return }
if (!vcode) { RbHighbar.create($lang('InputVcodePls')); return }
if (!newpwd) { RbHighbar.create($lang('InputNewPasswordPls')); return }
if (newpwd !== newpwd2) { RbHighbar.create($lang('PasswordNotMatch')); return }
let _data = { email: email, vcode: vcode, newpwd: newpwd }
let _btn = $(this).button('loading')
$.post(rb.baseUrl + '/user/user-confirm-passwd', JSON.stringify(_data), function(res) {
if (res.error_code == 0){
_btn.text('密码重置成功')
_btn.text($lang('ActionSuccess'))
setTimeout(()=>{ location.href = './login' }, 1000)
} else {
RbHighbar.create(res.error_msg)
@ -99,9 +102,9 @@ let resend_countdown = function(first){
if (countdown_timer) clearTimeout(countdown_timer)
countdown_seconds = 60
}
$('.J_vcode-resend').text('重新获取 (' + (--countdown_seconds) + ')')
$('.J_vcode-resend').text($lang('GetVcode2') + ' (' + (--countdown_seconds) + ')')
if (countdown_seconds == 0) {
$('.J_vcode-resend').attr('disabled', false).text('重新获取')
$('.J_vcode-resend').attr('disabled', false).text($lang('GetVcode2'))
} else {
countdown_timer = setTimeout(resend_countdown, 1000)
}

View file

@ -46,8 +46,12 @@
.rb-content {
z-index: 2;
}
.select-lang a {
display: inline-block;
padding: 3px;
}
</style>
<title>登录</title>
<title>${bundle.lang("Login")}</title>
</head>
<body class="rb-splash-screen">
<div class="rb-wrapper rb-login">
@ -60,32 +64,37 @@
<div class="card-body">
<form id="login-form">
<div class="form-group">
<input class="form-control" id="user" type="text" placeholder="用户名 (或邮箱)">
<input class="form-control" id="user" type="text" placeholder="${bundle.lang('UsernameOrEmail')}">
</div>
<div class="form-group">
<input class="form-control" id="passwd" type="password" placeholder="登录密码">
<input class="form-control" id="passwd" type="password" placeholder="${bundle.lang('LoginPassword')}">
</div>
<div class="form-group row pt-0 hide vcode-row" data-state="${sessionScope.needLoginVCode}">
<div class="form-group row pt-0 mb-3 hide vcode-row" data-state="${sessionScope.needLoginVCode}">
<div class="col-6 pr-0">
<input class="form-control" type="text" placeholder="输入右侧验证码">
<input class="form-control" type="text" placeholder="${bundle.lang('InputCaptcha')}">
</div>
<div class="col-6 text-right pl-0 pr-0">
<img style="max-width:100%;margin-right:-15px" alt="验证码" title="点击刷新">
<img style="max-width:100%;margin-right:-15px" alt="${bundle.lang('Captcha')}" title="${bundle.lang('ClickReload')}">
</div>
</div>
<div class="form-group row login-tools">
<div class="col-6 login-remember">
<label class="custom-control custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="autoLogin"><span class="custom-control-label"> 记住登录</span>
<input class="custom-control-input" type="checkbox" id="autoLogin"><span class="custom-control-label"> ${bundle.lang('RememberMe')}</span>
</label>
</div>
<div class="col-6 login-forgot-password">
<a href="forgot-passwd">找回密码</a>
<a href="forgot-passwd">${bundle.lang('ForgotPassword')}</a>
</div>
</div>
<div class="form-group login-submit">
<button class="btn btn-primary btn-xl" type="submit" data-loading-text="登录中">登录</button>
<div class="mt-4 text-center">还没有账号?<a href="signup">立即注册</a></div>
<button class="btn btn-primary btn-xl" type="submit">${bundle.lang('Login')}</button>
<div class="mt-4 text-center">${bundle.lang('NoAccountYet')}&nbsp;<a href="signup">${bundle.lang('SignupNow')}</a></div>
</div>
<div class="select-lang text-center mb-2">
<a href="?locale=zh_CN" title="中文"><img src="${baseUrl}/assets/img/flag/zh-CN.png" /></a>
<a href="?locale=en_US" title="English"><img src="${baseUrl}/assets/img/flag/en-US.png" /></a>
<a href="?locale=ja_JP" title="日本語"><img src="${baseUrl}/assets/img/flag/ja-JP.png" /></a>
</div>
</form>
</div>
@ -107,7 +116,6 @@ useLiveWallpaper = <%=SysConfiguration.getBool(ConfigurableItem.LiveWallpaper)%>
<script type="text/babel">
$(document).ready(function() {
if (top != self) { parent.location.reload(); return }
if ($urlp('t') == 99) RbHighbar.create('注册申请已提交,请等待管理员审核', 'success', { timeout: 999999 })
$('.vcode-row img').click(function(){
$(this).attr('src', rb.baseUrl + '/user/captcha?' + $random())
@ -123,9 +131,9 @@ $(document).ready(function() {
let user = $val('#user'),
passwd = $val('#passwd'),
vcode = $val('.vcode-row input')
if (!user || !passwd){ RbHighbar.create('请输入用户名和密码'); return }
if (vcodeState && !vcode){ RbHighbar.create('请输入验证码'); return }
if (!user || !passwd){ RbHighbar.create($lang('InputUserOrPasswordPls')); return }
if (vcodeState && !vcode){ RbHighbar.create($lang('InputCaptchaPls')); return }
let btn = $('.login-submit button').button('loading')
let url = rb.baseUrl + '/user/user-login?user=' + $encode(user) + '&passwd=' + $encode(passwd) + '&autoLogin=' + $val('#autoLogin')
if (!!vcode) url += '&vcode=' + vcode
@ -138,7 +146,7 @@ $(document).ready(function() {
btn.button('reset')
} else {
$('.vcode-row img').trigger('click')
RbHighbar.create(res.error_msg || '登录失败,请稍后重试')
RbHighbar.create(res.error_msg)
btn.button('reset')
}
})
@ -154,10 +162,16 @@ $(document).ready(function() {
setTimeout(() => {
$('.rb-bgimg').css('background-image', 'url(' + res.url + ')').animate({ opacity: 1 })
}, 400)
if (res.copyright) $('.rb-bgimg').attr('title', res.copyright + ' (' + res.source + ')')
if (res.copyright) $('.rb-bgimg').attr('alt', res.copyright + ' (' + res.source + ')')
}
}).fail(function () { /* NOOP */ })
}
$('.select-lang>a').click(function (event) {
event.preventDefault()
let locale = $(this).attr('href')
$.post(rb.baseUrl + '/language/select' + locale, ()=> location.replace(locale))
})
})
</script>
</body>

View file

@ -0,0 +1,58 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2019 devezhao <zhaofang123@gmail.com>
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 <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.helper.language;
import com.rebuild.server.TestSupport;
import org.junit.Test;
import java.util.Locale;
import static org.junit.Assert.*;
/**
* @author devezhao
* @since 11/29/2019
*/
public class LanguagesTest extends TestSupport {
@Test
public void getBundle() {
System.out.println(Languages.instance.getDefaultBundle());
System.out.println(Languages.instance.getCurrentBundle());
System.out.println(Languages.instance.getBundle(Locale.getDefault()));
assertEquals(Locale.US.toString(), Languages.instance.getBundle(Locale.US).locale());
assertEquals(Locale.JAPAN.toString(), Languages.instance.getBundle(Locale.JAPAN).locale());
}
@Test
public void lang() {
System.out.println(Languages.lang("rebuild"));
System.out.println(Languages.lang("rebuild-undef"));
System.out.println(Languages.instance.getBundle(Locale.US).lang("rebuild"));
System.out.println(Languages.instance.getBundle(Locale.JAPAN).lang("rebuild"));
System.out.println(Languages.instance.getBundle(Locale.GERMAN).lang("rebuild"));
}
@Test
public void langMerge() {
System.out.println(Languages.lang("UsernameOrEmail"));
}
}