diff --git a/.gitignore b/.gitignore index 504e73b..c975d23 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ local.properties # 模型文件体积大,不入库;运行时手动放入 app/src/main/assets/ *.onnx +~$*.docx +models/ diff --git a/app/src/main/java/com/k2fsa/sherpa/onnx/LanguageUtil.kt b/app/src/main/java/com/k2fsa/sherpa/onnx/LanguageUtil.kt new file mode 100644 index 0000000..a389f93 --- /dev/null +++ b/app/src/main/java/com/k2fsa/sherpa/onnx/LanguageUtil.kt @@ -0,0 +1,66 @@ +package com.k2fsa.sherpa.onnx + +import android.content.Context +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.os.LocaleListCompat +import java.util.Locale + +// ============================================================================ +// LanguageUtil — 应用界面语言(中/英/日 + 跟随系统) +// +// 持久化在 SharedPreferences;启动时 apply() 一次;切换走 +// AppCompatDelegate.setApplicationLocales(appcompat 1.6 自带 API<33 回移), +// 由框架自动重建 Activity。业务数据(声纹内部键、纪要 schema 键、jsonl) +// 一律不受界面语言影响 —— 语言只影响"显示层"。 +// ============================================================================ +object LanguageUtil { + const val MODE_SYSTEM = "system" + const val MODE_ZH = "zh" + const val MODE_EN = "en" + const val MODE_JA = "ja" + + private const val PREFS = "lang" + private const val KEY = "mode" + + fun mode(ctx: Context): String = + ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + .getString(KEY, MODE_SYSTEM) ?: MODE_SYSTEM + + fun apply(ctx: Context) { + val locales = when (mode(ctx)) { + MODE_ZH -> LocaleListCompat.forLanguageTags("zh") + MODE_EN -> LocaleListCompat.forLanguageTags("en") + MODE_JA -> LocaleListCompat.forLanguageTags("ja") + else -> LocaleListCompat.getEmptyLocaleList() // 跟随系统 + } + // 值相同则为 no-op,不会触发重建 + if (AppCompatDelegate.getApplicationLocales() != locales) { + AppCompatDelegate.setApplicationLocales(locales) + } + } + + fun set(ctx: Context, m: String) { + ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + .edit().putString(KEY, m).apply() + // setApplicationLocales 会自动 recreate;system 模式下清空后回退 + // 到当前显示语言,避免"空 locale 不生效导致不重建" + val target = if (m == MODE_SYSTEM) currentFallback() else m + val locales = when (target) { + MODE_JA -> LocaleListCompat.forLanguageTags("ja") + MODE_EN -> LocaleListCompat.forLanguageTags("en") + else -> LocaleListCompat.forLanguageTags("zh") + } + AppCompatDelegate.setApplicationLocales(locales) + if (m == MODE_SYSTEM) { + // 再切回真正的跟随系统(下一次启动生效) + ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + .edit().putString(KEY, MODE_SYSTEM).apply() + AppCompatDelegate.setApplicationLocales( + LocaleListCompat.forLanguageTags(currentFallback()) + ) + } + } + + private fun currentFallback(): String = + Locale.getDefault().language.takeIf { it == "ja" || it == "en" } ?: "zh" +} diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml new file mode 100644 index 0000000..4180b40 --- /dev/null +++ b/app/src/main/res/values-en/strings.xml @@ -0,0 +1,75 @@ + + Tap Start to begin live transcription.\n\nSource code and all models are open resources — see github.com/k2fsa/sherpa-onnx.\nTwo-pass recognition · speaker ID · offline meeting minutes + Start + Stop + Pause + Resume + OK + Cancel + Minutes + Cancel + Copy + History + Back to live + Language + UI language + Follow system + Tap “Minutes” to summarize this meeting offline. The latest minutes appear here. + + Loading 1st-pass model (nemotron-3.5)… + Loading 2nd-pass model (funasr-nano)… + Loading speaker model… + Models ready. + Tap Start to begin + Speaker ID enabled + ⚠ Speaker ID off: model %1$s missing (adb push it to %2$s) + Model loading failed: %1$s\n\nAll models live under %2$s — push them with adb + + Identified %1$d + Unknown + + Name speaker “%1$s” + Rename “%1$s” to… + Real name for “%1$s”, e.g. Alice + Name can’t be “S+digits” or a reserved display name + Renamed to “%1$s”; adjacent lines of the same speaker merged + Speaker not settled yet — finish a full sentence first + + Past meetings + Recording — end this session before browsing history + No past meetings to show + %1$s · %2$d lines · %3$s%4$s + · has minutes + History: %1$s (read-only) + + Choose a meeting to summarize + [Current meeting] %1$d lines · %2$s + · has minutes (continue) + Nothing to summarize yet — record a meeting first + This meeting has no settled lines + Minutes in progress — tap again to cancel + This device ABI lacks the summarizer runtime (arm64-v8a only) + Summarizer model missing. Run:\n%1$s + Loading summarizer model (%1$s)… + notes + drafting + Running %1$s: %2$d/%3$d + Model ready, resuming from checkpoint (%1$d/%2$d lines done)… + Model ready, %1$d lines — building notes… + Minutes saved (%1$s, %2$d chunks%3$s): %4$s + , %1$d chunks failed + Cancelled (previous minutes kept) + Cancelled automatically: %1$s (previous minutes kept) + Failed: %1$s + Cancelled + Cancelled automatically + Summarizing — transcription continues uninterrupted + Minutes copied to clipboard + + Last minutes: %1$s (will be overwritten by a new run) + Meeting “%1$s” has no minutes yet — tap “Minutes” to finish it (incremental) + + Session active — end recording before switching language + Copied to clipboard + Cancelling… + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..50ac339 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,75 @@ + + 「開始」を押すとリアルタイム文字起こしを開始します。\n\nソースコードとモデルはすべて公開されています: github.com/k2fsa/sherpa-onnx\n2パス認識 · 話者識別 · オフライン議事録 + 開始 + 停止 + 一時停止 + 続行 + OK + キャンセル + 議事録生成 + 生成を中止 + 議事録コピー + 過去の会議 + 現在に戻る + 言語 + 表示言語 + システムに追従 + 「議事録生成」を押すとこの会議をオフラインで要約します。結果と前回の議事録はここに表示されます + + 第1パスモデル(nemotron-3.5)を読み込み中… + 第2パスモデル(funasr-nano)を読み込み中… + 声紋モデルを読み込み中… + モデルの準備が完了しました。 + 「開始」を押して認識してください + 話者識別は有効です + ⚠ 話者識別は無効:声紋モデル %1$s がありません(%2$s へ adb push してください) + モデル読み込みに失敗:%1$s\n\nモデルはすべて %2$s に配置 — adb push で配信してください + + 識別済み %1$d + 未識別 + + 話者「%1$s」に名前を付ける + 「%1$s」を次の名前に変更… + 「%1$s」の本名(例:田中) + 名前は「S+数字」やシステム予約名は使えません + 「%1$s」に改名し、隣接する同一話者の行を結合しました + この発話の話者はまだ確定していません。一文話し終えてから命名してください + + 過去の会議 + 録音中です。今回の会議を終了してから履歴を閲覧できます + 閲覧できる過去の会議がありません + %1$s · %2$d 発 · %3$s%4$s + · 議事録あり + 履歴:%1$s(読み取り専用) + + 要約する会議を選択 + 【現在の会議】%1$d 発 · %2$s + · 議事録あり(続きから) + 要約できる会議がありません。先に録音してください + この会議に確定した発話がありません + 議事録を生成中です。もう一度押すと中止できます + この端末のABIには要約ランタイムが含まれていません(arm64-v8aのみ) + 要約モデルがありません。実行してください:\n%1$s + 要約モデル(%1$s)を読み込み中… + メモ更新 + 清書 + %1$s実行中: %2$d/%3$d + モデル準備完了、前回の中断点から続行(%1$d/%2$d 消化済み)… + モデル準備完了、全 %1$d 発、メモ構築を開始… + 議事録を保存しました(%1$s、%2$d ブロック%3$s):%4$s + 、%1$d ブロック解析失敗 + 中止しました(前回の議事録は保持) + 自動キャンセル:%1$s(前回の議事録は保持) + 生成失敗:%1$s + 中止しました + 自動キャンセルしました + 議事録生成中でも文字起こしは停止しません + 議事録をクリップボードにコピーしました + + 前回の議事録:%1$s(新規生成すると上書きされます) + 前回の会議「%1$s」は議事録未生成です。「議事録生成」で続きを生成できます(増分、新規発話のみ処理) + + セッション終了後に言語を切り替えられます + クリップボードにコピーしました + 中止中… + diff --git a/docs/项目阶段性总结_2026-09-11.docx b/docs/项目阶段性总结_2026-09-11.docx new file mode 100644 index 0000000..9021136 Binary files /dev/null and b/docs/项目阶段性总结_2026-09-11.docx differ