diff --git a/NotchIA.xcodeproj/project.pbxproj b/NotchIA.xcodeproj/project.pbxproj index 1b47fb1..2aa6fee 100644 --- a/NotchIA.xcodeproj/project.pbxproj +++ b/NotchIA.xcodeproj/project.pbxproj @@ -1277,7 +1277,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20803; + CURRENT_PROJECT_VERSION = 20804; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1285,7 +1285,7 @@ INFOPLIST_KEY_CFBundleDisplayName = NotchIAXPCHelper; INFOPLIST_KEY_NSHumanReadableCopyright = ""; MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.3; + MARKETING_VERSION = 2.8.4; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia.NotchIAXPCHelper; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -1303,7 +1303,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20803; + CURRENT_PROJECT_VERSION = 20804; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1311,7 +1311,7 @@ INFOPLIST_KEY_CFBundleDisplayName = NotchIAXPCHelper; INFOPLIST_KEY_NSHumanReadableCopyright = ""; MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.3; + MARKETING_VERSION = 2.8.4; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia.NotchIAXPCHelper; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -1462,7 +1462,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20803; + CURRENT_PROJECT_VERSION = 20804; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"NotchIA/Preview Content\""; DEVELOPMENT_TEAM = ""; @@ -1490,7 +1490,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.3; + MARKETING_VERSION = 2.8.4; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1518,7 +1518,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 20803; + CURRENT_PROJECT_VERSION = 20804; DEAD_CODE_STRIPPING = YES; DEPLOYMENT_POSTPROCESSING = YES; DEVELOPMENT_ASSET_PATHS = "\"NotchIA/Preview Content\""; @@ -1546,7 +1546,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.3; + MARKETING_VERSION = 2.8.4; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/NotchIA/Localizable.xcstrings b/NotchIA/Localizable.xcstrings index 881f7dd..890ac02 100644 --- a/NotchIA/Localizable.xcstrings +++ b/NotchIA/Localizable.xcstrings @@ -42807,6 +42807,202 @@ } } } + }, + "Apple Intelligence indisponible — %@.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence unavailable — %@." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence indisponible — %@." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence no disponible — %@." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence nicht verfügbar — %@." + } + } + } + }, + "Mac non compatible Apple Intelligence": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mac not compatible with Apple Intelligence" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Mac non compatible Apple Intelligence" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mac no compatible con Apple Intelligence" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Mac nicht mit Apple Intelligence kompatibel" + } + } + } + }, + "Apple Intelligence désactivée": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence is disabled" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence désactivée" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence está desactivada" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence ist deaktiviert" + } + } + } + }, + "modèle Apple Intelligence pas encore prêt": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence model not ready yet" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "modèle Apple Intelligence pas encore prêt" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "el modelo de Apple Intelligence aún no está listo" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Apple-Intelligence-Modell noch nicht bereit" + } + } + } + }, + "Apple Intelligence indisponible": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence unavailable" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence indisponible" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence no disponible" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Apple Intelligence nicht verfügbar" + } + } + } + }, + "macOS 26 requis pour l’IA locale": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "macOS 26 required for on-device AI" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "macOS 26 requis pour l’IA locale" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "macOS 26 necesario para la IA local" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "macOS 26 erforderlich für lokale KI" + } + } + } + }, + "FoundationModels non disponible": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "FoundationModels framework unavailable" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "FoundationModels non disponible" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "FoundationModels no disponible" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "FoundationModels nicht verfügbar" + } + } + } } }, "version": "1.0" diff --git a/NotchIA/components/Shelf/Services/DocumentSummaryService.swift b/NotchIA/components/Shelf/Services/DocumentSummaryService.swift index 399c28d..1fc8779 100644 --- a/NotchIA/components/Shelf/Services/DocumentSummaryService.swift +++ b/NotchIA/components/Shelf/Services/DocumentSummaryService.swift @@ -8,11 +8,12 @@ // or when Apple Intelligence isn't enabled by the user. // +import AppKit +import Defaults import Foundation +import OSLog import PDFKit import Vision -import AppKit -import OSLog #if canImport(FoundationModels) import FoundationModels @@ -34,7 +35,7 @@ final class DocumentSummaryService { var errorDescription: String? { switch self { case .unavailable(let reason): - return "Apple Intelligence indisponible — \(reason)." + return String(localized: "Apple Intelligence indisponible — \(reason).") case .unsupportedFile(let ext): return String(localized: "Type de fichier « \(ext) » non pris en charge.") case .noTextFound: @@ -200,13 +201,15 @@ final class DocumentSummaryService { #if canImport(FoundationModels) if #available(macOS 26.0, *) { do { + let lang = Defaults[.appLanguage].promptInstruction let session = LanguageModelSession(instructions: """ Tu es un assistant qui résume des documents pour un utilisateur \ - pressé. Pour chaque document, produis un résumé clair en \ - français sous forme de 5 à 8 puces concises (chacune commence \ - par « • »). Garde les chiffres, dates, noms et points clés. \ - Ne dépasse pas 220 mots au total. Ne reformule pas la consigne, \ - ne mets pas de préambule — uniquement les puces. + pressé. Pour chaque document, produis un résumé clair \(lang) \ + sous forme de 5 à 8 puces concises (chacune commence par « • »). \ + Garde les chiffres, dates, noms et points clés. Ne dépasse pas \ + 220 mots au total. Ne reformule pas la consigne, ne mets pas \ + de préambule — uniquement les puces. + **LANGUE DE SORTIE OBLIGATOIRE : \(lang).** """) let response = try await session.respond(to: prompt) return response.content.trimmingCharacters(in: .whitespacesAndNewlines) @@ -341,14 +344,16 @@ final class DocumentSummaryService { #if canImport(FoundationModels) if #available(macOS 26.0, *) { do { + let lang = Defaults[.appLanguage].promptInstruction let session = LanguageModelSession(instructions: """ Tu reçois plusieurs documents thématiquement liés, \ - séparés par « === ». Produis UN résumé unifié en français \ + séparés par « === ». Produis UN résumé unifié \(lang) \ en 6 à 10 puces (chacune commence par « • »). \ Synthétise les informations importantes en mélangeant \ les sources sans les nommer une par une. Garde chiffres, \ dates et points clés. Ne dépasse pas 250 mots. Pas de \ préambule, uniquement les puces. + **LANGUE DE SORTIE OBLIGATOIRE : \(lang).** """) let response = try await session.respond(to: payload) return response.content.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/NotchIA/extensions/AppLanguage.swift b/NotchIA/extensions/AppLanguage.swift index c7eedc1..b26cfb9 100644 --- a/NotchIA/extensions/AppLanguage.swift +++ b/NotchIA/extensions/AppLanguage.swift @@ -46,6 +46,27 @@ enum AppLanguage: String, CaseIterable, Identifiable, Defaults.Serializable { case .de: return ["de"] } } + + /// Phrase à injecter dans un prompt LLM (FoundationModels) pour forcer + /// la langue de sortie. `.system` retombe sur la langue préférée macOS. + /// Utilisé par DigestManager + DocumentSummaryService. + var promptInstruction: String { + switch self { + case .system: + let primary = (Locale.preferredLanguages.first ?? "en") + .split(separator: "-").first.map(String.init)?.lowercased() ?? "en" + switch primary { + case "fr": return "en français" + case "es": return "en español" + case "de": return "auf Deutsch" + default: return "in English" + } + case .en: return "in English" + case .fr: return "en français" + case .es: return "en español" + case .de: return "auf Deutsch" + } + } } enum AppLanguageManager { diff --git a/NotchIA/managers/DigestManager.swift b/NotchIA/managers/DigestManager.swift index f604b73..ad70f7c 100644 --- a/NotchIA/managers/DigestManager.swift +++ b/NotchIA/managers/DigestManager.swift @@ -486,6 +486,7 @@ final class DigestManager: ObservableObject { #if canImport(FoundationModels) if #available(macOS 26.0, *) { + let lang = Defaults[.appLanguage].promptInstruction let session = LanguageModelSession(instructions: """ Tu produis une section du Digest quotidien de NotchIA. Résume \ uniquement les éléments fournis, sans inventer d'information. \ @@ -493,9 +494,10 @@ final class DigestManager: ObservableObject { garde ce cadrage temporel dans l'analyse. \ Ne répète pas la liste des sources. Regroupe ces nouveautés en \ un rapport par thème, facile à lire dans une petite notch. \ - Réponds en français, en Markdown aéré, en 120 à 170 mots. \ + Réponds \(lang), en Markdown aéré, en 120 à 170 mots. \ N'utilise jamais de gras Markdown. N'écris aucun astérisque ; \ utilise uniquement des tirets pour les puces. + **LANGUE DE SORTIE OBLIGATOIRE : \(lang).** Format obligatoire : ## Titre court du thème @@ -631,19 +633,19 @@ final class DigestManager: ObservableObject { case .unavailable(let reason): switch reason { case .deviceNotEligible: - return "Mac non compatible Apple Intelligence" + return String(localized: "Mac non compatible Apple Intelligence") case .appleIntelligenceNotEnabled: - return "Apple Intelligence désactivée" + return String(localized: "Apple Intelligence désactivée") case .modelNotReady: - return "modèle Apple Intelligence pas encore prêt" + return String(localized: "modèle Apple Intelligence pas encore prêt") @unknown default: - return "Apple Intelligence indisponible" + return String(localized: "Apple Intelligence indisponible") } } } - return "macOS 26 requis pour l’IA locale" + return String(localized: "macOS 26 requis pour l’IA locale") #else - return "FoundationModels non disponible" + return String(localized: "FoundationModels non disponible") #endif } } diff --git a/updater/appcast.xml b/updater/appcast.xml index 685f4e3..0ee617b 100644 --- a/updater/appcast.xml +++ b/updater/appcast.xml @@ -32,6 +32,16 @@ --> + + 2.8.4 + Mon, 25 May 2026 23:13:24 +0000 + https://github.com/coaxel2/NotchIA/releases + 20804 + 2.8.4 + 15.0 + + + 2.8.3 Mon, 25 May 2026 20:34:13 +0000 @@ -71,16 +81,6 @@ Le code de détection ne fonctionne pas fiable sur macOS 26+ à cause d'un bug A - [ ] CI build + smoke test]]> - - 2.8.1 - Sun, 17 May 2026 17:45:44 +0000 - https://github.com/coaxel2/NotchIA/releases - 20801 - 2.8.1 - 15.0 - - - 2.7.3 Mon, 24 Nov 2025 08:07:37 +0000