11import 'dart:io' ;
22
3+ import 'package:flutter/material.dart' ;
4+ import 'package:localizer_app_main/i18n/strings.g.dart' ;
5+
6+ /// Error types for categorization and localization.
7+ enum FriendlyErrorType {
8+ fileNotFound,
9+ accessDenied,
10+ isDirectory,
11+ fileAccess,
12+ unsupportedFormat,
13+ networkError,
14+ notGitRepo,
15+ mergeConflict,
16+ gitAuthFailed,
17+ gitOperationFailed,
18+ invalidJson,
19+ rateLimitReached,
20+ invalidApiKey,
21+ translationServiceError,
22+ outOfMemory,
23+ genericError,
24+ }
25+
326/// A helper class to map technical exceptions to user-friendly messages.
427class FriendlyErrorService {
528 FriendlyErrorService ._();
@@ -13,28 +36,23 @@ class FriendlyErrorService {
1336 errorString.contains ('filesystemexception' )) {
1437 if (errorString.contains ('no such file' ) ||
1538 errorString.contains ('cannot find' )) {
16- return FriendlyError (
17- message: 'The file or folder could not be found.' ,
18- suggestion: 'Check if the file was moved or deleted.' ,
39+ return const FriendlyError (
40+ type: FriendlyErrorType .fileNotFound,
1941 );
2042 }
2143 if (errorString.contains ('access denied' ) ||
2244 errorString.contains ('permission denied' )) {
23- return FriendlyError (
24- message: 'Access to the file was denied.' ,
25- suggestion:
26- 'Try running the app as administrator or check file permissions.' ,
45+ return const FriendlyError (
46+ type: FriendlyErrorType .accessDenied,
2747 );
2848 }
2949 if (errorString.contains ('is a directory' )) {
30- return FriendlyError (
31- message: 'A folder was selected instead of a file.' ,
32- suggestion: 'Please select a valid file.' ,
50+ return const FriendlyError (
51+ type: FriendlyErrorType .isDirectory,
3352 );
3453 }
35- return FriendlyError (
36- message: 'There was a problem accessing the file.' ,
37- suggestion: 'Try closing other programs that might be using it.' ,
54+ return const FriendlyError (
55+ type: FriendlyErrorType .fileAccess,
3856 );
3957 }
4058
@@ -43,107 +61,216 @@ class FriendlyErrorService {
4361 errorString.contains ('unsupportederror' ) ||
4462 errorString.contains ('not supported' ) ||
4563 errorString.contains ('unsupported file type' )) {
46- return FriendlyError (
47- message: 'This file format or operation is not supported.' ,
48- suggestion: 'Check the file extension or try a different format.' ,
64+ return const FriendlyError (
65+ type: FriendlyErrorType .unsupportedFormat,
4966 );
5067 }
5168
5269 // Network errors
5370 if (errorString.contains ('socket' ) ||
5471 errorString.contains ('network' ) ||
5572 errorString.contains ('connection' )) {
56- return FriendlyError (
57- message: 'Could not connect to the server.' ,
58- suggestion: 'Check your internet connection and try again.' ,
73+ return const FriendlyError (
74+ type: FriendlyErrorType .networkError,
5975 );
6076 }
6177
6278 // Git errors
6379 if (errorString.contains ('git' )) {
6480 if (errorString.contains ('not a git repository' )) {
65- return FriendlyError (
66- message: 'This folder is not a Git project.' ,
67- suggestion: 'Navigate to a folder that contains a .git directory.' ,
81+ return const FriendlyError (
82+ type: FriendlyErrorType .notGitRepo,
6883 );
6984 }
7085 if (errorString.contains ('merge conflict' ) ||
7186 errorString.contains ('conflict' )) {
72- return FriendlyError (
73- message: 'There are merge conflicts in the repository.' ,
74- suggestion: 'Resolve the conflicts before continuing.' ,
87+ return const FriendlyError (
88+ type: FriendlyErrorType .mergeConflict,
7589 );
7690 }
7791 if (errorString.contains ('authentication' ) ||
7892 errorString.contains ('credentials' )) {
79- return FriendlyError (
80- message: 'Git authentication failed.' ,
81- suggestion: 'Check your credentials in Settings > Version Control.' ,
93+ return const FriendlyError (
94+ type: FriendlyErrorType .gitAuthFailed,
8295 );
8396 }
84- return FriendlyError (
85- message: 'A Git operation failed.' ,
86- suggestion: 'Check the Git view for more details.' ,
97+ return const FriendlyError (
98+ type: FriendlyErrorType .gitOperationFailed,
8799 );
88100 }
89101
90102 // JSON parsing errors
91103 if (errorString.contains ('json' ) ||
92104 errorString.contains ('formatexception' )) {
93- return FriendlyError (
94- message: 'The file format is invalid or corrupted.' ,
95- suggestion: 'Ensure the file contains valid JSON content.' ,
105+ return const FriendlyError (
106+ type: FriendlyErrorType .invalidJson,
96107 );
97108 }
98109
99110 // Translation/API errors
100111 if (errorString.contains ('api' ) || errorString.contains ('translation' )) {
101112 if (errorString.contains ('rate limit' ) || errorString.contains ('quota' )) {
102- return FriendlyError (
103- message: 'Translation limit reached.' ,
104- suggestion: 'Wait a few minutes or check your API plan limits.' ,
113+ return const FriendlyError (
114+ type: FriendlyErrorType .rateLimitReached,
105115 );
106116 }
107117 if (errorString.contains ('invalid key' ) ||
108118 errorString.contains ('unauthorized' )) {
109- return FriendlyError (
110- message: 'Invalid API key.' ,
111- suggestion: 'Go to Settings > AI Translation to update your API key.' ,
119+ return const FriendlyError (
120+ type: FriendlyErrorType .invalidApiKey,
112121 );
113122 }
114- return FriendlyError (
115- message: 'Translation service error.' ,
116- suggestion: 'Check your API key and internet connection.' ,
123+ return const FriendlyError (
124+ type: FriendlyErrorType .translationServiceError,
117125 );
118126 }
119127
120128 // Out of memory
121129 if (errorString.contains ('out of memory' ) ||
122130 errorString.contains ('memory' )) {
123- return FriendlyError (
124- message: 'Not enough memory to complete this operation.' ,
125- suggestion: 'Try closing other applications or using smaller files.' ,
131+ return const FriendlyError (
132+ type: FriendlyErrorType .outOfMemory,
126133 );
127134 }
128135
129136 // Generic fallback
130- return FriendlyError (
131- message: 'Something went wrong.' ,
132- suggestion: 'Please try again. If the problem persists, restart the app.' ,
137+ return const FriendlyError (
138+ type: FriendlyErrorType .genericError,
133139 );
134140 }
135141}
136142
137- /// Data class holding a friendly error message and optional recovery suggestion .
143+ /// Data class holding a friendly error type for localization .
138144class FriendlyError {
139- final String message;
140- final String ? suggestion;
145+ final FriendlyErrorType type;
141146
142147 const FriendlyError ({
143- required this .message,
144- this .suggestion,
148+ required this .type,
145149 });
146150
151+ /// Get localized message using BuildContext.
152+ String getLocalizedMessage (BuildContext context) {
153+ final t = context.t.friendlyErrors;
154+ switch (type) {
155+ case FriendlyErrorType .fileNotFound:
156+ return t.fileNotFound.message;
157+ case FriendlyErrorType .accessDenied:
158+ return t.accessDenied.message;
159+ case FriendlyErrorType .isDirectory:
160+ return t.isDirectory.message;
161+ case FriendlyErrorType .fileAccess:
162+ return t.fileAccess.message;
163+ case FriendlyErrorType .unsupportedFormat:
164+ return t.unsupportedFormat.message;
165+ case FriendlyErrorType .networkError:
166+ return t.networkError.message;
167+ case FriendlyErrorType .notGitRepo:
168+ return t.notGitRepo.message;
169+ case FriendlyErrorType .mergeConflict:
170+ return t.mergeConflict.message;
171+ case FriendlyErrorType .gitAuthFailed:
172+ return t.gitAuthFailed.message;
173+ case FriendlyErrorType .gitOperationFailed:
174+ return t.gitOperationFailed.message;
175+ case FriendlyErrorType .invalidJson:
176+ return t.invalidJson.message;
177+ case FriendlyErrorType .rateLimitReached:
178+ return t.rateLimitReached.message;
179+ case FriendlyErrorType .invalidApiKey:
180+ return t.invalidApiKey.message;
181+ case FriendlyErrorType .translationServiceError:
182+ return t.translationServiceError.message;
183+ case FriendlyErrorType .outOfMemory:
184+ return t.outOfMemory.message;
185+ case FriendlyErrorType .genericError:
186+ return t.genericError.message;
187+ }
188+ }
189+
190+ /// Get localized suggestion using BuildContext.
191+ String ? getLocalizedSuggestion (BuildContext context) {
192+ final t = context.t.friendlyErrors;
193+ switch (type) {
194+ case FriendlyErrorType .fileNotFound:
195+ return t.fileNotFound.suggestion;
196+ case FriendlyErrorType .accessDenied:
197+ return t.accessDenied.suggestion;
198+ case FriendlyErrorType .isDirectory:
199+ return t.isDirectory.suggestion;
200+ case FriendlyErrorType .fileAccess:
201+ return t.fileAccess.suggestion;
202+ case FriendlyErrorType .unsupportedFormat:
203+ return t.unsupportedFormat.suggestion;
204+ case FriendlyErrorType .networkError:
205+ return t.networkError.suggestion;
206+ case FriendlyErrorType .notGitRepo:
207+ return t.notGitRepo.suggestion;
208+ case FriendlyErrorType .mergeConflict:
209+ return t.mergeConflict.suggestion;
210+ case FriendlyErrorType .gitAuthFailed:
211+ return t.gitAuthFailed.suggestion;
212+ case FriendlyErrorType .gitOperationFailed:
213+ return t.gitOperationFailed.suggestion;
214+ case FriendlyErrorType .invalidJson:
215+ return t.invalidJson.suggestion;
216+ case FriendlyErrorType .rateLimitReached:
217+ return t.rateLimitReached.suggestion;
218+ case FriendlyErrorType .invalidApiKey:
219+ return t.invalidApiKey.suggestion;
220+ case FriendlyErrorType .translationServiceError:
221+ return t.translationServiceError.suggestion;
222+ case FriendlyErrorType .outOfMemory:
223+ return t.outOfMemory.suggestion;
224+ case FriendlyErrorType .genericError:
225+ return t.genericError.suggestion;
226+ }
227+ }
228+
229+ /// Get full localized error (message + suggestion) using BuildContext.
230+ String toLocalizedString (BuildContext context) {
231+ final message = getLocalizedMessage (context);
232+ final suggestion = getLocalizedSuggestion (context);
233+ return suggestion != null ? '$message $suggestion ' : message;
234+ }
235+
236+ /// Fallback English message for logging/debugging (no context needed).
237+ String get fallbackMessage {
238+ switch (type) {
239+ case FriendlyErrorType .fileNotFound:
240+ return 'The file or folder could not be found.' ;
241+ case FriendlyErrorType .accessDenied:
242+ return 'Access to the file was denied.' ;
243+ case FriendlyErrorType .isDirectory:
244+ return 'A folder was selected instead of a file.' ;
245+ case FriendlyErrorType .fileAccess:
246+ return 'There was a problem accessing the file.' ;
247+ case FriendlyErrorType .unsupportedFormat:
248+ return 'This file format or operation is not supported.' ;
249+ case FriendlyErrorType .networkError:
250+ return 'Could not connect to the server.' ;
251+ case FriendlyErrorType .notGitRepo:
252+ return 'This folder is not a Git project.' ;
253+ case FriendlyErrorType .mergeConflict:
254+ return 'There are merge conflicts in the repository.' ;
255+ case FriendlyErrorType .gitAuthFailed:
256+ return 'Git authentication failed.' ;
257+ case FriendlyErrorType .gitOperationFailed:
258+ return 'A Git operation failed.' ;
259+ case FriendlyErrorType .invalidJson:
260+ return 'The file format is invalid or corrupted.' ;
261+ case FriendlyErrorType .rateLimitReached:
262+ return 'Translation limit reached.' ;
263+ case FriendlyErrorType .invalidApiKey:
264+ return 'Invalid API key.' ;
265+ case FriendlyErrorType .translationServiceError:
266+ return 'Translation service error.' ;
267+ case FriendlyErrorType .outOfMemory:
268+ return 'Not enough memory to complete this operation.' ;
269+ case FriendlyErrorType .genericError:
270+ return 'Something went wrong.' ;
271+ }
272+ }
273+
147274 @override
148- String toString () => suggestion != null ? '$ message $ suggestion ' : message ;
275+ String toString () => fallbackMessage ;
149276}
0 commit comments