国际化解决方案

需求分析

JavaWeb国际化是为了适应不同国家和地区的语言和文化习惯,使得网站能够使用某个特定的语言、区域等,以满足不同用户的需求。以下是对JavaWeb国际化需求的一个概述:

  1. 配置文件:在JavaWeb项目中需要配置国际化资源文件,用来存储不同语言环境下的文本信息。通常采用Properties文件格式,根据不同语言环境创建不同的Properties文件。
  2. 代码实现:修改代码中的提示信息、标签名、按钮等文本信息为国际化资源文件中的属性。例如在JSP中使用fmt:message标签。通过获取请求的locale,动态切换国际化资源文件,自动加载对应语言环境的文本信息。
  3. 自定义Locale:如果系统要支持更多语言,可以定义自己的locale。locale控制了语言、时间、数字格式、货币格式等各种本地化信息。在JavaWeb项目中,locale可以通过组合language、country和variant来定义。具体语言定义可以参照ISO 639标准。
  4. 测试:为了检验国际化是否生效,需要进行测试。可以在浏览器、操作系统、IDE等中切换语言环境,查看是否正确的显示了不同的语言和文本信息。通过对以上需求的分析和处理,可以为JavaWeb项目快速地添加国际化的支持,以支持不同的语言环境和用户需求。可以通过国际化的支持提高网站的用户友好性和用户体验。同时,需要注意国际化资源文件的管理和维护,以保证代码的可读性和易维护性。

解决方案

我们采用了java提供的语言切换类ResourceBundle,再结合Redis和资源文件实现动态切换语言环境的功能。我们主要实现中英文切换,主要实现的点如下:

  1. 验证信息国际化
    1. 新增数据时会验证参数是否为空
    2. 新增数据时会验证参数是否超出长度
    3. 新增数据时会验证参数格式是否正确
  2. 返回信息国际化
    1. 新增成功是返回国际化后的信息
  3. 数据信息国际化
    1. 菜单展示国际化名称

实现步骤

验证信息国际化

实现步骤如下:

  1. 入参添加判空注解、长度校验注解、正则校验注解
    1. regexp:正则字符串
    2. message:映射语言包中的key
    3. min:最小长度
    4. max:最大长度
    5. message:映射语言包中的key
    6. message:映射语言包中的key
    7. @NotBlank
    8. @Length
    9. @Pattern
  2. 维护国际化语言包
    1. empty:入参为空返回信息
    2. length:入参长度不符合返回信息
    3. regexp:正则不满足返回信息
    4. existed:校验重复返回信息
    5. 语言包命名规则为模块名称+_language_+国际化枚举标识+.properties,java内部会根据国际化枚举标识自动匹配对应的语言包
    6. 语言包中key值命名规则为模块名称.+子模块名称.+字段.+类型,我们这里采用了如下几种类型:
  3. 还有比部分校验信息归类为返回信息国际化处理
@Getter
@Setter
@ApiModel(value = "UserInfoInsertDto", description = "DUserInfoSaveDto用户保存入参")
public class UserInfoInsertDto {

    @ApiModelProperty(value = "用户名称", required = true, example = "admin")
    @NotBlank(message = "system.userInfo.username.empty")
    @Length(min = 1, max = 20, message = "system.userInfo.username.length")
    @Pattern(regexp = Regexp.CHINESE_AND_ENGLISH, message = "system.userInfo.username.regexp")
    private String username;
}
#角色管理
system.roleInfo.id.empty=Role id cannot be empty

system.roleInfo.roleCode.empty=Role code cannot be empty
system.roleInfo.roleCode.length=The length of the role code is between 6 and 20 characters
system.roleInfo.roleCode.regexp=The role code must be prefixed with 'ROLE_', and can only be entered in uppercase English
system.roleInfo.roleCode.existed=Role code already exists

system.roleInfo.roleName.empty=Role name cannot be empty
system.roleInfo.roleName.length=The length of the role name is between 1 and 20 characters
system.roleINfo.roleName.regexp=Role names can only be entered in Chinese and English

system.roleInfo.remark.length=The length of the role description is between 0,100 characters.
system.roleInfo.remark.regexp=Role description can only be entered in Chinese and English
#角色管理
system.roleInfo.id.empty=角色id不能为空

system.roleInfo.roleCode.empty=角色代码不能为空
system.roleInfo.roleCode.length=角色代码长度在6~20字符之间
system.roleInfo.roleCode.regexp=角色代码前缀必须为‘ROLE_’,且只能输入英文大写
system.roleInfo.roleCode.existed=角色代码已存在

system.roleInfo.roleName.empty=角色名称不能为空
system.roleInfo.roleName.length=角色名称长度在1~20字符之间
system.roleINfo.roleName.regexp=角色名称只能输入中英文

system.roleInfo.remark.length=角色描述长度在0~100字符之间
system.roleInfo.remark.regexp=角色描述只能输入中英文

