flutter-dev 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/CHANGELOG.md +18 -0
  2. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/PKG-INFO +12 -2
  3. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/README.md +11 -1
  4. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/fdev.py +12 -0
  5. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/flutter_dev.egg-info/SOURCES.txt +3 -1
  6. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/build.py +1 -1
  7. flutter_dev-0.1.4/managers/localization.py +293 -0
  8. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/pyproject.toml +2 -2
  9. flutter_dev-0.1.4/tests/test_localization.py +93 -0
  10. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/.env.example +0 -0
  11. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/LICENSE +0 -0
  12. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/MANIFEST.in +0 -0
  13. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/common_utils.py +0 -0
  14. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/core/__init__.py +0 -0
  15. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/core/constants.py +0 -0
  16. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/core/state.py +0 -0
  17. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/create_page.py +0 -0
  18. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/gemini_api.py +0 -0
  19. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/git_diff_output_editor.py +0 -0
  20. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/install_legacy.py +0 -0
  21. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/__init__.py +0 -0
  22. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/ai.py +0 -0
  23. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/app.py +0 -0
  24. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/brew.py +0 -0
  25. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/datetime.py +0 -0
  26. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/device.py +0 -0
  27. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/doctor.py +0 -0
  28. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/git.py +0 -0
  29. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/git_account.py +0 -0
  30. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/merge.py +0 -0
  31. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/mirror.py +0 -0
  32. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/project.py +0 -0
  33. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/web_deploy.py +0 -0
  34. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/requirements.txt +0 -0
  35. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/setup.cfg +0 -0
  36. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/setup.py +0 -0
  37. {flutter_dev-0.1.2 → flutter_dev-0.1.4}/switch_ai.py +0 -0
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 0.1.4 - 2026-06-15
6
+
7
+ ### Added
8
+
9
+ - Added `fdev arb-to-csv` to export Flutter ARB localization files from `lib/l10n` into `translations.csv` by default.
10
+ - Added `fdev csv-to-arb` to generate `app_<locale>.arb` files into `lib/l10n` from `translations.csv` by default.
11
+ - Added Python regression coverage for ARB/CSV conversion, locale parsing, default paths, and ARB metadata preservation.
12
+
13
+ ### Changed
14
+
15
+ - Documented the ARB/CSV localization workflow in the README command reference and quick-start examples.
16
+
17
+ ## 0.1.3 - 2026-06-08
18
+
19
+ ### Fixed
20
+
21
+ - Fixed CocoaPods command execution for `fdev pod` and `fdev ipa` so pod subcommands keep their arguments on macOS and Linux.
22
+
5
23
  ## 0.1.2 - 2026-06-07
6
24
 
7
25
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flutter-dev
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A cross-platform CLI toolkit for Flutter development workflows.
5
5
  Author: Royal Court BD
6
6
  License-Expression: MIT
@@ -41,12 +41,13 @@ The package is published on PyPI as `flutter-dev` and installs console commands
41
41
  pip install flutter-dev
