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.alibaba.fastjson.JSON;
import com.rebuild.server.Application; import com.rebuild.server.Application;
import com.rebuild.server.helper.SysConfiguration; 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.User;
import com.rebuild.server.service.bizz.privileges.ZeroEntry; import com.rebuild.server.service.bizz.privileges.ZeroEntry;
import com.rebuild.utils.JSONUtils; import com.rebuild.utils.JSONUtils;
@ -53,7 +54,7 @@ public class LoginToken extends BaseApi {
String password = context.getParameterNotBlank("password"); String password = context.getParameterNotBlank("password");
if (COUNTER.counter(user).add().seconds(30).than(3)) { 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); String hasError = checkUser(user, password);
@ -92,13 +93,13 @@ public class LoginToken extends BaseApi {
*/ */
public static String checkUser(String user, String password) { public static String checkUser(String user, String password) {
if (!Application.getUserStore().exists(user)) { if (!Application.getUserStore().exists(user)) {
return "用户名或密码错误"; return Languages.lang("InputWrong", "UsernameOrPassword");
} }
User loginUser = Application.getUserStore().getUser(user); User loginUser = Application.getUserStore().getUser(user);
if (!loginUser.isActive() if (!loginUser.isActive()
|| !Application.getSecurityManager().allowed(loginUser.getId(), ZeroEntry.AllowLogin)) { || !Application.getSecurityManager().allowed(loginUser.getId(), ZeroEntry.AllowLogin)) {
return "用户未激活或不允许登录"; return Languages.lang("UnactiveUserTip");
} }
Object[] foundUser = Application.createQueryNoFilter( Object[] foundUser = Application.createQueryNoFilter(
@ -108,7 +109,7 @@ public class LoginToken extends BaseApi {
.unique(); .unique();
if (foundUser == null if (foundUser == null
|| !foundUser[0].equals(EncryptUtils.toSHA256Hex(password))) { || !foundUser[0].equals(EncryptUtils.toSHA256Hex(password))) {
return "用户名或密码错误"; return Languages.lang("InputWrong", "UsernameOrPassword");
} }
return null; return null;

View file

@ -138,8 +138,8 @@ public class SMSender {
* @throws IOException * @throws IOException
*/ */
protected static Element getMailTemplate() throws IOException { protected static Element getMailTemplate() throws IOException {
File temp = SysConfiguration.getFileOfRes("locales/mail-notify.html"); File tmp = SysConfiguration.getFileOfRes("locales/email_zh-CN.html");
Document html = Jsoup.parse(temp, "utf-8"); Document html = Jsoup.parse(tmp, "utf-8");
return html.body(); 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) { public static boolean allowed(HttpServletRequest request, ZeroEntry entry) {
return Application.getSecurityManager().allowed(getRequestUser(request), 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 cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.rebuild.api.Controll; 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 com.rebuild.utils.AppUtils;
import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -43,11 +45,20 @@ public abstract class BaseControll extends Controll {
* @return * @return
*/ */
protected ID getRequestUser(HttpServletRequest request) { protected ID getRequestUser(HttpServletRequest request) {
ID userId = AppUtils.getRequestUser(request); ID user = AppUtils.getRequestUser(request);
if (userId == null) { if (user == null) {
throw new IllegalParameterException("无效请求用户"); 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; 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; import org.springframework.web.servlet.ModelAndView;
/** /**
@ -33,6 +36,12 @@ public abstract class BasePageControll extends BaseControll {
* @return * @return
*/ */
protected ModelAndView createModelAndView(String page) { 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.Record;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.rebuild.server.Application; import com.rebuild.server.Application;
import com.rebuild.server.helper.language.Languages;
import com.rebuild.server.metadata.EntityHelper; import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.service.bizz.CurrentCaller; import com.rebuild.server.service.bizz.CurrentCaller;
import com.rebuild.server.service.bizz.UserService; import com.rebuild.server.service.bizz.UserService;
import com.rebuild.web.user.signin.LoginControll; import com.rebuild.web.user.signin.LoginControll;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert; 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 Set<HttpSession> ONLINE_SESSIONS = new CopyOnWriteArraySet<>();
private static final Map<ID, HttpSession> ONLINE_USERS = new ConcurrentHashMap<>(); private static final Map<ID, HttpSession> ONLINE_USERS = new ConcurrentHashMap<>();
private static final ThreadLocal<String> LOCALE = new ThreadLocal<>();
@Override @Override
public void sessionCreated(HttpSessionEvent event) { 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) { public void storeLoginSuccessed(HttpServletRequest request) {
HttpSession s = request.getSession(); HttpSession s = request.getSession();
Object loginUser = s.getAttribute(WebUtils.CURRENT_USER); 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_SESSIONS.remove(s);
ONLINE_USERS.put((ID) loginUser, 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 @Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) { Exception ex) {
LOG.error("Handler - " + handler + "\nException - " + ex); LOG.error("\nHandler : " + handler + "\nException : ", ex);
return null; return null;
} }
} }

View file

@ -62,6 +62,10 @@ public class RequestWatchHandler extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception { Object handler) throws Exception {
response.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8");
request.getSession(true);
// for Language
Application.getSessionStore().setLocale(AppUtils.getLocale(request));
final String requestUrl = request.getRequestURI(); final String requestUrl = request.getRequestURI();
if (noCache && !(ServletUtils.isAjaxRequest(request) if (noCache && !(ServletUtils.isAjaxRequest(request)
@ -205,6 +209,6 @@ public class RequestWatchHandler extends HandlerInterceptorAdapter {
reqUrl = reqUrl.replaceFirst(ServerListener.getContextPath(), ""); reqUrl = reqUrl.replaceFirst(ServerListener.getContextPath(), "");
return reqUrl.startsWith("/gw/") || reqUrl.startsWith("/assets/") || reqUrl.startsWith("/error/") return reqUrl.startsWith("/gw/") || reqUrl.startsWith("/assets/") || reqUrl.startsWith("/error/")
|| reqUrl.startsWith("/t/") || reqUrl.startsWith("/s/") || 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 javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import static com.rebuild.server.helper.language.Languages.lang;
/** /**
* @author zhaofang123@gmail.com * @author zhaofang123@gmail.com
* @since 07/25/2018 * @since 07/25/2018
@ -132,10 +134,10 @@ public class LoginControll extends BasePageControll {
Boolean needVcode = (Boolean) ServletUtils.getSessionAttribute(request, NEED_VCODE); Boolean needVcode = (Boolean) ServletUtils.getSessionAttribute(request, NEED_VCODE);
if (needVcode != null && needVcode if (needVcode != null && needVcode
&& (StringUtils.isBlank(vcode) || !CaptchaUtil.ver(vcode, request))) { && (StringUtils.isBlank(vcode) || !CaptchaUtil.ver(vcode, request))) {
writeFailure(response, "验证码错误"); writeFailure(response, lang("InputWrong", "Captcha"));
return; return;
} }
final String user = getParameterNotNull(request, "user"); final String user = getParameterNotNull(request, "user");
final String password = getParameterNotNull(request, "passwd"); final String password = getParameterNotNull(request, "passwd");
@ -248,23 +250,23 @@ public class LoginControll extends BasePageControll {
@RequestMapping("user-forgot-passwd") @RequestMapping("user-forgot-passwd")
public void userForgotPasswd(HttpServletRequest request, HttpServletResponse response) { public void userForgotPasswd(HttpServletRequest request, HttpServletResponse response) {
if (!SMSender.availableMail()) { if (!SMSender.availableMail()) {
writeFailure(response, "邮件服务账户未配置,请联系管理员配置"); writeFailure(response, lang("EmailAccountUnset"));
return; return;
} }
String email = getParameterNotNull(request, "email"); String email = getParameterNotNull(request, "email");
if (!RegexUtils.isEMail(email) || !Application.getUserStore().existsEmail(email)) { if (!RegexUtils.isEMail(email) || !Application.getUserStore().existsEmail(email)) {
writeFailure(response, "无效邮箱"); writeFailure(response, lang("InputInvalid", "Email"));
return; return;
} }
String vcode = VCode.generate(email, 2); String vcode = VCode.generate(email, 2);
String content = "<p>你的重置密码验证码是 <b>" + vcode + "</b><p>"; String content = String.format(lang("YourVcodeForResetPassword"), vcode);
String sentid = SMSender.sendMail(email, "重置密码", content); String sentid = SMSender.sendMail(email, lang("ResetPassword"), content);
if (sentid != null) { if (sentid != null) {
writeSuccess(response); writeSuccess(response);
} else { } else {
writeFailure(response, "无法发送验证码,请稍后重试"); writeFailure(response);
} }
} }
@ -275,7 +277,7 @@ public class LoginControll extends BasePageControll {
String email = data.getString("email"); String email = data.getString("email");
String vcode = data.getString("vcode"); String vcode = data.getString("vcode");
if (!VCode.verfiy(email, vcode, true)) { if (!VCode.verfiy(email, vcode, true)) {
writeFailure(response, "验证码无效"); writeFailure(response, lang("InputInvalid", "Vcode"));
return; return;
} }

View file

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Mail Notification Template</title> <title>EMail Template</title>
</head> </head>
<body> <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"> <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/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.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}/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-components.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-base.js"></script> <script src="${baseUrl}/assets/js/rb-base.js"></script>
<script src="${baseUrl}/assets/js/rb-page.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 { .login-forgot-password {
line-height: 2.1; line-height: 1.92;
text-align: right 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) { complete: function (xhr) {
// eslint-disable-next-line no-empty // eslint-disable-next-line no-empty
if (xhr.status === 200 || xhr.status === 0) { } // That's OK 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 { else {
var error = xhr.responseText var error = xhr.responseText
if (rb.env !== 'dev' && error && error.contains('Exception : ')) error = error.split('Exception : ')[1] 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.stopPropagation) e.stopPropagation()
if (e && e.nativeEvent) e.nativeEvent.stopImmediatePropagation() if (e && e.nativeEvent) e.nativeEvent.stopImmediatePropagation()
return false 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-dialog" style={{ maxWidth: (this.props.width || 680) + 'px' }}>
<div className="modal-content"> <div className="modal-content">
<div className="modal-header modal-header-colored"> <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> <button className="close" type="button" onClick={() => this.hide()}><span className="zmdi zmdi-close" /></button>
</div> </div>
<div className={'modal-body' + (inFrame ? ' iframe rb-loading' : '') + (inFrame && this.state.frameLoad !== false ? ' rb-loading-active' : '')}> <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 type = this.props.type || 'primary'
let content = this.props.htmlMessage ? let content = this.props.htmlMessage ?
<div className="mt-3" style={{ lineHeight: 1.8 }} dangerouslySetInnerHTML={{ __html: 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 cancel = (this.props.cancel || this.hide).bind(this)
let confirm = (this.props.confirm || this.hide).bind(this) let confirm = (this.props.confirm || this.hide).bind(this)

View file

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

View file

@ -46,8 +46,12 @@
.rb-content { .rb-content {
z-index: 2; z-index: 2;
} }
.select-lang a {
display: inline-block;
padding: 3px;
}
</style> </style>
<title>登录</title> <title>${bundle.lang("Login")}</title>
</head> </head>
<body class="rb-splash-screen"> <body class="rb-splash-screen">
<div class="rb-wrapper rb-login"> <div class="rb-wrapper rb-login">
@ -60,32 +64,37 @@
<div class="card-body"> <div class="card-body">
<form id="login-form"> <form id="login-form">
<div class="form-group"> <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>
<div class="form-group"> <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>
<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"> <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>
<div class="col-6 text-right pl-0 pr-0"> <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> </div>
<div class="form-group row login-tools"> <div class="form-group row login-tools">
<div class="col-6 login-remember"> <div class="col-6 login-remember">
<label class="custom-control custom-checkbox custom-control-inline mb-0"> <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> </label>
</div> </div>
<div class="col-6 login-forgot-password"> <div class="col-6 login-forgot-password">
<a href="forgot-passwd">找回密码</a> <a href="forgot-passwd">${bundle.lang('ForgotPassword')}</a>
</div> </div>
</div> </div>
<div class="form-group login-submit"> <div class="form-group login-submit">
<button class="btn btn-primary btn-xl" type="submit" data-loading-text="登录中">登录</button> <button class="btn btn-primary btn-xl" type="submit">${bundle.lang('Login')}</button>
<div class="mt-4 text-center">还没有账号?<a href="signup">立即注册</a></div> <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> </div>
</form> </form>
</div> </div>
@ -107,7 +116,6 @@ useLiveWallpaper = <%=SysConfiguration.getBool(ConfigurableItem.LiveWallpaper)%>
<script type="text/babel"> <script type="text/babel">
$(document).ready(function() { $(document).ready(function() {
if (top != self) { parent.location.reload(); return } if (top != self) { parent.location.reload(); return }
if ($urlp('t') == 99) RbHighbar.create('注册申请已提交,请等待管理员审核', 'success', { timeout: 999999 })
$('.vcode-row img').click(function(){ $('.vcode-row img').click(function(){
$(this).attr('src', rb.baseUrl + '/user/captcha?' + $random()) $(this).attr('src', rb.baseUrl + '/user/captcha?' + $random())
@ -123,9 +131,9 @@ $(document).ready(function() {
let user = $val('#user'), let user = $val('#user'),
passwd = $val('#passwd'), passwd = $val('#passwd'),
vcode = $val('.vcode-row input') vcode = $val('.vcode-row input')
if (!user || !passwd){ RbHighbar.create('请输入用户名和密码'); return } if (!user || !passwd){ RbHighbar.create($lang('InputUserOrPasswordPls')); return }
if (vcodeState && !vcode){ RbHighbar.create('请输入验证码'); return } if (vcodeState && !vcode){ RbHighbar.create($lang('InputCaptchaPls')); return }
let btn = $('.login-submit button').button('loading') let btn = $('.login-submit button').button('loading')
let url = rb.baseUrl + '/user/user-login?user=' + $encode(user) + '&passwd=' + $encode(passwd) + '&autoLogin=' + $val('#autoLogin') let url = rb.baseUrl + '/user/user-login?user=' + $encode(user) + '&passwd=' + $encode(passwd) + '&autoLogin=' + $val('#autoLogin')
if (!!vcode) url += '&vcode=' + vcode if (!!vcode) url += '&vcode=' + vcode
@ -138,7 +146,7 @@ $(document).ready(function() {
btn.button('reset') btn.button('reset')
} else { } else {
$('.vcode-row img').trigger('click') $('.vcode-row img').trigger('click')
RbHighbar.create(res.error_msg || '登录失败,请稍后重试') RbHighbar.create(res.error_msg)
btn.button('reset') btn.button('reset')
} }
}) })
@ -154,10 +162,16 @@ $(document).ready(function() {
setTimeout(() => { setTimeout(() => {
$('.rb-bgimg').css('background-image', 'url(' + res.url + ')').animate({ opacity: 1 }) $('.rb-bgimg').css('background-image', 'url(' + res.url + ')').animate({ opacity: 1 })
}, 400) }, 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 */ }) }).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> </script>
</body> </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"));
}
}