Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal]: 改进的i18n #1172

Open
yqs112358 opened this issue Mar 28, 2023 · 21 comments
Open

[Proposal]: 改进的i18n #1172

yqs112358 opened this issue Mar 28, 2023 · 21 comments
Labels
enhancement Intended improvement or added functionality status: proposal Suggestion or idea for consideration
Milestone

Comments

@yqs112358
Copy link
Member

yqs112358 commented Mar 28, 2023

原因

ll2现有的的i18n机制存在相当严重的问题。见现有机制的样例代码:

logger.error(tr("ll.addonsHelper.addAddonToList.fail", addon.getPrintName(), addonListFile));

从源码中可以看出,tr前面的部分为翻译文件的层级结构,不能直接看到翻译内容。老方案有如下问题:

  • 翻译文件的实际层次结构意义不大,用户基本都是在crowdin中用编辑器翻译完毕之后直接下载产物
  • 当前的设计给修改翻译文本带来麻烦,每次需要去crowdin搜索并修改,加上crowdin字符串搜索不能带”.“,导致使用体验较差
  • 无法实时在代码中根据翻译内容考虑如何传参,修改翻译或者修改传参的过程中可能出现问题
  • 如果需要为已有插件编写i18n,需要逐个复制到crowdin,修改占位符,再复制翻译标识回来改写源码,整个修改操作复杂且容易出错,比较坐牢。导致开发者无动力适配i18n
  • 给fmt库传入未经验证的字符串有较大的风险;在编写代码时无法保证前面的翻译字符串一定正确,如果在crowdin中不慎遗漏或者破坏了占位符,实际运行时将导致发生崩溃
  • 如果翻译文件不存在,会导致显示炸裂

改进思路

模仿QT设计,在源码中直接使用如下方式编写代码:

logger.error(tr("Invalid Addon List File {0}, backup to {1}", addon.getPrintName(), addonListFile));

直接把源字符串放到了格式字符串位置,整个用法跟fmt写法一致。同时如果需要修改字符串或者传参,直接修改原字符串即可,不需要离开IDE
如果语言文件(或部分译文条目)不存在,可以直接fallback到原文字符串,避免显示炸裂

初步方案

翻译提取方法

方案一: 类似Qt Linguist,编写一个单独的程序,使用正则匹配/clang解析AST,匹配出格式字符串并输出到文件,以供上传到crowdin进行翻译操作
方案二: 对Translation::load进行改动,使用元编程在编译期收集所有的格式字符串并放入一个常量数组。当load发现翻译文件不存在(或者部分条目缺失)时,在目标位置根据此数组自动生成(或者补全)空白的语言文件,以用于翻译。运行时tr函数直接根据原文字符串匹配加载译文即可。

语言文件和Key

新方案的语言文件建议使用一个单层的json,每一条的key为原文字符串,value为翻译字符串
如果翻译字符串为空,或者词条直接不存在,则fallback到原文字符串。无需额外设置fallback语言类型。

未来

未来LLSE i18n也需要引进此方案,在翻译提取程序处稍作修改即可

(补充:有关提高性能的考虑)

考虑到i18n处理的对象为字符串,在实际代码运行过程中会被频繁调用,而直接使用原文字符串作为unordered_map的key进行查询会导致运行时频繁计算字符串哈希。因此有如下改进方案:
在读取翻译文件的同时建立 Trie,将字符串翻译结果挂载到对应的叶子节点上。在查找字符串时直接查询 Trie 得到结果。

@yqs112358 yqs112358 added enhancement Intended improvement or added functionality status: proposal Suggestion or idea for consideration labels Mar 28, 2023
@Jasonzyt
Copy link
Member

Jasonzyt commented Apr 2, 2023

