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.
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/CHANGELOG.md +18 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/PKG-INFO +12 -2
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/README.md +11 -1
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/fdev.py +12 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/flutter_dev.egg-info/SOURCES.txt +3 -1
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/build.py +1 -1
- flutter_dev-0.1.4/managers/localization.py +293 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/pyproject.toml +2 -2
- flutter_dev-0.1.4/tests/test_localization.py +93 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/.env.example +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/LICENSE +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/MANIFEST.in +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/common_utils.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/core/__init__.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/core/constants.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/core/state.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/create_page.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/gemini_api.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/git_diff_output_editor.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/install_legacy.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/__init__.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/ai.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/app.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/brew.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/datetime.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/device.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/doctor.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/git.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/git_account.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/merge.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/mirror.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/project.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/managers/web_deploy.py +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/requirements.txt +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/setup.cfg +0 -0
- {flutter_dev-0.1.2 → flutter_dev-0.1.4}/setup.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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>=
|
|
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.
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|