返回信息国际化

实现步骤如下:

  1. 新建国际化工具类
    1. 单个参数国际化
    2. 多个参数国际化
  2. 新建对应国际化语言包的接口
    1. 通用语言包接口
    2. 异常语言包接口
    3. 按模块划分的语言包接口
  3. 返回的信息通过国际化工具类转换
    1. 单个参数
    2. 拼接参数
@Component
public class I18nUtils {

    private static StringRedisTemplate stringRedisTemplate;

    @Autowired
    public I18nUtils(StringRedisTemplate stringRedisTemplate) {
        I18nUtils.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取国际化通用信息
     *
     * @param code key值
     * @return {@link String}
     */

    public static String getMessage(String code) {
        String language = CommonConstant.LANGUAGE;
        if (code.contains(CommonConstant.POINT)) {
            language = code.split(CommonConstant.POINT_ESCAPE)[0]
                    .concat(CommonConstant.UNDERLINE.concat(CommonConstant.LANGUAGE));
        }
        ResourceBundle bundle = ResourceBundle.getBundle(language, getLocale());
        return MessageFormat.format(bundle.getString(code), "");
    }

    /**
     * 获取国际化通用信息(带有占位参数的)
     *
     * @param code      key值
     * @param arguments 占位符替换信息
     * @return {@link String}
     */

    public static String getMessage(String code, Object... arguments) {
        String language = CommonConstant.LANGUAGE;
        if (code.contains(CommonConstant.POINT)) {
            language = code.split(CommonConstant.POINT_ESCAPE)[0]
                    .concat(CommonConstant.UNDERLINE.concat(CommonConstant.LANGUAGE));
        }
        ResourceBundle bundle = ResourceBundle.getBundle(language, getLocale());
        arguments = getObjects(bundle, arguments);
        return MessageFormat.format(bundle.getString(code), arguments);
    }

    /**
     * 占位参数处理
     *
     * @param bundle    ResourceBundle
     * @param arguments arguments
     * @return {@link Object}
     */

    private static Object[] getObjects(ResourceBundle bundle, Object[] arguments) {
        if (arguments != null) {
            for (int i = 0; i < arguments.length; i++) {
                if (arguments[i] != null && !"".equals(arguments[i])) {
                    arguments[i] = bundle.getString(arguments[i].toString());
                }
            }
            return arguments;
        }
        return new String[]{""};
    }

    /**
     * 获取redis中语言环境
     *
     * @return {@link Locale}
     */

    private static Locale getLocale() {
        String locale = stringRedisTemplate.opsForValue().get(RedisConstant.LOCALE);
        if (LocaleTypeEnum.SIMPLIFIED_CHINESE.getCode().equals(locale)) {
            return LocaleTypeEnum.SIMPLIFIED_CHINESE.getLocale();
        }
        if (LocaleTypeEnum.US.getCode().equals(locale)) {
            return LocaleTypeEnum.US.getLocale();
        }
        return Locale.getDefault();
    }
}
public interface LanguageConstant {

    /**
     * 新增
     */

    String INSERT = "insert";

    /**
     * 修改
     */

    String UPDATE = "update";

    /**
     * 删除
     */

    String DELETE = "delete";

    /**
     * 成功
     */

    String SUCCESS = "success";

    /**
     * 失败
     */

    String FAILED = "failed";
}
@Service
public class UserInfoServiceImpl
        extends ServiceImpl<UserInfoMapperUserInfoimplements IUserInfoService 
{
    @Override
    public RespJson insertItem(UserInfoInsertDto userInfoInsertDto) {
        return RespJson.success(
                    I18nUtils.getMessage(LanguageConstant.INSERT, LanguageConstant.SUCCESS));
        }
        return RespJson.error(
                I18nUtils.getMessage(LanguageConstant.INSERT, LanguageConstant.FAILED));
    }
}

数据信息国际化

实现步骤如下

  1. 数据库中字段分裂为多个字段存储多种语言文本
  2. 根据缓存中不同语言类型进行动态返回。
create table Spring_boot_demo_db.d_menu_info
(
menu_name varchar(20) charset utf8mb3 null comment '菜单名称',
menu_name_en varchar(100) null comment '菜单英文名称'
)
comment '菜单基表';


@Getter
@Setter
@ApiModel(value = "MenuInfoShowVo", description = "页面展示菜单集合")
public class MenuInfoShowVo {

@ApiModelProperty(value = "菜单名称")
private String menuName;

@ApiModelProperty(value = "菜单英文名称")
private String menuNameEn;

}

总结

以上是项目实践中积累的经验和解决方案,数据库中维护多种字段耦合度较高,不利于扩展,后期会寻找替代方案。大家如果有更好的解决方案请分享出来,互相学习。

原文始发于微信公众号(刘凌枫羽工作室):国际化解决方案

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251392.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!