42
42
  ```
43
43
 
44
- Current package metadata declares version `0.1.1`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
44
+ Current package metadata declares version `0.1.4`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
45
45
 
46
46
  ## Key Features
47
47
 
48
48
  - Flutter build automation for APK, split APK, AAB, IPA, release install, and Firebase web deploy.
49
49
  - Project maintenance helpers for `flutter clean`, dependency refresh, `build_runner`, localization generation, analysis, formatting, cache repair, and CocoaPods.
50
+ - Localization CSV helpers for exporting ARB files to translator-friendly CSV files and generating ARB files back from CSV.
50
51
  - Android device helpers for APK install, app uninstall, foreground app data clearing, screen mirroring with `scrcpy`, wireless ADB setup, and Date & Time settings.
51
52
  - AI-assisted Git commits using Groq, Mistral, SambaNova, or OpenRouter-compatible chat completion APIs.
52
53
  - Git workflow helpers for version tags, branch sync, deployment branch merge, discard, and GitHub account management through `gh`.
@@ -129,6 +130,12 @@ fdev doctor
129
130
  # Generate localization files
130
131
  fdev lang
131
132
 
133
+ # Export ARB translations from lib/l10n to translations.csv
134
+ fdev arb-to-csv
135
+
136
+ # Generate ARB files in lib/l10n from translations.csv
137
+ fdev csv-to-arb
138
+
132
139
  # Run build_runner
133
140
  fdev db
134
141
 
@@ -167,6 +174,8 @@ Android APK/AAB build commands rename output files using the Android app label a
167
174
  | `fdev setup` | Runs clean, dependency upgrade, `build_runner`, localization generation, another dependency upgrade, `flutter analyze`, and `dart format .`. |
168
175
  | `fdev cleanup` | Runs clean, pub get, `dart fix --apply`, `dart format .`, and `flutter pub upgrade --major-versions`. |
169
176
  | `fdev lang` | Runs `flutter gen-l10n`. |
177
+ | `fdev arb-to-csv` | Exports `lib/l10n/*.arb` into `translations.csv` by default. Use `-d/--directory` and `-o/--output` to override paths. |
178
+ | `fdev csv-to-arb` | Generates `app_<locale>.arb` files into `lib/l10n` from `translations.csv` by default. Use `-i/--input` and `-o/--output-dir` to override paths. |
170
179
  | `fdev db` | Runs `dart run build_runner build --delete-conflicting-outputs`. |
171
180
  | `fdev cache-repair` | Runs `flutter pub cache repair`. |
172
181
  | `fdev pod` | Updates CocoaPods from the `ios/` directory after removing `Podfile.lock` when present. |
@@ -450,6 +459,7 @@ For local packaging checks:
450
459
 
451
460
  ```bash
452
461
  python -m pip install --upgrade build twine
462
+ rm -rf dist build flutter_dev.egg-info
453
463
  python -m build
454
464
  python -m twine check dist/*
455
465
  ```
@@ -8,12 +8,13 @@ The package is published on PyPI as `flutter-dev` and installs console commands
8
8
  pip install flutter-dev
9
9
  ```
10
10
 
11
- Current package metadata declares version `0.1.1`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
11
+ Current package metadata declares version `0.1.4`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
12
12
 
13
13
  ## Key Features
14
14
 
15
15
  - Flutter build automation for APK, split APK, AAB, IPA, release install, and Firebase web deploy.
16
16
  - Project maintenance helpers for `flutter clean`, dependency refresh, `build_runner`, localization generation, analysis, formatting, cache repair, and CocoaPods.
17
+ - Localization CSV helpers for exporting ARB files to translator-friendly CSV files and generating ARB files back from CSV.
17
18
  - Android device helpers for APK install, app uninstall, foreground app data clearing, screen mirroring with `scrcpy`, wireless ADB setup, and Date & Time settings.
18
19
  - AI-assisted Git commits using Groq, Mistral, SambaNova, or OpenRouter-compatible chat completion APIs.
19
20
  - Git workflow helpers for version tags, branch sync, deployment branch merge, discard, and GitHub account management through `gh`.
@@ -96,6 +97,12 @@ fdev doctor
96
97
  # Generate localization files
97
98
  fdev lang
98
99
 
100
+ # Export ARB translations from lib/l10n to translations.csv
101
+ fdev arb-to-csv
102
+
103
+ # Generate ARB files in lib/l10n from translations.csv
104
+ fdev csv-to-arb
105
+
99
106
  # Run build_runner
100
107
  fdev db
101
108
 
@@ -134,6 +141,8 @@ Android APK/AAB build commands rename output files using the Android app label a
134
141
  | `fdev setup` | Runs clean, dependency upgrade, `build_runner`, localization generation, another dependency upgrade, `flutter analyze`, and `dart format .`. |
135
142
  | `fdev cleanup` | Runs clean, pub get, `dart fix --apply`, `dart format .`, and `flutter pub upgrade --major-versions`. |
136
143
  | `fdev lang` | Runs `flutter gen-l10n`. |
144
+ | `fdev arb-to-csv` | Exports `lib/l10n/*.arb` into `translations.csv` by default. Use `-d/--directory` and `-o/--output` to override paths. |
145
+ | `fdev csv-to-arb` | Generates `app_<locale>.arb` files into `lib/l10n` from `translations.csv` by default. Use `-i/--input` and `-o/--output-dir` to override paths. |
137
146
  | `fdev db` | Runs `dart run build_runner build --delete-conflicting-outputs`. |
138
147
  | `fdev cache-repair` | Runs `flutter pub cache repair`. |
139
148
  | `fdev pod` | Updates CocoaPods from the `ios/` directory after removing `Podfile.lock` when present. |
@@ -417,6 +426,7 @@ For local packaging checks:
417
426
 
418
427
  ```bash
419
428
  python -m pip install --upgrade build twine
429
+ rm -rf dist build flutter_dev.egg-info
420
430
  python -m build
421
431
  python -m twine check dist/*
422
432
  ```
@@ -47,6 +47,10 @@ from managers.mirror import (
47
47
  from managers.merge import (
48
48
  merge_files,
49
49
  )
50
+ from managers.localization import (
51
+ arb_to_csv_command,
52
+ csv_to_arb_command,
53
+ )
50
54
  from managers.ai import (
51
55
  show_ai_status,
52
56
  switch_ai_service,
@@ -86,6 +90,8 @@ def show_usage():
86
90
  print(" cleanup Clean project and get dependencies")
87
91
  print(" cache-repair Repair pub cache")
88
92
  print(" page Create page structure (usage: fdev page <page_name>)")
93
+ print(" arb-to-csv Export ARB localization files to CSV")
94
+ print(" csv-to-arb Generate ARB localization files from CSV")
89
95
 
90
96
  print(f"\n{BLUE}Device Commands:{NC}")
91
97
  print(" install Install built APK on connected device")
@@ -129,6 +135,8 @@ def show_usage():
129
135
  print(f" {GREEN}fdev git{NC} # Interactive GitHub account manager")
130
136
  print(f" {GREEN}fdev git switch royalcourtbd{NC} # Switch to a specific GH account")
131
137
  print(f" {GREEN}fdev git config{NC} # Update git user.name / user.email")
138
+ print(f" {GREEN}fdev arb-to-csv{NC} # Export lib/l10n ARB files to translations.csv")
139
+ print(f" {GREEN}fdev csv-to-arb{NC} # Generate lib/l10n ARB files from translations.csv")
132
140
 
133
141
  print(f"\n{BLUE}Note:{NC}")
134
142
  print(f" {YELLOW}Multiple devices detected?{NC} Tool will prompt you to select one.")
@@ -172,6 +180,10 @@ def main():
172
180
  repair_cache()
173
181
  elif command == "cleanup":
174
182
  cleanup_project()
183
+ elif command == "arb-to-csv":
184
+ sys.exit(arb_to_csv_command(sys.argv[2:]))
185
+ elif command == "csv-to-arb":
186
+ sys.exit(csv_to_arb_command(sys.argv[2:]))
175
187
 
176
188
  # Device commands
177
189
  elif command == "install":
@@ -26,7 +26,9 @@ managers/device.py
26
26
  managers/doctor.py
27
27
  managers/git.py
28
28
  managers/git_account.py
29
+ managers/localization.py
29
30
  managers/merge.py
30
31
  managers/mirror.py
31
32
  managers/project.py
32
- managers/web_deploy.py
33
+ managers/web_deploy.py
34
+ tests/test_localization.py
@@ -56,7 +56,7 @@ def run_flutter_command(cmd_list, description, env=None):
56
56
  env: Optional environment variables dict (default: inherits current env)
57
57
  """
58
58
  # Windows compatibility for shell commands
59
- shell_needed = (is_windows() and cmd_list[0] in ['timeout', 'start', 'flutter', 'dart']) or cmd_list[0] == 'pod'
59
+ shell_needed = is_windows() and cmd_list[0] in ['timeout', 'start', 'flutter', 'dart']
60
60
 
61
61
  process = subprocess.Popen(
62
62
  cmd_list,
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Localization Manager - ARB and CSV conversion helpers.
4
+ """
5
+
6
+ import argparse
7
+ import csv
8
+ import json
9
+ import os
10
+ import tempfile
11
+ from collections import OrderedDict
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional, Tuple
15
+
16
+ from common_utils import BLUE, CHECKMARK, GREEN, RED, YELLOW, NC
17
+
18
+
19
+ DEFAULT_ARB_DIRECTORY = Path("lib/l10n")
20
+ DEFAULT_CSV_FILE = Path("translations.csv")
21
+
22
+
23
+ @dataclass
24
+ class LocalizationConversionResult:
25
+ """Summary returned by localization conversion functions."""
26
+
27
+ languages: List[str]
28
+ total_keys: int
29
+ output_path: Optional[Path] = None
30
+ output_directory: Optional[Path] = None
31
+ files_written: int = 0
32
+ missing_translations: int = 0
33
+
34
+
35
+ def extract_language_code(filename):
36
+ """Extract the locale from an ARB filename."""
37
+ stem = Path(filename).stem
38
+ if "_" in stem:
39
+ return stem.split("_", 1)[1]
40
+ return stem
41
+
42
+
43
+ def _load_arb_messages(filepath):
44
+ with open(filepath, "r", encoding="utf-8") as file:
45
+ data = json.load(file, object_pairs_hook=OrderedDict)
46
+
47
+ return OrderedDict((key, value) for key, value in data.items() if not key.startswith("@"))
48
+
49
+
50
+ def _order_arb_files(arb_files):
51
+ sorted_files = sorted(arb_files, key=lambda path: path.name)
52
+ english_files = [path for path in sorted_files if extract_language_code(path.name) == "en"]
53
+ if not english_files:
54
+ return sorted_files
55
+ return english_files + [path for path in sorted_files if path not in english_files]
56
+
57
+
58
+ def convert_arb_to_csv(arb_directory=DEFAULT_ARB_DIRECTORY, output_file=DEFAULT_CSV_FILE):
59
+ """Convert ARB files from a directory into one CSV file."""
60
+ arb_dir = Path(arb_directory)
61
+ output_path = Path(output_file)
62
+
63
+ if not arb_dir.exists():
64
+ raise FileNotFoundError(f"ARB directory does not exist: {arb_dir}")
65
+ if not arb_dir.is_dir():
66
+ raise NotADirectoryError(f"ARB path is not a directory: {arb_dir}")
67
+
68
+ arb_files = _order_arb_files(list(arb_dir.glob("*.arb")))
69
+ if not arb_files:
70
+ raise FileNotFoundError(f"No ARB files found in {arb_dir}")
71
+
72
+ all_data = OrderedDict()
73
+ languages = []
74
+ ordered_keys = []
75
+ seen_keys = set()
76
+
77
+ for arb_file in arb_files:
78
+ lang_code = extract_language_code(arb_file.name)
79
+ messages = _load_arb_messages(arb_file)
80
+
81
+ all_data[lang_code] = messages
82
+ languages.append(lang_code)
83
+
84
+ for key in messages.keys():
85
+ if key not in seen_keys:
86
+ ordered_keys.append(key)
87
+ seen_keys.add(key)
88
+
89
+ output_path.parent.mkdir(parents=True, exist_ok=True)
90
+ missing_translations = 0
91
+
92
+ with open(output_path, "w", newline="", encoding="utf-8") as csvfile:
93
+ writer = csv.writer(csvfile)
94
+ writer.writerow(["key"] + languages)
95
+
96
+ for key in ordered_keys:
97
+ row = [key]
98
+ for lang in languages:
99
+ if key not in all_data[lang]:
100
+ missing_translations += 1
101
+ row.append(all_data[lang].get(key, ""))
102
+ writer.writerow(row)
103
+
104
+ return LocalizationConversionResult(
105
+ languages=languages,
106
+ total_keys=len(ordered_keys),
107
+ output_path=output_path,
108
+ missing_translations=missing_translations,
109
+ )
110
+
111
+
112
+ def _read_csv_translations(input_file):
113
+ csv_path = Path(input_file)
114
+ if not csv_path.exists():
115
+ raise FileNotFoundError(f"CSV file does not exist: {csv_path}")
116
+
117
+ with open(csv_path, "r", encoding="utf-8", newline="") as file:
118
+ reader = csv.reader(file)
119
+ try:
120
+ header = next(reader)
121
+ except StopIteration:
122
+ raise ValueError("CSV file is empty")
123
+
124
+ if not header or header[0].strip() != "key":
125
+ raise ValueError("First CSV column must be 'key'")
126
+
127
+ languages = [language.strip() for language in header[1:] if language.strip()]
128
+ if not languages:
129
+ raise ValueError("CSV must include at least one language column")
130
+
131
+ translations = OrderedDict((language, OrderedDict()) for language in languages)
132
+ keys = []
133
+ seen_keys = set()
134
+
135
+ for row_number, row in enumerate(reader, start=2):
136
+ if not row or not any(cell.strip() for cell in row):
137
+ continue
138
+
139
+ key = row[0].strip()
140
+ if not key:
141
+ raise ValueError(f"Row {row_number}: key is required")
142
+ if key in seen_keys:
143
+ raise ValueError(f"Row {row_number}: duplicate key '{key}'")
144
+
145
+ keys.append(key)
146
+ seen_keys.add(key)
147
+
148
+ for index, language in enumerate(languages):
149
+ value = row[index + 1] if index + 1 < len(row) else ""
150
+ translations[language][key] = value
151
+
152
+ return languages, keys, translations
153
+
154
+
155
+ def _load_existing_metadata(arb_file):
156
+ if not arb_file.exists():
157
+ return OrderedDict(), OrderedDict()
158
+
159
+ with open(arb_file, "r", encoding="utf-8") as file:
160
+ data = json.load(file, object_pairs_hook=OrderedDict)
161
+
162
+ global_metadata = OrderedDict()
163
+ message_metadata = OrderedDict()
164
+
165
+ for key, value in data.items():
166
+ if key == "@@locale":
167
+ continue
168
+ if key.startswith("@@"):
169
+ global_metadata[key] = value
170
+ elif key.startswith("@"):
171
+ message_metadata[key] = value
172
+
173
+ return global_metadata, message_metadata
174
+
175
+
176
+ def _atomic_write_json(file_path, data):
177
+ temp_name = None
178
+ try:
179
+ with tempfile.NamedTemporaryFile(
180
+ "w",
181
+ delete=False,
182
+ dir=str(file_path.parent),
183
+ encoding="utf-8",
184
+ ) as temp_file:
185
+ json.dump(data, temp_file, ensure_ascii=False, indent=2)
186
+ temp_file.write("\n")
187
+ temp_name = temp_file.name
188
+
189
+ os.replace(temp_name, file_path)
190
+ finally:
191
+ if temp_name and os.path.exists(temp_name):
192
+ os.unlink(temp_name)
193
+
194
+
195
+ def convert_csv_to_arb(input_file=DEFAULT_CSV_FILE, output_directory=DEFAULT_ARB_DIRECTORY):
196
+ """Convert one CSV translation file into ARB files."""
197
+ output_dir = Path(output_directory)
198
+ output_dir.mkdir(parents=True, exist_ok=True)
199
+
200
+ languages, keys, translations = _read_csv_translations(input_file)
201
+
202
+ for language in languages:
203
+ arb_file = output_dir / f"app_{language}.arb"
204
+ global_metadata, message_metadata = _load_existing_metadata(arb_file)
205
+
206
+ arb_data = OrderedDict()
207
+ arb_data["@@locale"] = language
208
+ for key, value in global_metadata.items():
209
+ arb_data[key] = value
210
+
211
+ for key in keys:
212
+ arb_data[key] = translations[language][key]
213
+ metadata_key = f"@{key}"
214
+ if metadata_key in message_metadata:
215
+ arb_data[metadata_key] = message_metadata[metadata_key]
216
+
217
+ _atomic_write_json(arb_file, arb_data)
218
+
219
+ return LocalizationConversionResult(
220
+ languages=languages,
221
+ total_keys=len(keys),
222
+ output_directory=output_dir,
223
+ files_written=len(languages),
224
+ )
225
+
226
+
227
+ def arb_to_csv_command(argv=None):
228
+ """CLI entry point for ARB to CSV export."""
229
+ parser = argparse.ArgumentParser(
230
+ prog="fdev arb-to-csv",
231
+ description="Export Flutter ARB localization files to a CSV file.",
232
+ )
233
+ parser.add_argument(
234
+ "-d",
235
+ "--directory",
236
+ default=str(DEFAULT_ARB_DIRECTORY),
237
+ help="Directory containing ARB files (default: lib/l10n)",
238
+ )
239
+ parser.add_argument(
240
+ "-o",
241
+ "--output",
242
+ default=str(DEFAULT_CSV_FILE),
243
+ help="Output CSV file path (default: translations.csv)",
244
+ )
245
+ args = parser.parse_args(argv)
246
+
247
+ try:
248
+ result = convert_arb_to_csv(args.directory, args.output)
249
+ except (OSError, ValueError, json.JSONDecodeError) as error:
250
+ print(f"{RED}Error: {error}{NC}")
251
+ return 1
252
+
253
+ print(f"{CHECKMARK} ARB files exported to CSV.")
254
+ print(f"{BLUE}Output:{NC} {result.output_path}")
255
+ print(f"{BLUE}Languages:{NC} {', '.join(result.languages)}")
256
+ print(f"{BLUE}Total keys:{NC} {result.total_keys}")
257
+ if result.missing_translations:
258
+ print(f"{YELLOW}Missing translations:{NC} {result.missing_translations}")
259
+ return 0
260
+
261
+
262
+ def csv_to_arb_command(argv=None):
263
+ """CLI entry point for CSV to ARB generation."""
264
+ parser = argparse.ArgumentParser(
265
+ prog="fdev csv-to-arb",
266
+ description="Generate Flutter ARB localization files from a CSV file.",
267
+ )
268
+ parser.add_argument(
269
+ "-i",
270
+ "--input",
271
+ default=str(DEFAULT_CSV_FILE),
272
+ help="Input CSV file path (default: translations.csv)",
273
+ )
274
+ parser.add_argument(
275
+ "-o",
276
+ "--output-dir",
277
+ default=str(DEFAULT_ARB_DIRECTORY),
278
+ help="Directory where ARB files will be written (default: lib/l10n)",
279
+ )
280
+ args = parser.parse_args(argv)
281
+
282
+ try:
283
+ result = convert_csv_to_arb(args.input, args.output_dir)
284
+ except (OSError, ValueError, json.JSONDecodeError) as error:
285
+ print(f"{RED}Error: {error}{NC}")
286
+ return 1
287
+
288
+ print(f"{CHECKMARK} CSV translations generated ARB files.")
289
+ print(f"{BLUE}Output directory:{NC} {result.output_directory}")
290
+ print(f"{BLUE}Languages:{NC} {', '.join(result.languages)}")
291
+ print(f"{BLUE}Total keys:{NC} {result.total_keys}")
292
+ print(f"{GREEN}Files written:{NC} {result.files_written}")
293
+ return 0
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools>=69", "wheel"]
2
+ requires = ["setuptools>=77.0.3", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flutter-dev"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "A cross-platform CLI toolkit for Flutter development workflows."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -0,0 +1,93 @@
1
+ import csv
2
+ import json
3
+ import tempfile
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+ from managers.localization import (
8
+ convert_arb_to_csv,
9
+ convert_csv_to_arb,
10
+ extract_language_code,
11
+ )
12
+
13
+
14
+ class LocalizationConversionTests(unittest.TestCase):
15
+ def test_extract_language_code_preserves_multi_part_locale(self):
16
+ self.assertEqual(extract_language_code("app_en.arb"), "en")
17
+ self.assertEqual(extract_language_code("app_pt_BR.arb"), "pt_BR")
18
+
19
+ def test_convert_arb_to_csv_uses_default_l10n_folder_and_en_first(self):
20
+ with tempfile.TemporaryDirectory() as tmp:
21
+ root = Path(tmp)
22
+ l10n = root / "lib" / "l10n"
23
+ l10n.mkdir(parents=True)
24
+ (l10n / "app_bn.arb").write_text(
25
+ json.dumps(
26
+ {
27
+ "@@locale": "bn",
28
+ "hello": "হ্যালো",
29
+ "onlyBn": "শুধু বাংলা",
30
+ },
31
+ ensure_ascii=False,
32
+ ),
33
+ encoding="utf-8",
34
+ )
35
+ (l10n / "app_en.arb").write_text(
36
+ json.dumps(
37
+ {
38
+ "@@locale": "en",
39
+ "hello": "Hello",
40
+ "onlyEn": "Only English",
41
+ },
42
+ ensure_ascii=False,
43
+ ),
44
+ encoding="utf-8",
45
+ )
46
+
47
+ result = convert_arb_to_csv(root / "lib" / "l10n", root / "translations.csv")
48
+
49
+ self.assertEqual(result.total_keys, 3)
50
+ self.assertEqual(result.languages, ["en", "bn"])
51
+ with (root / "translations.csv").open(encoding="utf-8", newline="") as file:
52
+ rows = list(csv.reader(file))
53
+ self.assertEqual(rows[0], ["key", "en", "bn"])
54
+ self.assertIn(["hello", "Hello", "হ্যালো"], rows)
55
+ self.assertIn(["onlyEn", "Only English", ""], rows)
56
+ self.assertIn(["onlyBn", "", "শুধু বাংলা"], rows)
57
+
58
+ def test_convert_csv_to_arb_writes_default_l10n_folder_and_preserves_metadata(self):
59
+ with tempfile.TemporaryDirectory() as tmp:
60
+ root = Path(tmp)
61
+ l10n = root / "lib" / "l10n"
62
+ l10n.mkdir(parents=True)
63
+ (l10n / "app_en.arb").write_text(
64
+ json.dumps(
65
+ {
66
+ "@@locale": "en",
67
+ "hello": "Old",
68
+ "@hello": {"description": "Greeting text"},
69
+ },
70
+ ensure_ascii=False,
71
+ indent=2,
72
+ ),
73
+ encoding="utf-8",
74
+ )
75
+ (root / "translations.csv").write_text(
76
+ "key,en,bn\nhello,Hello,হ্যালো\nbye,Bye,বিদায়\n",
77
+ encoding="utf-8",
78
+ )
79
+
80
+ result = convert_csv_to_arb(root / "translations.csv", root / "lib" / "l10n")
81
+
82
+ self.assertEqual(result.languages, ["en", "bn"])
83
+ en_data = json.loads((l10n / "app_en.arb").read_text(encoding="utf-8"))
84
+ bn_data = json.loads((l10n / "app_bn.arb").read_text(encoding="utf-8"))
85
+ self.assertEqual(en_data["@@locale"], "en")
86
+ self.assertEqual(en_data["hello"], "Hello")
87
+ self.assertEqual(en_data["@hello"], {"description": "Greeting text"})
88
+ self.assertEqual(bn_data["@@locale"], "bn")
89
+ self.assertEqual(bn_data["bye"], "বিদায়")
90
+
91
+
92
+ if __name__ == "__main__":
93
+ unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes