Ariver
2026-05-06 108b05c619b432f093e9cd64a15ce3bbc0e02840
checkpoint: v3.1.0 — 国际化:9语种 JSON 翻译文件 + Language 菜单 + L10n/tr()
4 files modified
12 files added
521 ■■■■■ changed files
Apptag/ApptagApp.swift 32 ●●●● patch | view | raw | blame | history
Apptag/Info.plist 2 ●●● patch | view | raw | blame | history
Apptag/L10n.swift 63 ●●●●● patch | view | raw | blame | history
Apptag/Localization/en.json 46 ●●●●● patch | view | raw | blame | history
Apptag/Localization/es.json 46 ●●●●● patch | view | raw | blame | history
Apptag/Localization/fr.json 46 ●●●●● patch | view | raw | blame | history
Apptag/Localization/it.json 46 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ja.json 46 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ko.json 46 ●●●●● patch | view | raw | blame | history
Apptag/Localization/ru.json 46 ●●●●● patch | view | raw | blame | history
Apptag/Localization/zh-Hans.json 46 ●●●●● patch | view | raw | blame | history
Apptag/Localization/zh-Hant.json 46 ●●●●● patch | view | raw | blame | history
CHANGELOG.md 7 ●●●●● patch | view | raw | blame | history
X-screencap/SCR-20260506-puyk.jpeg patch | view | raw | blame | history
X-screencap/录制于 2026-05-06 18.05.17.mp4 patch | view | raw | blame | history
build.sh 3 ●●●●● patch | view | raw | blame | history
Apptag/ApptagApp.swift
@@ -24,7 +24,7 @@
    private var isInEditMode = false  // Suppress auto-dismiss during editing
    func applicationDidFinishLaunching(_ notification: Notification) {
        // Use .accessory so the app appears in Force Quit (unlike LSUIElement)
        L10n.setup()
        NSApp.setActivationPolicy(.accessory)
        setupMenuBar()
        registerHotkey()
@@ -50,7 +50,7 @@
        let menu = NSMenu()
        menu.addItem(
            NSMenuItem(
                title: "Show Apptag",
                title: tr("menu.show"),
                action: #selector(toggleOverlay),
                keyEquivalent: ""
            )
@@ -61,7 +61,7 @@
        let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "?"
        let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "?"
        let versionItem = NSMenuItem(
            title: "Version \(appVersion) (\(buildNumber))",
            title: "\(tr("menu.version")) \(appVersion) (\(buildNumber))",
            action: nil,
            keyEquivalent: ""
        )
@@ -70,16 +70,31 @@
        menu.addItem(.separator())
        let prefsItem = NSMenuItem(
            title: "Preferences...",
            title: tr("menu.preferences"),
            action: #selector(openPreferences),
            keyEquivalent: ","
        )
        prefsItem.keyEquivalentModifierMask = .command
        menu.addItem(prefsItem)
        menu.addItem(.separator())
        // Language submenu
        let langMenu = NSMenu()
        let currentLang = UserDefaults.standard.string(forKey: "appLanguage") ?? "en"
        for (code, name) in L10n.supported {
            let item = NSMenuItem(title: name, action: #selector(switchLanguage(_:)), keyEquivalent: "")
            item.representedObject = code
            item.state = (code == currentLang) ? .on : .off
            langMenu.addItem(item)
        }
        let langItem = NSMenuItem(title: tr("menu.language"), action: nil, keyEquivalent: "")
        langItem.submenu = langMenu
        menu.addItem(langItem)
        menu.addItem(.separator())
        menu.addItem(
            NSMenuItem(
                title: "Quit",
                title: tr("menu.quit"),
                action: #selector(NSApplication.terminate(_:)),
                keyEquivalent: "q"
            )
@@ -237,6 +252,13 @@
        hideOverlay()
        NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
    }
    @objc private func switchLanguage(_ sender: NSMenuItem) {
        guard let code = sender.representedObject as? String else { return }
        L10n.switchTo(code)
        // Rebuild menu to update checkmarks
        setupMenuBar()
    }
}
// MARK: - NSView-level backdrop dismiss (works even if SwiftUI rendering is slow)
Apptag/Info.plist
@@ -19,7 +19,7 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>3.0.11</string>
    <string>3.1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSMinimumSystemVersion</key>
Apptag/L10n.swift
New file
@@ -0,0 +1,63 @@
import Foundation
// MARK: - Localization Manager
enum L10n {
    static var current: [String: String] = [:]
    /// Call once at startup to load the saved or system language.
    static func setup() {
        let code = UserDefaults.standard.string(forKey: "appLanguage") ?? fallbackCode()
        load(code)
    }
    static let supported: [(code: String, name: String)] = [
        ("en", "English"),
        ("zh-Hans", "简体中文"),
        ("zh-Hant", "繁體中文"),
        ("ja", "日本語"),
        ("ko", "한국어"),
        ("ru", "Русский"),
        ("fr", "Français"),
        ("it", "Italiano"),
        ("es", "Español"),
    ]
    static func switchTo(_ code: String) {
        load(code)
        UserDefaults.standard.set(code, forKey: "appLanguage")
    }
    private static func load(_ code: String) {
        guard let url = Bundle.main.url(
            forResource: code, withExtension: "json",
            subdirectory: "Localization"
        ) else {
            // Fallback to bundled English
            if code != "en" { load("en") }
            return
        }
        guard let data = try? Data(contentsOf: url),
              let dict = try? JSONSerialization.jsonObject(with: data) as? [String: String]
        else { return }
        current = dict
    }
    private static func fallbackCode() -> String {
        let lang = Locale.preferredLanguages.first ?? "en"
        if lang.hasPrefix("zh-Hant") || lang.hasPrefix("zh-HK") || lang.hasPrefix("zh-TW") { return "zh-Hant" }
        if lang.hasPrefix("zh") { return "zh-Hans" }
        if lang.hasPrefix("ja") { return "ja" }
        if lang.hasPrefix("ko") { return "ko" }
        if lang.hasPrefix("ru") { return "ru" }
        if lang.hasPrefix("fr") { return "fr" }
        if lang.hasPrefix("it") { return "it" }
        if lang.hasPrefix("es") { return "es" }
        return "en"
    }
}
/// Convenience function — use `tr("key")` everywhere.
func tr(_ key: String) -> String {
    L10n.current[key] ?? key
}
Apptag/Localization/en.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "Show Apptag",
  "menu.preferences": "Preferences…",
  "menu.quit": "Quit",
  "menu.language": "Language",
  "menu.version": "Version",
  "settings.general": "General",
  "settings.tags": "Tags",
  "settings.data": "Data",
  "settings.about": "About",
  "settings.appListStyle": "App list style:",
  "settings.flat": "Flat",
  "settings.container": "Container",
  "settings.flatDesc": "\"Flat\" shows apps directly. \"Container\" wraps each tag group in a rounded box.",
  "settings.hideAppNames": "Hide app names",
  "settings.tagPosition": "Tag position:",
  "settings.left": "Left",
  "settings.right": "Right",
  "settings.top": "Top",
  "settings.tagPosDesc": "Where the tag navigation bar appears. Left/Right puts tags in a sidebar.",
  "settings.tagFontSize": "Tag font size:",
  "settings.tagFontDesc": "Adjust the size of group-name labels in the overlay.",
  "settings.iconSize": "Icon size:",
  "settings.iconSizeDesc": "Icon display size. Grid columns adjust automatically.",
  "settings.reindex": "Re-index Now",
  "settings.reindexDesc": "Re-scan all app directories and refresh the tag database.",
  "settings.indexMgmt": "Index Management",
  "settings.backup": "Backup & Restore",
  "settings.export": "Export Tags…",
  "settings.import": "Import Tags…",
  "settings.backupDesc": "Export saves your tag assignments to a JSON file. Import replaces the current database with one from a file.",
  "settings.importFailed": "Import Failed",
  "edit.exit": "Exit editing",
  "edit.title": "Edit App Categories",
  "edit.confirm": "Confirm",
  "edit.selectTags": "Select tags:",
  "edit.dragHint": "Drag to reorder",
  "edit.success": "分类成功",
  "edit.noTags": "No tags yet. Click \"New Tag\" to create one.",
  "tag.newTag": "New Tag",
  "tag.name": "Tag name",
  "tag.save": "Save",
  "tag.cancel": "Cancel",
  "tag.add": "Add",
  "tag.newName": "New tag name"
}
Apptag/Localization/es.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "Mostrar Apptag",
  "menu.preferences": "Preferencias…",
  "menu.quit": "Salir",
  "menu.language": "Idioma",
  "menu.version": "Versión",
  "settings.general": "General",
  "settings.tags": "Etiquetas",
  "settings.data": "Datos",
  "settings.about": "Acerca de",
  "settings.appListStyle": "Estilo de lista:",
  "settings.flat": "Plano",
  "settings.container": "Contenedor",
  "settings.flatDesc": "«Plano» muestra apps directamente. «Contenedor» agrupa etiquetas en cajas.",
  "settings.hideAppNames": "Ocultar nombres",
  "settings.tagPosition": "Posición de etiquetas:",
  "settings.left": "Izquierda",
  "settings.right": "Derecha",
  "settings.top": "Arriba",
  "settings.tagPosDesc": "Dónde aparece la barra de navegación de etiquetas.",
  "settings.tagFontSize": "Tamaño de fuente:",
  "settings.tagFontDesc": "Ajusta el tamaño de las etiquetas de grupo.",
  "settings.iconSize": "Tamaño de iconos:",
  "settings.iconSizeDesc": "Tamaño de visualización de iconos.",
  "settings.reindex": "Reindexar",
  "settings.reindexDesc": "Volver a escanear todos los directorios de apps.",
  "settings.indexMgmt": "Gestión de índice",
  "settings.backup": "Copia de seguridad",
  "settings.export": "Exportar etiquetas…",
  "settings.import": "Importar etiquetas…",
  "settings.backupDesc": "La exportación guarda las asignaciones de etiquetas en un archivo JSON.",
  "settings.importFailed": "Error de importación",
  "edit.exit": "Salir de edición",
  "edit.title": "Editar categorías",
  "edit.confirm": "Confirmar",
  "edit.selectTags": "Seleccionar:",
  "edit.dragHint": "Arrastrar para reordenar",
  "edit.success": "Éxito",
  "edit.noTags": "Sin etiquetas.",
  "tag.newTag": "Nueva etiqueta",
  "tag.name": "Nombre",
  "tag.save": "Guardar",
  "tag.cancel": "Cancelar",
  "tag.add": "Añadir",
  "tag.newName": "Nuevo nombre"
}
Apptag/Localization/fr.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "Afficher Apptag",
  "menu.preferences": "Préférences…",
  "menu.quit": "Quitter",
  "menu.language": "Langue",
  "menu.version": "Version",
  "settings.general": "Général",
  "settings.tags": "Étiquettes",
  "settings.data": "Données",
  "settings.about": "À propos",
  "settings.appListStyle": "Style de liste:",
  "settings.flat": "Plat",
  "settings.container": "Conteneur",
  "settings.flatDesc": "«Plat» affiche les apps directement. «Conteneur» groupe les étiquettes dans des boîtes.",
  "settings.hideAppNames": "Masquer les noms",
  "settings.tagPosition": "Position des étiquettes:",
  "settings.left": "Gauche",
  "settings.right": "Droite",
  "settings.top": "Haut",
  "settings.tagPosDesc": "Où la barre de navigation des étiquettes apparaît.",
  "settings.tagFontSize": "Taille de police:",
  "settings.tagFontDesc": "Ajuste la taille des noms de groupe dans l'overlay.",
  "settings.iconSize": "Taille des icônes:",
  "settings.iconSizeDesc": "Taille d'affichage des icônes.",
  "settings.reindex": "Réindexer",
  "settings.reindexDesc": "Réanalyser tous les répertoires d'applications.",
  "settings.indexMgmt": "Gestion de l'index",
  "settings.backup": "Sauvegarde et restauration",
  "settings.export": "Exporter les étiquettes…",
  "settings.import": "Importer les étiquettes…",
  "settings.backupDesc": "L'exportation enregistre les attributions d'étiquettes dans un fichier JSON.",
  "settings.importFailed": "Échec de l'importation",
  "edit.exit": "Quitter l'édition",
  "edit.title": "Modifier les catégories",
  "edit.confirm": "Confirmer",
  "edit.selectTags": "Sélectionner:",
  "edit.dragHint": "Glisser pour réorganiser",
  "edit.success": "Succès",
  "edit.noTags": "Aucune étiquette.",
  "tag.newTag": "Nouvelle étiquette",
  "tag.name": "Nom",
  "tag.save": "Enregistrer",
  "tag.cancel": "Annuler",
  "tag.add": "Ajouter",
  "tag.newName": "Nouveau nom"
}
Apptag/Localization/it.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "Mostra Apptag",
  "menu.preferences": "Preferenze…",
  "menu.quit": "Esci",
  "menu.language": "Lingua",
  "menu.version": "Versione",
  "settings.general": "Generale",
  "settings.tags": "Tag",
  "settings.data": "Dati",
  "settings.about": "Informazioni",
  "settings.appListStyle": "Stile elenco:",
  "settings.flat": "Piatto",
  "settings.container": "Contenitore",
  "settings.flatDesc": "«Piatto» mostra le app direttamente. «Contenitore» raggruppa i tag in riquadri.",
  "settings.hideAppNames": "Nascondi nomi",
  "settings.tagPosition": "Posizione tag:",
  "settings.left": "Sinistra",
  "settings.right": "Destra",
  "settings.top": "Alto",
  "settings.tagPosDesc": "Dove appare la barra di navigazione dei tag.",
  "settings.tagFontSize": "Dimensione font:",
  "settings.tagFontDesc": "Regola la dimensione delle etichette dei gruppi.",
  "settings.iconSize": "Dimensione icone:",
  "settings.iconSizeDesc": "Dimensione di visualizzazione delle icone.",
  "settings.reindex": "Reindicizza",
  "settings.reindexDesc": "Ripeti la scansione di tutte le directory delle app.",
  "settings.indexMgmt": "Gestione indice",
  "settings.backup": "Backup e ripristino",
  "settings.export": "Esporta tag…",
  "settings.import": "Importa tag…",
  "settings.backupDesc": "L'esportazione salva le assegnazioni dei tag in un file JSON.",
  "settings.importFailed": "Importazione fallita",
  "edit.exit": "Esci dalla modifica",
  "edit.title": "Modifica categorie",
  "edit.confirm": "Conferma",
  "edit.selectTags": "Seleziona tag:",
  "edit.dragHint": "Trascina per riordinare",
  "edit.success": "Successo",
  "edit.noTags": "Nessun tag.",
  "tag.newTag": "Nuovo tag",
  "tag.name": "Nome tag",
  "tag.save": "Salva",
  "tag.cancel": "Annulla",
  "tag.add": "Aggiungi",
  "tag.newName": "Nuovo nome"
}
Apptag/Localization/ja.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "Apptagを表示",
  "menu.preferences": "設定…",
  "menu.quit": "終了",
  "menu.language": "言語",
  "menu.version": "バージョン",
  "settings.general": "一般",
  "settings.tags": "タグ",
  "settings.data": "データ",
  "settings.about": "について",
  "settings.appListStyle": "Appリストスタイル:",
  "settings.flat": "フラット",
  "settings.container": "コンテナ",
  "settings.flatDesc": "「フラット」はAppを直接表示。「コンテナ」は各タググループを角丸ボックスで囲みます。",
  "settings.hideAppNames": "App名を隠す",
  "settings.tagPosition": "タグナビ位置:",
  "settings.left": "左",
  "settings.right": "右",
  "settings.top": "上",
  "settings.tagPosDesc": "タグナビゲーションバーの表示位置。左/右はサイドバーに表示します。",
  "settings.tagFontSize": "タグフォントサイズ:",
  "settings.tagFontDesc": "オーバーレイ内のグループ名ラベルのサイズを調整します。",
  "settings.iconSize": "アイコンサイズ:",
  "settings.iconSizeDesc": "アイコンの表示サイズ。グリッド列は自動調整されます。",
  "settings.reindex": "再インデックス",
  "settings.reindexDesc": "すべてのAppディレクトリを再スキャンし、タグデータベースを更新します。",
  "settings.indexMgmt": "インデックス管理",
  "settings.backup": "バックアップと復元",
  "settings.export": "タグをエクスポート…",
  "settings.import": "タグをインポート…",
  "settings.backupDesc": "エクスポートはタグ割り当てをJSONファイルに保存します。インポートはファイルのデータで現在のデータベースを置き換えます。",
  "settings.importFailed": "インポート失敗",
  "edit.exit": "編集を終了",
  "edit.title": "App分類を編集",
  "edit.confirm": "確認",
  "edit.selectTags": "タグを選択:",
  "edit.dragHint": "ドラッグで並び替え",
  "edit.success": "分類成功",
  "edit.noTags": "タグがありません。「新規タグ」をクリックして作成してください。",
  "tag.newTag": "新規タグ",
  "tag.name": "タグ名",
  "tag.save": "保存",
  "tag.cancel": "キャンセル",
  "tag.add": "追加",
  "tag.newName": "新規タグ名"
}
Apptag/Localization/ko.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "Apptag 보기",
  "menu.preferences": "설정…",
  "menu.quit": "종료",
  "menu.language": "언어",
  "menu.version": "버전",
  "settings.general": "일반",
  "settings.tags": "태그",
  "settings.data": "데이터",
  "settings.about": "정보",
  "settings.appListStyle": "앱 목록 스타일:",
  "settings.flat": "플랫",
  "settings.container": "컨테이너",
  "settings.flatDesc": "「플랫」은 앱을 직접 표시합니다.「컨테이너」는 각 태그 그룹을 둥근 상자로 묶습니다.",
  "settings.hideAppNames": "앱 이름 숨기기",
  "settings.tagPosition": "태그 탐색 위치:",
  "settings.left": "왼쪽",
  "settings.right": "오른쪽",
  "settings.top": "위쪽",
  "settings.tagPosDesc": "태그 탐색 막대의 표시 위치입니다.",
  "settings.tagFontSize": "태그 글꼴 크기:",
  "settings.tagFontDesc": "오버레이에서 그룹 이름 레이블의 크기를 조정합니다.",
  "settings.iconSize": "아이콘 크기:",
  "settings.iconSizeDesc": "아이콘 표시 크기입니다.",
  "settings.reindex": "재인덱싱",
  "settings.reindexDesc": "모든 앱 디렉토리를 다시 스캔하고 태그 데이터베이스를 새로 고칩니다.",
  "settings.indexMgmt": "인덱스 관리",
  "settings.backup": "백업 및 복원",
  "settings.export": "태그 내보내기…",
  "settings.import": "태그 가져오기…",
  "settings.backupDesc": "내보내기는 태그 할당을 JSON 파일로 저장합니다.",
  "settings.importFailed": "가져오기 실패",
  "edit.exit": "편집 종료",
  "edit.title": "앱 분류 편집",
  "edit.confirm": "확인",
  "edit.selectTags": "태그 선택:",
  "edit.dragHint": "드래그하여 재정렬",
  "edit.success": "분류 성공",
  "edit.noTags": "태그가 없습니다.",
  "tag.newTag": "새 태그",
  "tag.name": "태그 이름",
  "tag.save": "저장",
  "tag.cancel": "취소",
  "tag.add": "추가",
  "tag.newName": "새 태그 이름"
}
Apptag/Localization/ru.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "Показать Apptag",
  "menu.preferences": "Настройки…",
  "menu.quit": "Выход",
  "menu.language": "Язык",
  "menu.version": "Версия",
  "settings.general": "Общие",
  "settings.tags": "Теги",
  "settings.data": "Данные",
  "settings.about": "О программе",
  "settings.appListStyle": "Стиль списка:",
  "settings.flat": "Плитка",
  "settings.container": "Контейнер",
  "settings.flatDesc": "«Плитка» показывает приложения напрямую. «Контейнер» помещает группы в рамки.",
  "settings.hideAppNames": "Скрыть названия",
  "settings.tagPosition": "Позиция тегов:",
  "settings.left": "Слева",
  "settings.right": "Справа",
  "settings.top": "Сверху",
  "settings.tagPosDesc": "Где отображается панель навигации по тегам.",
  "settings.tagFontSize": "Размер шрифта:",
  "settings.tagFontDesc": "Размер названий групп в оверлее.",
  "settings.iconSize": "Размер иконок:",
  "settings.iconSizeDesc": "Размер отображения иконок.",
  "settings.reindex": "Переиндексировать",
  "settings.reindexDesc": "Пересканировать все директории приложений.",
  "settings.indexMgmt": "Управление индексом",
  "settings.backup": "Резервное копирование",
  "settings.export": "Экспорт тегов…",
  "settings.import": "Импорт тегов…",
  "settings.backupDesc": "Экспорт сохраняет назначения тегов в JSON-файл.",
  "settings.importFailed": "Ошибка импорта",
  "edit.exit": "Выйти из редактирования",
  "edit.title": "Редактировать категории",
  "edit.confirm": "Подтвердить",
  "edit.selectTags": "Выбрать теги:",
  "edit.dragHint": "Перетащите для сортировки",
  "edit.success": "Готово",
  "edit.noTags": "Нет тегов.",
  "tag.newTag": "Новый тег",
  "tag.name": "Имя тега",
  "tag.save": "Сохранить",
  "tag.cancel": "Отмена",
  "tag.add": "Добавить",
  "tag.newName": "Новое имя тега"
}
Apptag/Localization/zh-Hans.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "显示 Apptag",
  "menu.preferences": "设置…",
  "menu.quit": "退出",
  "menu.language": "语言",
  "menu.version": "版本",
  "settings.general": "通用",
  "settings.tags": "标签",
  "settings.data": "数据",
  "settings.about": "关于",
  "settings.appListStyle": "App 列表样式:",
  "settings.flat": "平铺",
  "settings.container": "容器",
  "settings.flatDesc": "「平铺」直接显示 App。「容器」将每个标签组放入圆角框。",
  "settings.hideAppNames": "隐藏 App 名称",
  "settings.tagPosition": "标签导航位置:",
  "settings.left": "左侧",
  "settings.right": "右侧",
  "settings.top": "顶部",
  "settings.tagPosDesc": "标签导航栏的显示位置。左/右将在侧边栏中显示标签。",
  "settings.tagFontSize": "标签字体大小:",
  "settings.tagFontDesc": "调整浮层中分组名称标签的大小。",
  "settings.iconSize": "图标大小:",
  "settings.iconSizeDesc": "图标显示尺寸。网格列会自动调整。",
  "settings.reindex": "重新索引",
  "settings.reindexDesc": "重新扫描所有 App 目录并刷新标签数据库。",
  "settings.indexMgmt": "索引管理",
  "settings.backup": "备份与恢复",
  "settings.export": "导出标签…",
  "settings.import": "导入标签…",
  "settings.backupDesc": "导出将标签分配保存为 JSON 文件。导入将用文件中的数据替换当前数据库。",
  "settings.importFailed": "导入失败",
  "edit.exit": "退出编辑",
  "edit.title": "编辑 App 分类",
  "edit.confirm": "确认",
  "edit.selectTags": "选择标签:",
  "edit.dragHint": "拖动排序",
  "edit.success": "分类成功",
  "edit.noTags": "暂无标签。点击「新建标签」创建一个。",
  "tag.newTag": "新建标签",
  "tag.name": "标签名称",
  "tag.save": "保存",
  "tag.cancel": "取消",
  "tag.add": "添加",
  "tag.newName": "新标签名称"
}
Apptag/Localization/zh-Hant.json
New file
@@ -0,0 +1,46 @@
{
  "menu.show": "顯示 Apptag",
  "menu.preferences": "設定…",
  "menu.quit": "退出",
  "menu.language": "語言",
  "menu.version": "版本",
  "settings.general": "一般",
  "settings.tags": "標籤",
  "settings.data": "資料",
  "settings.about": "關於",
  "settings.appListStyle": "App 列表樣式:",
  "settings.flat": "平鋪",
  "settings.container": "容器",
  "settings.flatDesc": "「平鋪」直接顯示 App。「容器」將每個標籤組放入圓角框。",
  "settings.hideAppNames": "隱藏 App 名稱",
  "settings.tagPosition": "標籤導航位置:",
  "settings.left": "左側",
  "settings.right": "右側",
  "settings.top": "頂部",
  "settings.tagPosDesc": "標籤導航欄的顯示位置。左/右將在側邊欄中顯示標籤。",
  "settings.tagFontSize": "標籤字型大小:",
  "settings.tagFontDesc": "調整浮層中分組名稱標籤的大小。",
  "settings.iconSize": "圖示大小:",
  "settings.iconSizeDesc": "圖示顯示尺寸。網格列會自動調整。",
  "settings.reindex": "重新索引",
  "settings.reindexDesc": "重新掃描所有 App 目錄並重新整理標籤資料庫。",
  "settings.indexMgmt": "索引管理",
  "settings.backup": "備份與還原",
  "settings.export": "匯出標籤…",
  "settings.import": "匯入標籤…",
  "settings.backupDesc": "匯出將標籤指派儲存為 JSON 檔案。匯入將用檔案中的資料取代目前資料庫。",
  "settings.importFailed": "匯入失敗",
  "edit.exit": "退出編輯",
  "edit.title": "編輯 App 分類",
  "edit.confirm": "確認",
  "edit.selectTags": "選擇標籤:",
  "edit.dragHint": "拖曳排序",
  "edit.success": "分類成功",
  "edit.noTags": "尚無標籤。點擊「新增標籤」建立一個。",
  "tag.newTag": "新增標籤",
  "tag.name": "標籤名稱",
  "tag.save": "儲存",
  "tag.cancel": "取消",
  "tag.add": "新增",
  "tag.newName": "新標籤名稱"
}
CHANGELOG.md
@@ -1,5 +1,12 @@
# Apptag Changelog
## [3.1.0] — 2026-05-06
- 国际化:支持 9 种语言(英/简中/繁中/日/韩/俄/法/意/西)
- 菜单栏新增 Language 子菜单切换语言
- 翻译文件独立存储在 `Apptag/Localization/*.json`
- `L10n.swift` 管理器 + `tr()` 辅助函数
## [3.0.11] — 2026-05-06
- 容器标题最小宽度优化:`.layoutPriority(1)` 让文字优先占空间,分隔线压缩,可显示约15字符
X-screencap/SCR-20260506-puyk.jpeg
X-screencap/录制于 2026-05-06 18.05.17.mp4
Binary files differ
build.sh
@@ -39,6 +39,9 @@
    iconutil -c icns "$SWIFT_DIR/AppIcon.iconset" -o "$RESOURCES_DIR/AppIcon.icns" 2>/dev/null
fi
echo "==> Copying Localization files..."
cp -r "$SWIFT_DIR/Localization" "$RESOURCES_DIR/Localization"
echo "==> Setting permissions..."
chmod +x "$MACOS_DIR/$APP_NAME"