LogoNGA 开发文档

FYTxt 多语言框架

FYTxt 是支持 Kotlin MultiPlatform 以及 Compose MultiPlatform 的多语言框架

完全做到了 方便易用高性能全平台支持,可以监听系统语言变化,还支持锁定语言列表以便于自定义语言列表, 并且有翻译率统计与语言组设定

FYTxt 基于 FVV 而不是传统的 xml 存放文本, 原理是通过 Gradle 插件自动生成 Kotlin 文件, 可以通过配置来让指定语言的文本以 KDoc 形式为文本注释,通过 IDE 即可快速查看文本具体内容

并且 Android 平台的 Compose 方式实现了默认使用 Pangu Text 来达成优化中英文字符间距

支持范围

对于各平台的支持度如下:

平台运行环境获取方式自动更新语言列表备注
AndroidJVMJava API支持Compose 可自动重绘;常规使用需要手动在 Activity 添加监听
AndroidNativegetprop 命令行不支持
DesktopJVMJava API不支持
iOSNativeNative API不支持iOS 在切换语言后会重启,故无需监听。
LinuxNativeLANG 环境变量不支持
WindowsNativeWin32 API不支持
WebJS / WasmJS API支持

对于部分不支持自动更新语言列表的平台,如有需要请自行循环更新

也就是说,FYTxt 完全可在 常规 UICompose UI 以至于 共享库可执行 均可实现多语言

引入依赖

先依赖源中有 PkgPub 的依赖源

然后添加 FYTxt 的 Gradle 插件:

plugins {
    id("dev.oom-wg.purejoy.fyl.fytxt") version "+" apply false
}

FYTxt 可在 KMP(org.jetbrains.kotlin.multiplatform) 或 Android(com.android.application/com.android.library) 中使用

在项目中引入并配置内容:

plugins {
    id("dev.oom-wg.purejoy.fyl.fytxt")
}

fytxt {
    // 配置内容...
}

支持的配置内容如下:

Prop

Type

使用方法

配置概念

FYTxt 采用了 语言组 概念来存放各个语言变种(例如 常规语言喵言喵语), 第一个语言组将被视为默认语言组,其他语言组的语言支持范围需小于等于默认语言组, 当其他语言组出现文本缺失时,将优先采用默认语言组的同语言文本

FYTxt 将所有平台的语言标签均转为了下划线连接全大写文本(例如 ZH_HANS_CNEN_GB

不过当 Gradle 插件在搜索时,会自动将文件夹名称转为全大写,您依旧可以将文件夹名称命名为 zh_CN 等内容, 但不可使用连字符连接

默认语言也会在运行时自动转为全大写,它将作为 KDoc 生成时的默认语言与所有语言的回退文本使用

如果将默认语言设为 中文,且软件仅支持 中文英文,那么当系统语言为其他语言时,将显示 中文

由于部分语言可能难以匹配,所以支持配置语言与其对应的正则表达式,依此优化语言匹配效果

正则表达式对应的语言标签必须是全大写,所以建议所有语言标签都使用全大写

虽然 Android 已经支持了很多年的脚本标签(例如 zh-Hans),但是其匹配顺序难以推测,并且格式难写

大部分系统也只会返回 zh-CN 这样的语言与地区的标签,而不是 zh-Hans-CN 这样的完整标签

并且由于中文的体系复杂,中国大陆地区新加坡 使用简体中文,中国的大陆以外的 其他地区 使用繁体中文, 使用单纯的前缀匹配很难完整覆盖,故推出正则匹配

当该语言有正则规则时,只匹配其正则规则,若无,则使用前缀匹配, 如下规则可仅 中国大陆新加坡 使用简体中文,其他中文地区均使用繁体中文:

langAliases = mapOf(
    "ZH_HANS" to "^ZH_.*(HANS|CN|SG)",
    "ZH_HANT" to "^ZH_(?!.*(HANS|CN|SG)).*"
)

编写多语言文件

Gradle 插件会遍历传入的文件夹,以传入的文件夹的子文件夹作为语言标签,子文件夹内所有 FVV 文件作为语言数据

在目录下创建例如:

Hello = "你好"
Home = {
    Welcome = "欢迎%s"
}

以上内容会自动生成如下 kotlin 代码 (若以 common 目录作为语言组 Common,且其他可选选项均为默认配置):

internal object`FYTxt`{init{`FYTxtGroups`}
internal enum class`FYTxtGroups`:FYTxtGroup{`Common`{override val stats=mapOf(`FYTxtTags`.EN to 0.5,`FYTxtTags`.ZH to 1.0)};companion object{init{FYTxtConfig.init(`Common`,`FYTxtTags`.entries)}}}
internal enum class`FYTxtTags`:FYTxtTag{EN{override val pattern=null},ZH{override val pattern="""^ZH_(?!.*(HANS|CN|SG)).*""".toRegex()}}

/**你好
*/
val`Hello`get()=FYTxtConfig.activeTags.value.firstNotNullOfOrNull{it as`FYTxtTags`
when(it){
`FYTxtTags`.EN->"""Hello"""
`FYTxtTags`.ZH->"""你好"""
else -> null}
}?:"""你好"""
object`Home`{init{`FYTxtGroups`}
/**欢迎%s
*@suppress Common: EN
*/
val`Welcome`get()=FYTxtConfig.activeTags.value.firstNotNullOfOrNull{it as`FYTxtTags`
when(it){
`FYTxtTags`.ZH->"""欢迎%s"""
else -> null}
}?:"""欢迎%s"""
}
}

对如上代码的解释:

  • FYTxt 为自动生成的根类
  • FYTxtGroups 为语言组 enum,其中包含了各个语言的翻译率
  • FYTxtTags 为各个语言标签

具体的语言文本采用 firstNotNullOfOrNull 遍历 when 匹配 enum,性能好, 在经过 ProGuard/R8 优化后会变成性能更好的判断匹配

在代码中使用

由于要兼顾常规方式与 Compose 方式两个场景,以 getter 调用常规方式函数调用Compose 方式

如果要调用文本,只需要调用 FYTxt.Hello(常规方式) 或 FYTxt.Home.Welcome()(Compose 方式) 即可

调用 Compose 方式时,可以直接传入内容来实现格式化文本

Android JVM/JVM 支持格式化,其他平台仅有简单的 %s 替换

Android Jetpack Compose 输出后的文本默认经过 Pangu Text 处理

Android JVM 使用常规方式需手动调用 FYTxtConfig.updateTags() 以更新

示例
import dev.oom_wg.purejoy.fyl.fytxt.FYTxtConfig

class AppActivity : Activity() {
    override fun onConfigurationChanged(newConfig: Configuration) =
        super.onConfigurationChanged(newConfig).also { FYTxtConfig.updateTags() }
}

Compose 使用时,需要在 UI 外层使用 FYTxtProvider 以监听语言变化

详细功能说明

自定义语言列表

若需要自定义语言列表,而不是一直跟随系统,可使用 FYTxtConfig.updateTags():

  • tags: 自定义的语言列表,需要全大写下划线连接
  • lock: 锁定语言列表,锁定后将不会自动更新语言列表,仅可通过传入 tags 更新 (锁定/解锁 均执行一次即可,无需每次调用时都传入)

切换语言组

可使用 FYTxtConfig.updateGroup() 传入由插件自动生成的语言组 enum 以实现切换

获取翻译率

由插件自动生成的语言组 enum 中的每个语言组均已记录每个语言组中的每个语言的翻译率

在此页面...