嵌套模式纯粹是因为crowdin会自动这样分层,你去试试就知道了(
在浏览器crowdin添加字符串Id设为a.b.c就会自动分,."a.b.c"不会分

@yqs112358
Copy link
Member Author

我知道,所以直接让程序或者i18n API自己生成,一方面省的在crowdin里面手动加,一方面也没这个问题了

@RimuruChan
Copy link
Member

@yqs112358 估计还得做个编辑器,和context解析器

@yqs112358
Copy link
Member Author

@yqs112358 估计还得做个编辑器,和context解析器

emm,我感觉不用(?
就,提取内容的话用之前讨论的那个办法,i18n API在load的时候如果发现源文件不存在,就自动生成一套空白的
然后把生成的传到crowdin就行了,用他的编辑器
弄完下下来就OK

@Jasonzyt
Copy link
Member

Jasonzyt commented Apr 2, 2023

@yqs112358 估计还得做个编辑器,和context解析器

如果原文改了,context不就寄了

@RimuruChan
Copy link
Member

@Jasonzyt 原文改了,按理翻译也得改

@yqs112358
Copy link
Member Author

crowdin那边可以update文件的,想必他也能根据变动自动更新要翻译的内容

@Jasonzyt
Copy link
Member

Jasonzyt commented Apr 2, 2023

@Jasonzyt 原文改了,按理翻译也得改

crowdin上的translation记录就没了…

@RimuruChan
Copy link
Member

@Jasonzyt 所以这就是和qt的linguist有冲突的地方

@yqs112358
Copy link
Member Author

是啊,是这样的,每次有变动的话就重新翻译一下

@RimuruChan
Copy link
Member

@Jasonzyt 我觉得节点还是要有,但是能不能改进

@RimuruChan
Copy link
Member

@RimuruChan 前段时间我就在想怎么弄,不过没想出好方案

@RimuruChan
Copy link
Member

@RimuruChan 或许可以模板放节点,默认内容放函数体😘

@yqs112358
Copy link
Member Author

节点的话一定要说有用也可能有点用,比如两个不同地方的同一个语句可能有不同的意思
但是得想办法设计一个优雅一点的API,至少默认情况下要按这里提的方案来

@yqs112358
Copy link
Member Author

@RimuruChan 或许可以模板放节点,默认内容放函数体😘

可以

@RimuruChan
Copy link
Member

@yqs112358 晚点再看看具体怎么实现罢,生搬硬套肯定不行,最好就是取其精华😘

@lgc2333
Copy link

lgc2333 commented Apr 3, 2023

有个东西叫gnu gettext

@RimuruChan
Copy link
Member

有个东西叫gnu gettext

GPL does not compatible with LGPL

@futrime futrime changed the title LL3基础设施:改进的i18n Proposal #3: 改进的i18n Jun 8, 2023
@futrime futrime changed the title Proposal #3: 改进的i18n Proposal: 改进的i18n Jun 8, 2023
@yqs112358
Copy link
Member Author

yqs112358 commented Sep 12, 2023

方案二有点困难,蚌。目前看来如果条件允许,建议优先考虑方案一。

如果有能搞出在编译期,不依赖变量储存纯stateful meta programing实现的变量容器,或者什么编译期可变constexpr容器之类的,欢迎大佬补全

目前进度:(卡在 ”待实现:插formatStr到编译期容器“ 那里)

#include <array>
#include <string_view>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cstdio>

using namespace std;

// 待实现:插formatStr到编译期容器
template<auto formatStr>
consteval int _trAddString()
{
    //...
    return 0;
}


// formatStr为下面lambda返回的那个Str对象
// 使用formatStr.get()获得原C风格字面值,formatStr.get_string_view获取string_view(均为constexpr)
template<auto formatStr, typename... Args>
std::string_view trImpl(Args... args) {
    constexpr int res = _trAddString<formatStr>();     // 插str到编译期容器

    // 接下来:从翻译map中根据原文读取译文,如果译文存在返回译文,如果不存在就fallback到原文
    return formatStr.get_string_view();
}

// 将原文字符串塞进模板参数中,让每个不同的原文字符串都能单独特例化一次trImpl
// 实现:对于每个字符串都分配一个单独的对象,且在对象中携带其原字符串
struct CompileString {};
#define COMPILE_STRING(formatStr) []() \
{ \
    struct Str : public CompileString \
    { \
        constexpr std::string_view get_string_view() const { return {(formatStr), sizeof(formatStr)}; } \
        constexpr const char* get() const { return (formatStr); } \
    }; \
    return Str{}; \
}()

// 美观起见转发一下
#define tr(formatStr, ...) \
    trImpl<COMPILE_STRING(formatStr)>(##__VA_ARGS__)

void not_called()
{
    tr("hello {}", 1);
    tr("world {} {}", 2, "test");
}

void load() {
    // ...

    // 首先遍历翻译map中已有所有的原文,对于任何一个不在strings中的原文,删除对应的翻译条目
    // 再遍历strings,对于每个有效的原文,如果不存在于翻译map,则添加一个对应的空条目
    /*for (auto str : strings)
    {
        if (!str) break;
        cout << str << endl;

    }*/
    // 全部处理完后将map写入翻译文件
}

int main()
{
    tr("liteloaderbds   author:{1}   version:{0}", "v1.2.3", "LiteLDev");
    load();
    system("pause");
    return 0;
}

@OEOTYAN OEOTYAN closed this as not planned Won't fix, can't repro, duplicate, stale Oct 11, 2023
@yqs112358 yqs112358 reopened this Oct 11, 2023
@futrime futrime added priority: low Can be postponed and removed priority: low Can be postponed labels Oct 14, 2023
@OEOTYAN
Copy link
Member

OEOTYAN commented Dec 2, 2023

哈哈 这个还真做好了 有个宏定义可以开

@OEOTYAN OEOTYAN closed this as completed Dec 2, 2023
@RimuruChan RimuruChan added this to the v1.0.0 milestone Jan 15, 2024
@RimuruChan RimuruChan reopened this Jan 29, 2024
@RimuruChan RimuruChan changed the title Proposal: 改进的i18n [Proposal]: 改进的i18n Jan 30, 2024
@RimuruChan
Copy link
Member

应该会使用gettext的方案,不过不用gnu的库,自己实现

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Intended improvement or added functionality status: proposal Suggestion or idea for consideration
Development

No branches or pull requests

6 participants