RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1068768
Accepted
Orkhan Hasanli
Orkhan Hasanli
Asked:2020-01-12 06:44:52 +0000 UTC2020-01-12 06:44:52 +0000 UTC 2020-01-12 06:44:52 +0000 UTC

Spring/Hibernate 中的国际化和本地化

  • 772

再会!

在 Spring Boot 中开发网站时,我想知道是否能胜任国际化。

我重新阅读了许多来自不同服务的不同解决方案,但没有找到满意的答案。

最好学习“最佳实践”并听取您关于实施的想法。对将消息存储在数据库中而不是将它们存储在 messages.properties 文件中的国际化感兴趣

关于我现在如何实现它:基于这篇文章 - https://medium.com/i18n-and-l10n-resources-for-developers/database-stored-messages-for-i18n-in-spring-boot -11dc2ee5c1f7

明显的优点:

  • 数据以一对语言环境、键、内容存储在数据库中的一个表中。使用密钥和本地化,我们找到翻译并将其发送到前面。

缺点:

  • 每个消息本地化组都需要自己独特的。键(我使用UUID.randomUUID())。那些。Locale - ru Locale - en 他们有一个独特的。钥匙。例如主页标题
  • 另一个问题是实体将必要的原语(例如,String)存储在实体本身中是没有意义的,但它们需要存储在转换表中。例如,对于 Page 实体,标题字符串变得不必要,因为该字符串的翻译包含在另一个表中。

这就提出了一个问题——如何正确地将翻译表(实体)与其他实体联系起来,这样就可以通过引用实体本身而不是翻译的本质来获得所需的翻译?

找到了一些解决方案:

  1. https://stackoverflow.com/questions/39704729/how-to-internationalize-a-hibernate-entity 在这里,在第一个选项中,建议存储 HashMap 而不是实体的原语。第二个答案建议链接 ManyToMany 表
  2. https://stackoverflow.com/questions/49916912/most-elegant-solution-for-internationalization-with-jpa-hibernate
  3. https://stackoverflow.com/questions/6350415/internationalization-with-hibernate
  4. https://stackoverflow.com/questions/49916912/most-elegant-solution-for-internationalization-with-jpa-hibernate 在这个选项中,建议为每个实体创建一个翻译表,这也不好。
  5. https://thoughts-on-java.org/localized-data-hibernate/ 还建议为每个实体创建一个单独的表
  6. https://github.com/deathman92/localized 我还发现了这样一个库(尽管它不起作用),借助它您可以使用本地化注释。

真的没有“现成的”解决方案或任何有效解决方案的选项吗?

提前感谢您的建议和回答!

