Xliffie 2.4 - ようやくSwiftに移行し始めた

English version here.

Xliffie 2.4 がリリースされました!オンデバイス機能が実装されており、ネットなくてもアプリを翻訳できるようになりました。

今までと同じ翻訳 UI のソースのところに On-Device を選択すると、システムにインストールされてる言語パックで翻訳できるようになりました。インストールされてない場合はその場ですぐダウンロードできます。

UI


10年以上前のプロジェクトですが、ようやく Swift を入れました。時代の波でもあるし、必要でもありました。

私は多分周りよりもずっと Objective-C にしがみついてると思う、Objective-C Dinosaur です。Swift は嫌いではないが、過去に触った経験が微妙だったところもあって、ずっと距離を置いてた。Swift が発表された当時からずっと触ってたし使ってた、なんなら WWDC のその場にもいた、しかし毎年「新機能です!すごいです!」って言ってる割にはバグが多く、早期から Swift を触ってた人ならわかると思いますが、コンパイラが落ちることもよくあった。その中には修正できる機能もあれば、根本的に構造的に致命な問題もあったりする。取り込みたい機能が多すぎて、その欲張りのせいで型チェックが重くなる。「こんな機能もありますよ!」と宣伝してるが、その下に小さな文字で「*なお機能Aと機能Xを同時に使うとコンパイルがクッソ重くなります」と。ロジックを間違えたら私のせいですが、Swift 書くときは常にコンパイラの機嫌を伺わなければならない、コード的に正しくてもコンパイルできないかもしれない。

error: the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions

「型推論重すぎて諦めた」ってこれ、私のせいですか?型システムとチェッカーの問題だろ。昔、自分のマシンではコンパイルできたが、CI で重すぎてコンパイル落ちたプロジェクトもありました。また、1人のプロジェクトならまだいいが、複数人でプロジェクトを触ると、思わぬところで型チェックの時間制限に引っかかってエラー出た時の責任も面倒臭かった。

しかし Swift のコミュニティーは私よりずっとポジティブで積極的で素晴らしい。新しいメジャーバージョンが出るたびにコードを書き直したりしてるし、今イケてるライブラリーはほとんど Swift 製です、CocoaPods もReadonly になって、最近では使いたい API が Swift-only になった。時代の波だ、いよいよ Swift を入れる時が来た。

私のアプリ、Xliffie はアプリを翻訳するためのアプリです、ローカライゼーション、つまりシステムの言語設定に合わせて UI の言語を変える機能を実装するには、Xliff というファイルを編集する必要がありますが、それのエディターです。一番便利な機能は自動翻訳—— Google Translate や DeepL に翻訳してもらう機能。そして最近の macOS ではオンデバイス翻訳機能が実装されましたので早速使ってみたかったが、残念ながら Swift じゃないとアクセスできないAPIだった。

一見 Objective-C でもアクセスできそうに見えるが、実は唯一アクセスできる API は「すでにインストールされてる言語のみ翻訳できる」ナーフ状態だった1。設定アプリ隅っこに隠れてるボタンから翻訳言語をインストールしないといけないって、誰がするねん。

設定アプリの隅っこ

この制限はわざと作ったにしか見えない。

A session created using (SwiftUI’s) .translationTask() can always request downloads, however when it’s created directly using init(installedSource:target:) it cannot request downloads. The system only throws an error when attempting to translate and the languages aren’t installed. 2

要約すると「翻訳言語のダウンロードをトリガーしたければ SwiftUI 使ってね」。しょうがないからなんとか回避するしかない。

0x0の透明な SwiftUI View を作り、それを View に入れて TranslationSession をトリガーさせる。

struct TranslatorView: View {
    let configuration: TranslationSession.Configuration
    let onSession: (TranslationSession) async -> Void
    var body: some View {
        Color.clear.translationTask(configuration) { session in
            await onSession(session)
        }
    }
}

let view = TranslatorView(
    configuration: ...,
) { session in
    // ここでダウンロードをトリガーする
    await session.prepareTranslation()
}
let hosting = NSHostingView(rootView: view)
// Must be attached to a window for SwiftUI lifecycle to run
hosting.frame = .zero
hostingView = hosting
self.view.addSubview(hosting)

ダウンロード画面

これは間違いなく hacky だけど、これ以外の方法は思いつかなかった。なんせ iOS はともかく SwiftUI を Mac 上で使うのはまだ早い気がする。

ついでに CocoaPods から Swift Package Manager に移行した、今までの古い依存も全部モダンなものに更新した。久しぶりに Swift を触ったが昔よりも大分よくなった気がする。それは Apple のハードウエアがめっちゃ速くなったおかけなのか知らないが。


1

原文の言語と翻訳先の言語、両方インストールされてないとエラーが出る

2

https://developer.apple.com/documentation/translation/translationsession/canrequestdownloads