
Simplified/traditional Chinese conversion with zero-copy .sok dictionary format, very fast conversions, inspect() staged outputs for diffs/highlighting, sixteen regional/config profiles and upstream-accurate behavior.
Sokkuri 是 OpenCC 的 Kotlin Multiplatform 移植,支持 JVM、Android 与 iOS,转换结果与上游 C++ 核心一致。
名称取自日语「すっきり」(清爽、利落),汉字取「晰」。
inspect():除了最终结果,还返回分词与转换链各级的输出,结构与上游 Converter::Inspect 一致。上游只在 C++ 与命令行中提供这项能力。s2hkp 与 hk2sp。.sok:词典共约 1.7 MiB,加载时不复制。s2t 转换 48 个字约 1.8 µs(JVM)。详见性能。repositories { mavenCentral() }
dependencies {
implementation("com.generalk1ng.sokkuri:sokkuri-runtime:0.2.1")
}sokkuri-runtime,其余模块会传递引入。Sokkuri.init(context)。词典随 klib 以 kotlin_resources 变体发布。用了 Compose Multiplatform 插件的工程不需要额外配置。纯 KMP 工程要在 shared 模块里把资源接入编译,并拷进 framework,因为 Kotlin/Native 不会自动拷贝资源:
// shared/build.gradle.kts
import org.jetbrains.kotlin.gradle.ComposeKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublication
@OptIn(ComposeKotlinGradlePluginApi::class)
kotlin.targets.withType<KotlinNativeTarget>().configureEach {
val kmpResources = project.extraProperties
.get(KotlinTargetResourcesPublication.EXTENSION_NAME) as KotlinTargetResourcesPublication
val sokkuriResources = kmpResources.resolveResources(this)
compilations["main"].defaultSourceSet.resources.srcDir(sokkuriResources)
// 把词典拷进 framework,iOS 的加载器经 NSBundle 读取
binaries.withType<Framework>().configureEach {
val copyDictionaries = tasks.register<Copy>("copySokkuriDictionaries") {
from(sokkuriResources)
into(outputFile)
}
linkTaskProvider.configure { finalizedBy(copyDictionaries) }
}
}单元测试同理:把 sokkuriResources 拷到测试二进制所在的目录,可参考本仓库 sokkuri-runtime 构建脚本里的 copyResourcesBeside* 任务。
val converter = Sokkuri.create(SokkuriConfig.S2TWP) // 简体 → 台湾正体,含台湾惯用词
converter.convert("鼠标里面的硅二极管坏了,导致光标分辨率降低。")
// → 滑鼠裡面的矽二極體壞了,導致游標解析度降低。配置名中 s 为简体,t 为繁体,tw 为台湾,hk 为香港,jp 为日文;后缀 p 表示同时转换地区惯用词。
| 简 → 繁 | 繁 → 简 | 繁体互转 | 日文 |
|---|---|---|---|
s2t 通用繁体 |
t2s |
t2tw / tw2t
|
jp2t 新字体 → 旧字体 |
s2tw 台湾字形 |
tw2s |
t2hk / hk2t
|
t2jp 旧字体 → 新字体 |
s2twp 台湾字形与词汇 |
tw2sp |
||
s2hk 香港字形 |
hk2s |
||
s2hkp 香港字形与词汇 |
hk2sp |
同一句话在各档下的结果:
内存里的一只烤面包机正在读取打印服务器的硬盘。
s2t 內存裏的一隻烤麪包機正在讀取打印服務器的硬盤。
s2hk 內存裏的一隻烤麪包機正在讀取打印服務器的硬盤。
s2tw 內存裡的一隻烤麵包機正在讀取打印服務器的硬盤。
s2twp 記憶體裡的一隻烤麵包機正在讀取列印伺服器的硬碟。
inspect() 返回转换过程:
val inspection = Sokkuri.create(SokkuriConfig.S2T).inspect("软件和网络")
inspection.segments // [软件和网络]:分词结果
inspection.stages // stage 1: [軟件和網絡]:转换链各级的输出
inspection.output // 軟件和網絡繁转简默认不使用少数可能在部分设备上显示为豆腐块(□)的字形映射,因此有 3 条上游测试期望与上游不同。需要与上游完全一致时:
Sokkuri.create(SokkuriConfig.T2S, SokkuriOptions { includeTofuRiskDictionaries = true })推荐用 createResult,它不抛异常:
import Sokkuri
let config = Sokkuri_apiSokkuriConfig.companion.fromStem(stem: "s2twp")!
let result = Sokkuri.Companion.shared.createResult(
config: config,
options: Sokkuri_apiSokkuriOptions.companion.DEFAULT,
)
if let ok = result as? Sokkuri.CreateResultSuccess {
print(ok.sokkuri.convert(input: "鼠标里面的硅二极管坏了"))
} else if let failure = result as? Sokkuri.CreateResultFailure {
// failure.error 声明为基类,可以 cast 到具体的子类
if let missing = failure.error as? Sokkuri_apiSokkuriException.FileNotFound {
print("缺少资源:\(missing.path)")
}
}也可以用 create,调用时加 try。Kotlin 异常到 Swift 一律是 NSError(domain 为 KotlinException),catch let e as SokkuriException 能编译但永远不会匹配:
do {
let sokkuri = try Sokkuri.Companion.shared.create(config: config, options: options)
} catch let error as NSError {
print(error.kotlinException ?? error)
}另外:
sokkuri-api 里的类型在 Swift 中带模块前缀,如 Sokkuri_apiSokkuriConfig;Sokkuri 与 Sokkuri.CreateResult 不带。switch 穷尽,只能用 as? 判断。./gradlew build # 全部平台的构建与测试
./gradlew :sokkuri-runtime:jvmTest # 只跑 JVM 测试
./gradlew :sokkuri-runtime:iosSimulatorArm64Test # iOS 模拟器测试,需要 Apple Silicon
./gradlew :tools:benchmark:run --args="convert --profile s2t" # 性能基准以下检查不在 build 里:
./gradlew fetchOpencc 按锁定的提交检出),比对还需要 cmake 与 C++ 编译器。命令是 ./gradlew :tools:upstream-diff:run 与 ./gradlew :tools:dictgen:checkDictionaries。CI 每周运行它们,发布前也会运行。.sok 的 Rust 独立读取器:./gradlew sokConformance,需要 Rust。不装 Rust 不影响构建与发布。设计与验证的文档见 docs/。
Apache License 2.0
Sokkuri 是 OpenCC 的 Kotlin Multiplatform 移植,支持 JVM、Android 与 iOS,转换结果与上游 C++ 核心一致。
名称取自日语「すっきり」(清爽、利落),汉字取「晰」。
inspect():除了最终结果,还返回分词与转换链各级的输出,结构与上游 Converter::Inspect 一致。上游只在 C++ 与命令行中提供这项能力。s2hkp 与 hk2sp。.sok:词典共约 1.7 MiB,加载时不复制。s2t 转换 48 个字约 1.8 µs(JVM)。详见性能。repositories { mavenCentral() }
dependencies {
implementation("com.generalk1ng.sokkuri:sokkuri-runtime:0.2.1")
}sokkuri-runtime,其余模块会传递引入。Sokkuri.init(context)。词典随 klib 以 kotlin_resources 变体发布。用了 Compose Multiplatform 插件的工程不需要额外配置。纯 KMP 工程要在 shared 模块里把资源接入编译,并拷进 framework,因为 Kotlin/Native 不会自动拷贝资源:
// shared/build.gradle.kts
import org.jetbrains.kotlin.gradle.ComposeKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublication
@OptIn(ComposeKotlinGradlePluginApi::class)
kotlin.targets.withType<KotlinNativeTarget>().configureEach {
val kmpResources = project.extraProperties
.get(KotlinTargetResourcesPublication.EXTENSION_NAME) as KotlinTargetResourcesPublication
val sokkuriResources = kmpResources.resolveResources(this)
compilations["main"].defaultSourceSet.resources.srcDir(sokkuriResources)
// 把词典拷进 framework,iOS 的加载器经 NSBundle 读取
binaries.withType<Framework>().configureEach {
val copyDictionaries = tasks.register<Copy>("copySokkuriDictionaries") {
from(sokkuriResources)
into(outputFile)
}
linkTaskProvider.configure { finalizedBy(copyDictionaries) }
}
}单元测试同理:把 sokkuriResources 拷到测试二进制所在的目录,可参考本仓库 sokkuri-runtime 构建脚本里的 copyResourcesBeside* 任务。
val converter = Sokkuri.create(SokkuriConfig.S2TWP) // 简体 → 台湾正体,含台湾惯用词
converter.convert("鼠标里面的硅二极管坏了,导致光标分辨率降低。")
// → 滑鼠裡面的矽二極體壞了,導致游標解析度降低。配置名中 s 为简体,t 为繁体,tw 为台湾,hk 为香港,jp 为日文;后缀 p 表示同时转换地区惯用词。
| 简 → 繁 | 繁 → 简 | 繁体互转 | 日文 |
|---|---|---|---|
s2t 通用繁体 |
t2s |
t2tw / tw2t
|
jp2t 新字体 → 旧字体 |
s2tw 台湾字形 |
tw2s |
t2hk / hk2t
|
t2jp 旧字体 → 新字体 |
s2twp 台湾字形与词汇 |
tw2sp |
||
s2hk 香港字形 |
hk2s |
||
s2hkp 香港字形与词汇 |
hk2sp |
同一句话在各档下的结果:
内存里的一只烤面包机正在读取打印服务器的硬盘。
s2t 內存裏的一隻烤麪包機正在讀取打印服務器的硬盤。
s2hk 內存裏的一隻烤麪包機正在讀取打印服務器的硬盤。
s2tw 內存裡的一隻烤麵包機正在讀取打印服務器的硬盤。
s2twp 記憶體裡的一隻烤麵包機正在讀取列印伺服器的硬碟。
inspect() 返回转换过程:
val inspection = Sokkuri.create(SokkuriConfig.S2T).inspect("软件和网络")
inspection.segments // [软件和网络]:分词结果
inspection.stages // stage 1: [軟件和網絡]:转换链各级的输出
inspection.output // 軟件和網絡繁转简默认不使用少数可能在部分设备上显示为豆腐块(□)的字形映射,因此有 3 条上游测试期望与上游不同。需要与上游完全一致时:
Sokkuri.create(SokkuriConfig.T2S, SokkuriOptions { includeTofuRiskDictionaries = true })推荐用 createResult,它不抛异常:
import Sokkuri
let config = Sokkuri_apiSokkuriConfig.companion.fromStem(stem: "s2twp")!
let result = Sokkuri.Companion.shared.createResult(
config: config,
options: Sokkuri_apiSokkuriOptions.companion.DEFAULT,
)
if let ok = result as? Sokkuri.CreateResultSuccess {
print(ok.sokkuri.convert(input: "鼠标里面的硅二极管坏了"))
} else if let failure = result as? Sokkuri.CreateResultFailure {
// failure.error 声明为基类,可以 cast 到具体的子类
if let missing = failure.error as? Sokkuri_apiSokkuriException.FileNotFound {
print("缺少资源:\(missing.path)")
}
}也可以用 create,调用时加 try。Kotlin 异常到 Swift 一律是 NSError(domain 为 KotlinException),catch let e as SokkuriException 能编译但永远不会匹配:
do {
let sokkuri = try Sokkuri.Companion.shared.create(config: config, options: options)
} catch let error as NSError {
print(error.kotlinException ?? error)
}另外:
sokkuri-api 里的类型在 Swift 中带模块前缀,如 Sokkuri_apiSokkuriConfig;Sokkuri 与 Sokkuri.CreateResult 不带。switch 穷尽,只能用 as? 判断。./gradlew build # 全部平台的构建与测试
./gradlew :sokkuri-runtime:jvmTest # 只跑 JVM 测试
./gradlew :sokkuri-runtime:iosSimulatorArm64Test # iOS 模拟器测试,需要 Apple Silicon
./gradlew :tools:benchmark:run --args="convert --profile s2t" # 性能基准以下检查不在 build 里:
./gradlew fetchOpencc 按锁定的提交检出),比对还需要 cmake 与 C++ 编译器。命令是 ./gradlew :tools:upstream-diff:run 与 ./gradlew :tools:dictgen:checkDictionaries。CI 每周运行它们,发布前也会运行。.sok 的 Rust 独立读取器:./gradlew sokConformance,需要 Rust。不装 Rust 不影响构建与发布。设计与验证的文档见 docs/。
Apache License 2.0