libretranslatr

This commit is contained in:
y1lm0z
2026-07-14 17:37:17 +03:00
parent 1b8149a4cf
commit 8a562e5bf6
46 changed files with 2040 additions and 106 deletions
+33
View File
@@ -0,0 +1,33 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/history_item.dart';
class HistoryService {
static const _key = 'translation_history';
static const _maxItems = 200;
Future<List<HistoryItem>> getAll() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getStringList(_key) ?? [];
return raw
.map((s) => HistoryItem.fromJson(jsonDecode(s) as Map<String, dynamic>))
.toList()
.reversed
.toList(); // en yeni en üstte
}
Future<void> add(HistoryItem item) async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getStringList(_key) ?? [];
raw.add(jsonEncode(item.toJson()));
final trimmed = raw.length > _maxItems
? raw.sublist(raw.length - _maxItems)
: raw;
await prefs.setStringList(_key, trimmed);
}
Future<void> clear() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_key);
}
}
+31
View File
@@ -0,0 +1,31 @@
import 'package:shared_preferences/shared_preferences.dart';
/// Sunucu URL'si ve API key gibi ayarları kalıcı olarak saklar.
class SettingsService {
static const _keyServerUrl = 'server_url';
static const _keyApiKey = 'api_key';
Future<String?> getServerUrl() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_keyServerUrl);
}
Future<String?> getApiKey() async {
final prefs = await SharedPreferences.getInstance();
final key = prefs.getString(_keyApiKey);
return (key == null || key.isEmpty) ? null : key;
}
Future<void> save({required String serverUrl, required String apiKey}) async {
final prefs = await SharedPreferences.getInstance();
// Sonundaki "/" karakterini temizle, karışıklık olmasın.
final cleanUrl = serverUrl.trim().replaceAll(RegExp(r'/+$'), '');
await prefs.setString(_keyServerUrl, cleanUrl);
await prefs.setString(_keyApiKey, apiKey.trim());
}
Future<bool> hasSettings() async {
final url = await getServerUrl();
return url != null && url.isNotEmpty;
}
}
+125
View File
@@ -0,0 +1,125 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/language.dart';
import 'settings_service.dart';
class TranslateException implements Exception {
final String message;
TranslateException(this.message);
@override
String toString() => message;
}
class DetectResult {
final String language;
final int confidence;
DetectResult({required this.language, required this.confidence});
}
/// Kullanıcının kendi sunucusundaki LibreTranslate API'sine istek atar.
class TranslateService {
final SettingsService _settings;
TranslateService(this._settings);
Future<String> _baseUrl() async {
final url = await _settings.getServerUrl();
if (url == null || url.isEmpty) {
throw TranslateException('Sunucu adresi ayarlanmamış. Ayarlar\'dan ekleyin.');
}
return url;
}
Map<String, dynamic> _withApiKey(Map<String, dynamic> body, String? apiKey) {
if (apiKey != null && apiKey.isNotEmpty) {
body['api_key'] = apiKey;
}
return body;
}
String _errorFromBody(String body, int statusCode) {
try {
final decoded = jsonDecode(body);
if (decoded is Map && decoded['error'] != null) {
return decoded['error'].toString();
}
} catch (_) {
// JSON değilse aşağıda genel mesaj kullanılır.
}
return 'Sunucu hatası (HTTP $statusCode)';
}
/// Sunucuya bağlanıp desteklenen dilleri getirir.
/// Hem "bağlantı testi" hem de dil listesini doldurmak için kullanılır.
Future<List<Language>> fetchLanguages() async {
final base = await _baseUrl();
final apiKey = await _settings.getApiKey();
final uri = Uri.parse('$base/languages').replace(
queryParameters: apiKey != null ? {'api_key': apiKey} : null,
);
final res = await http.get(uri).timeout(const Duration(seconds: 10));
if (res.statusCode != 200) {
throw TranslateException(_errorFromBody(res.body, res.statusCode));
}
final List<dynamic> data = jsonDecode(utf8.decode(res.bodyBytes));
return data.map((e) => Language.fromJson(e as Map<String, dynamic>)).toList();
}
Future<String> translate({
required String text,
required String sourceLang,
required String targetLang,
}) async {
if (text.trim().isEmpty) return '';
final base = await _baseUrl();
final apiKey = await _settings.getApiKey();
final body = _withApiKey({
'q': text,
'source': sourceLang,
'target': targetLang,
'format': 'text',
}, apiKey);
final res = await http
.post(
Uri.parse('$base/translate'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
)
.timeout(const Duration(seconds: 15));
if (res.statusCode != 200) {
throw TranslateException(_errorFromBody(res.body, res.statusCode));
}
final decoded = jsonDecode(utf8.decode(res.bodyBytes));
return decoded['translatedText'] as String;
}
Future<DetectResult?> detect(String text) async {
if (text.trim().isEmpty) return null;
final base = await _baseUrl();
final apiKey = await _settings.getApiKey();
final body = _withApiKey({'q': text}, apiKey);
final res = await http
.post(
Uri.parse('$base/detect'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
)
.timeout(const Duration(seconds: 10));
if (res.statusCode != 200) {
throw TranslateException(_errorFromBody(res.body, res.statusCode));
}
final List<dynamic> data = jsonDecode(utf8.decode(res.bodyBytes));
if (data.isEmpty) return null;
final best = data.first as Map<String, dynamic>;
return DetectResult(
language: best['language'] as String,
confidence: ((best['confidence'] ?? 0) as num).round(),
);
}
}