Skip to content

Commit f5986eb

Browse files
committed
feat(core): improve xcstrings translate workflow
1 parent 39467d5 commit f5986eb

6 files changed

Lines changed: 1065 additions & 152 deletions

File tree

langcodec-cli/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,17 @@ Behavior:
107107
- writes generated values as `needs_review`
108108
- skips plural entries in v1
109109
- writes in-place by default and supports `--dry-run`
110+
- performs output preflight validation before sending model requests
111+
- shows live progress in interactive terminals and prints translated result lines after completion
112+
- supports comma-separated `--target-lang` values for multi-language outputs such as `.xcstrings`, `.csv`, and `.tsv`
113+
- supports `translate.source` and `translate.sources` in `langcodec.toml`, so `langcodec translate` can run with no path flags
110114
- supports translate defaults from `langcodec.toml`
111115

112116
Example `langcodec.toml`:
113117

114118
```toml
115119
[translate]
120+
source = "locales/Localizable.xcstrings"
116121
provider = "openai"
117122
model = "gpt-4.1-mini"
118123
source_lang = "en"
@@ -121,10 +126,28 @@ concurrency = 4
121126
status = ["new", "stale"]
122127
```
123128

129+
For multi-language outputs, `target_lang` may also be a comma-separated string such as `"fr,de,ja"`.
130+
131+
For parallel config-driven runs, use:
132+
133+
```toml
134+
[translate]
135+
sources = [
136+
"features/auth/Localizable.xcstrings",
137+
"features/profile/Localizable.xcstrings",
138+
]
139+
provider = "openai"
140+
model = "gpt-4.1-mini"
141+
source_lang = "en"
142+
target_lang = "fr,de"
143+
```
144+
124145
Config notes:
125146

126147
- save the file as `langcodec.toml` in your project root, or pass `--config /absolute/path/to/langcodec.toml`
127148
- config discovery walks upward from the current directory until it finds `langcodec.toml`
149+
- `translate.source`, `translate.sources`, `translate.target`, and `translate.output` are resolved relative to the `langcodec.toml` file
150+
- `translate.sources` fans out into parallel translate runs; it cannot be combined with a single shared `target` or `output`
128151
- CLI flags still override config values
129152

130153
Provider/auth notes:

langcodec-cli/src/config.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ pub struct CliConfig {
99

1010
#[derive(Debug, Clone, Default, Deserialize)]
1111
pub struct TranslateConfig {
12+
pub source: Option<String>,
13+
pub sources: Option<Vec<String>>,
14+
pub target: Option<String>,
15+
pub output: Option<String>,
1216
pub provider: Option<String>,
1317
pub model: Option<String>,
1418
pub source_lang: Option<String>,
@@ -28,7 +32,10 @@ pub fn load_config(explicit_path: Option<&str>) -> Result<Option<LoadedConfig>,
2832
Some(path) => {
2933
let resolved = PathBuf::from(path);
3034
if !resolved.exists() {
31-
return Err(format!("Config file does not exist: {}", resolved.display()));
35+
return Err(format!(
36+
"Config file does not exist: {}",
37+
resolved.display()
38+
));
3239
}
3340
resolved
3441
}

langcodec-cli/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
pub mod config;
44
pub mod formats;
55
pub mod merge;
6-
pub mod translate;
76
pub mod transformers;
7+
pub mod translate;
88
pub mod validation;
99

1010
pub use formats::{CustomFormat, parse_custom_format};
1111
pub use langcodec::Codec;
12-
pub use translate::{TranslateOptions, run_translate_command};
1312
pub use transformers::custom_format_to_resource;
13+
pub use translate::{TranslateOptions, run_translate_command};

langcodec-cli/src/main.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ mod normalize;
99
mod path_glob;
1010
mod stats;
1111
mod sync;
12-
mod translate;
1312
mod transformers;
13+
mod translate;
1414
mod validation;
1515
mod view;
1616

@@ -259,9 +259,9 @@ enum Commands {
259259

260260
/// Translate source entries into a target language using Mentra-backed providers.
261261
Translate {
262-
/// Source localization file
262+
/// Source localization file. Required unless configured in `langcodec.toml`.
263263
#[arg(short = 's', long)]
264-
source: String,
264+
source: Option<String>,
265265

266266
/// Optional target localization file. If omitted, translates in-place within multi-language files.
267267
#[arg(short = 't', long)]
@@ -275,9 +275,9 @@ enum Commands {
275275
#[arg(long)]
276276
source_lang: Option<String>,
277277

278-
/// Target language code.
279-
#[arg(long)]
280-
target_lang: Option<String>,
278+
/// Target language code(s). Comma-separated values are supported for multi-language outputs.
279+
#[arg(long, value_name = "LANG", value_delimiter = ',')]
280+
target_lang: Vec<String>,
281281

282282
/// Filter target entries by status before translating (default: new,stale)
283283
#[arg(long)]
@@ -710,7 +710,10 @@ fn main() {
710710
config,
711711
dry_run,
712712
} => {
713-
let mut context = ValidationContext::new().with_input_file(source.clone());
713+
let mut context = ValidationContext::new();
714+
if let Some(source_path) = &source {
715+
context = context.with_input_file(source_path.clone());
716+
}
714717
if let Some(target_path) = &target
715718
&& std::path::Path::new(target_path).exists()
716719
{
@@ -728,19 +731,19 @@ fn main() {
728731
eprintln!("❌ Validation failed: {}", e);
729732
std::process::exit(1);
730733
}
731-
if let Some(lang_code) = &target_lang
732-
&& let Err(e) = validate_language_code(lang_code)
733-
{
734-
eprintln!("❌ Validation failed: {}", e);
735-
std::process::exit(1);
734+
for lang_code in &target_lang {
735+
if let Err(e) = validate_language_code(lang_code) {
736+
eprintln!("❌ Validation failed: {}", e);
737+
std::process::exit(1);
738+
}
736739
}
737740

738741
if let Err(e) = run_translate_command(TranslateOptions {
739742
source,
740743
target,
741744
output,
742745
source_lang,
743-
target_lang,
746+
target_langs: target_lang,
744747
status,
745748
provider,
746749
model,

0 commit comments

Comments
 (0)