spring
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Best Answer
    Antonio112009
    2020-01-12T12:39:05Z2020-01-12T12:39:05Z

    这个解决方案是我几年前在网上找到的。

    我在这个小项目中实现了支持多种语言的可能选项之一:链接到 GitHub。这段代码很久没有打开了。

    这种方法的优点:

    1. 轻松添加新语言
    2. 带翻译的单表
    3. 要为其他对象创建翻译,创建一个实体和一个带有翻译的链接表就足够了。

    这是图表: 在此处输入图像描述

    这个想法很简单:在表格中language,我们写下我们希望在网站上使用的语言。我们将文本写入表中translation并指明它属于哪种语言(使用 .-relation 表中的 id language。@ManyToOne)

    然后translation我们可以将不同的表连接到表。屏幕截图中的示例说明了页面翻译的原始版本。id是写的,页面上要翻译的文本的名字和页面的名字。在辅助表的帮助下text_translation,我收集了给定文本(@OneToMany-relation)的所有翻译。

    • 1
  2. Orkhan Hasanli
    2020-01-16T05:51:00Z2020-01-16T05:51:00Z

    因此,我对 Spring + Hibernate + JPA 进行了一些研究,并按如下方式实现了国际化:

    工作演示项目 - https://github.com/azerphoenix/spring-i18n-demo

    1)让我们创建实体:

    - -语

    @Entity
    @Data
    @Table(name = "languages")
    public class Language {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        // ID
        private Long languageId;
    
        private String languageName;
    
        private String locale;
    
        @OneToMany(mappedBy = "language")
        private List<Translation> translations = new ArrayList<>();
    
    }
    

    - - 翻译

    @Entity
    @Data
    @Table(name = "translations")
    public class Translation {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        // ID
        private Long translationId;
    
        /*@Lob*/
        @Column(name = "messagekey", length = 3000)
        private String key;
    
        /*@Lob*/
        @Column(name = "messagecontent", length = 100000)
        private String content;
    
        public Translation() {}
    
        @ManyToOne
        @JoinColumn(name = "locale")
        private Language language;
    
    }
    

    请注意,翻译和语言实体通过以下方式相互关联@OneToMany & @ManyToOne

    语言的表格视图

    +-------------+---------------+--------+
    | language_id | language_name | locale |
    +-------------+---------------+--------+
    |           1 | English       | en     |
    |           2 | Русский       | ru     |
    |           3 | Deutsch       | de     |
    +-------------+---------------+--------+
    

    语言表存储所有现有的(通过管理面板创建的)站点语言。如果您有固定数量的语言,那么您可以,例如,使用@PostConstruct默认语言进行初始化(或者使用 data.sql 创建必要的记录)。例如,

    @Service
    public class FirstRunService {
    
        @Autowired
        private LanguageRepository languageRepository;
    
        // Инициализируем языки
        @PostConstruct
        private void initLanguages() {
    
            Language de = new Language();
            de.setLanguageName("Deutsch");
            de.setLocale("de");
            languageRepository.save(de);
    
            Language ru = new Language();
            ru.setLanguageName("Русский");
            ru.setLocale("ru");
            languageRepository.save(ru);
    
            Language en = new Language();
            en.setLanguageName("English");
            en.setLocale("en");
            languageRepository.save(en);
        }
    }
    

    翻译的表格视图:

    +----------------+------------------+------------+--------+
    | translation_id |  messagecontent  | messagekey | locale |
    +----------------+------------------+------------+--------+
    |              4 | english_text     | key1       |      1 |
    |              5 | текст на русском | key1       |      2 |
    |              6 | text auf Deutsch | key1       |      3 |
    +----------------+------------------+------------+--------+
    

    翻译表存储在模板、表单等中显示值所需的翻译。

    实体的翻译(例如,帖子、页面、类别)作为单独的记录存储在实体本身中!因此,不会为它们创建单独的表。我从 WPML 插件 (WordPress) 和其他类似插件中采用了这个想法,而不是创建其他插件。表只是创建额外的。同一张表中的条目,但语言不同。

    让我们详细考虑一下。让我们创建一个 Post 实体来存储我们网站的文章。

    发布实体:

    @Entity
    @Data
    @Table(name = "posts")
    public class Post {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        // ID
        private Long postId;
    
        // Заголовок статьи
        private String postTitle;
    
        // Отрывок статьи
        @Lob
        @Column( length = 1500 )
        private String postExcerpt;
    
        // Текст статьи
        @Lob
        @Column( length = 100000 )
        private String postContent;
    
    
        @Lob
        @Column(length = 3000, nullable = false, updatable = false)
        private String translationKey;
    
        // ЧПУ статьи
        private String postSlug;
    
        // Дата и время создания
        @Column(nullable = false, updatable = false)
        private LocalDateTime creationDate;
    
        public Post(){}
    
    
        // Обратите внимание на этот участок кода!
        @ManyToOne
        @JoinColumn(name = "language_id")
        private Language language;
    }
    

    还要注意 translationKey 行。创建翻译时,我们生成UUID.randomUUID().toString();并将它们分配给一篇文章的所有翻译。

    由于我们通过@OneToMany & @ManyToOne(参见上面的代码)将 Post 与 Language 实体连接起来,因此对于 Language 实体,我们添加:

    @OneToMany(mappedBy = "language")
    private List<Page> pages = new ArrayList<>();
    

    Post 实体的表视图(帖子):

    +---------+---------------+---------------------+--------------+-------------------------+-----------+---------------------+-------------+
    | post_id | creation_date |     post_title      | post_content |      post_excerpt       | post_slug |   translationkey    | language_id |
    +---------+---------------+---------------------+--------------+-------------------------+-----------+---------------------+-------------+
    |       7 |             - | Überschrift Artikel | Artikel Text | Kurze Beschreibung      | post1     | Post_0001-1111-2222 |           1 |
    |       8 |             - | Заголовок статьи    | Текст статьи | Краткое описание статьи | post1     | Post_0001-1111-2222 |           2 |
    |       9 |             - | Post Title          | Post Content | Post Excerpt            | post1     | Post_0001-1111-2222 |           3 |
    +---------+---------------+---------------------+--------------+-------------------------+-----------+---------------------+-------------+
    

    注意最后一个 language_id 列。实际上,我们在一张表中创建了 3 条记录,它们的 language_id 不同。

    仍然需要建立多语制。在数据库中存储记录的多语言的想法已经从这里用尽了: https ://medium.com/i18n-and-l10n-resources-for-developers/database-stored-messages-for-i18n-in- spring-boot-11dc2ee5c1f7

    @Component("messageSource")
    public class DBMessageSource extends AbstractMessageSource {
    
        @Autowired
        private TranslationRepository translationRepository;
    
        private static final String DEFAULT_LOCALE_CODE = "az";
    
        @Override
        protected MessageFormat resolveCode(String key, Locale locale) {
    
            Translation message = translationRepository.findByKeyAndLanguageLocale(key, locale.getLanguage()).orElse(new Translation());
    
            if (message == null || message.getContent() == null) {
                return null;
            }
            return new MessageFormat(message.getContent(), locale);
        }
    
    }
    

    此类从翻译表中按键检索所需的翻译。

    翻译资料库

    @Repository
    public interface TranslationRepository extends JpaRepository<Translation, Long> {
    
        Optional<Translation> findByKeyAndLanguageLocale(String key, String locale);
    
        boolean existsByKeyAndLanguageLocale(String key, String locale);
    
    }
    

    语言库:

    @Repository
    public interface LanguageRepository extends JpaRepository<Language, Long> {
    
        Language findLanguageByLocale(String locale);
    
        boolean existsByLocale(String locale);
    
    
    }
    

    为了在控制器方法中获取文章的翻译,我们接受 locale 和 postSlug(或者,例如,translationKey),并相应地使用它们在数据库中查找文章并通过 Model 将其提供给客户端。代码示例:

    @Controller
    @RequestMapping("/posts")
    public class PostController {
    
        @Autowired
        PostRepository postRepository;
    
        @Autowired
        private LanguageRepository languageRepository;
    
        @GetMapping("{/postSlug}")
        public String possView (
                @PathVariable("postSlug") String postSlug,
                Model model,
                Locale locale
    
        ) {
    
            model.addAttribute("post", postRepository.findPostByPostSlugAndLocale(postSlug, languageRepository.findLanguageByLocale(locale.getLanguage()));
    
            return"possView.html";
        }
    
    }
    

    如果您需要从翻译表中获取所有翻译(例如,将它们交给管理面板进行编辑),那么根据 SQL Pivot 原理,我们使用这样的 SQL 语句:

    添加到 TranslationRepository:

    @Query(value =
                "SELECT \"messagekey\",\n" +
                        "       MAX(CASE WHEN (\"locale\"='1') THEN \"messagecontent\" ELSE null END ) AS content_de," +
                        "       MAX(CASE WHEN (\"locale\"='2') THEN \"messagecontent\" ELSE null END ) AS content_ru," +
                        "       MAX(CASE WHEN (\"locale\"='3') THEN \"messagecontent\" ELSE null END ) AS content_en," +
                        "       MAX(CASE WHEN (\"locale\"='1') THEN \"translation_id\" ELSE null END ) AS id_de," +
                        "       MAX(CASE WHEN (\"locale\"='2') THEN \"translation_id\" ELSE null END ) AS id_ru," +
                        "       MAX(CASE WHEN (\"locale\"='3') THEN \"translation_id\" ELSE null END ) AS id_en " +
                        "FROM \"translations\" GROUP BY \"messagekey\";", nativeQuery = true)
        List<TranslationProjection> getAllTranslationsWithLocaleAndId();
    

    注意实体 - TranslationProjection。TranslationProjection 是 Translation 实体的投影。其结构:

    public interface TranslationProjection {
    
        String getMessagekey();
    
        String getContent_De();
    
        String getContent_Ru();
    
        String getContent_En();
    
        Long getId_De();
    
        Long getId_Ru();
    
        Long getId_En();
    
    }
    

    此 SQL 查询的表视图:

    +------------+------------+------------+------------+-------+-------+-------+
    | messagekey | content_de | content_ru | content_en | id_de | id_ru | id_en |
    +------------+------------+------------+------------+-------+-------+-------+
    | key1       | Deutsch    | Русский    | Английский |    11 |    12 |    13 |
    | key2       | Deutsch    | Русский    | Английский |    14 |    15 |    16 |
    | key3       | Deutsch    | Русский    | Английский |    17 |    18 |    19 |
    +------------+------------+------------+------------+-------+-------+-------+
    

    好吧,您肯定需要为语言添加配置。

    @Configuration
    public class WebMVCConfig implements WebMvcConfigurer {
      /*
       * Internationalization settings
       */
    
      @Bean
      public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        slr.setDefaultLocale(new Locale("ru"));
        return slr;
      }
    
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        registry.addInterceptor(localeChangeInterceptor);
      }
    
    }
    

    因此,语言是通过?lang=${locale} 例如切换的,http://example.com/{postSlug}?lang=ru

    我不知道这个实现有多好......我只是将不同的想法组合成一堆......

    • 0

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5