import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:manager_app/l10n/app_localizations.dart'; /// Erreur remontée par l'API de traduction IA, en conservant le message du backend /// (ex. "Quota IA mensuel dépassé") pour pouvoir l'afficher tel quel à l'utilisateur. class AiTranslateException implements Exception { final String message; final int statusCode; AiTranslateException(this.message, this.statusCode); /// true si l'échec est dû à un quota atteint (mensuel ou période d'essai) bool get isQuotaExceeded => statusCode == 429; /// Le backend renvoie ses propres messages déjà rédigés ; quand il n'en donne /// aucun, c'est au client de formuler l'erreur dans la langue du manager. String localized(AppLocalizations l) => message.isNotEmpty ? message : (statusCode == 403 ? l.aiAssistantNotEnabled : l.aiTranslateError); @override String toString() => message; } class AiTranslateService { static Future> translate({ required String host, required String accessToken, required String instanceId, required String text, required String sourceLang, required List targetLangs, }) async { final uri = Uri.parse('$host/api/Ai/translate?instanceId=$instanceId'); final response = await http.post( uri, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $accessToken', }, body: jsonEncode({ 'text': text, 'sourceLang': sourceLang, 'targetLangs': targetLangs, }), ); if (response.statusCode != 200) { throw AiTranslateException(_extractMessage(response), response.statusCode); } final data = jsonDecode(utf8.decode(response.bodyBytes)); final translations = data['translations'] as Map; return translations.map((k, v) => MapEntry(k, v.toString())); } /// Le backend renvoie ses messages d'erreur en texte via StatusCode(429, "..."), /// ce qui donne une chaîne JSON. On décode en UTF-8 explicitement : le package http /// retombe sur latin1 quand le charset n'est pas précisé, ce qui casse les accents. static String _extractMessage(http.Response response) { final body = utf8.decode(response.bodyBytes, allowMalformed: true).trim(); if (body.isNotEmpty) { try { final decoded = jsonDecode(body); if (decoded is String && decoded.trim().isNotEmpty) return decoded.trim(); if (decoded is Map && decoded['title'] is String) return decoded['title'] as String; } catch (_) { return body; } } return ''; } }