Skip to content

Commit 9251ca1

Browse files
authored
Fix interactive selection bug in parallel Crowdin command (#29)
1 parent 50f7a22 commit 9251ca1

File tree

3 files changed

+139
-1
lines changed

3 files changed

+139
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ AI-powered translation tool for Laravel language files
1717

1818
## 🔄 Recent Updates
1919

20-
- 🔁 **Parallel Translation**: Translate multiple locales concurrently with the new `translate-parallel` command. Specify locales with `--locale=ko,ja` and run up to five processes at once by default.
20+
- 🔁 **Parallel Translation**: Translate multiple locales concurrently with the `translate-parallel` command.
2121
- **New Provider**: Added Google Gemini support (including the 2.5 models)
2222
- **AI Enhancement**: Added support for Claude 3.7's Extended Thinking capabilities
2323
- Extended context window up to 200K tokens, output tokens up to 64K tokens
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
namespace Kargnas\LaravelAiTranslator\Console;
4+
5+
use Symfony\Component\Process\Process;
6+
7+
class TranslateCrowdinParallel extends TranslateCrowdin
8+
{
9+
protected $signature = 'ai-translator:translate-crowdin-parallel'
10+
. ' {--token= : Crowdin API token (optional, will use CROWDIN_API_KEY env by default)}'
11+
. ' {--organization= : Crowdin organization (optional)}'
12+
. ' {--project= : Crowdin project ID}'
13+
. ' {--source-language= : Source language code}'
14+
. ' {--target-language= : Target language code}'
15+
. ' {--chunk-size=50 : Chunk size for translation}'
16+
. ' {--max-context-items=10000 : Maximum number of context items}'
17+
. ' {--show-prompt : Show AI prompts during translation}'
18+
. ' {--max-processes=5 : Number of languages to translate simultaneously}';
19+
20+
protected $description = 'Translate Crowdin strings in parallel.';
21+
22+
public function handle()
23+
{
24+
try {
25+
$token = $this->option('token') ?: env('CROWDIN_API_KEY');
26+
$organization = $this->option('organization');
27+
28+
if (empty($token)) {
29+
$this->warn('No API token provided. You can:');
30+
$this->line('1. Set CROWDIN_API_KEY in your .env file');
31+
$this->line('2. Use --token option');
32+
$this->line('3. Enter it interactively');
33+
$token = $this->secret('Enter your Crowdin API token');
34+
35+
if (empty($token)) {
36+
throw new \RuntimeException('API token is required to connect to Crowdin.');
37+
}
38+
}
39+
40+
$this->displayHeader();
41+
42+
$this->initializeServices($token, $organization);
43+
44+
if (!$this->projectService->selectProject($this->option('project'))) {
45+
return 1;
46+
}
47+
48+
if (!$this->languageService->selectLanguages(
49+
$this->option('source-language'),
50+
$this->option('target-language')
51+
)) {
52+
return 1;
53+
}
54+
55+
$this->languageService->selectReferenceLanguages();
56+
57+
$languages = $this->languageService->getTargetLanguages();
58+
$queue = [];
59+
foreach ($languages as $language) {
60+
$queue[] = $language['id'];
61+
}
62+
63+
$projectId = $this->projectService->getProjectId();
64+
$sourceLanguage = $this->languageService->getSourceLocale();
65+
66+
$maxProcesses = (int) ($this->option('max-processes') ?? 5);
67+
$running = [];
68+
69+
while (!empty($queue) || !empty($running)) {
70+
while (count($running) < $maxProcesses && !empty($queue)) {
71+
$lang = array_shift($queue);
72+
$process = new Process(
73+
$this->buildLanguageCommand($lang, $token, $organization, $projectId, $sourceLanguage),
74+
base_path()
75+
);
76+
$process->setTimeout(null);
77+
$process->start();
78+
$running[$lang] = $process;
79+
$this->info('▶ Started translation for ' . $lang);
80+
}
81+
82+
foreach ($running as $lang => $process) {
83+
if (!$process->isRunning()) {
84+
$this->output->write($process->getOutput());
85+
$error = $process->getErrorOutput();
86+
if ($error) {
87+
$this->error($error);
88+
}
89+
unset($running[$lang]);
90+
}
91+
}
92+
93+
usleep(100000);
94+
}
95+
96+
$this->line('\n' . $this->colors['green_bg'] . $this->colors['white'] . $this->colors['bold'] . ' All translations completed ' . $this->colors['reset']);
97+
return 0;
98+
} catch (\Exception $e) {
99+
$this->displayError($e);
100+
return 1;
101+
}
102+
}
103+
104+
private function buildLanguageCommand(
105+
string $language,
106+
string $token,
107+
?string $organization,
108+
?int $projectId,
109+
string $sourceLanguage
110+
): array
111+
{
112+
$cmd = [
113+
'php',
114+
'artisan',
115+
'ai-translator:translate-crowdin',
116+
'--token=' . $token,
117+
];
118+
119+
if ($organization) {
120+
$cmd[] = '--organization=' . $organization;
121+
}
122+
if ($projectId !== null) {
123+
$cmd[] = '--project=' . $projectId;
124+
}
125+
$cmd[] = '--source-language=' . $sourceLanguage;
126+
$cmd[] = '--target-language=' . $language;
127+
$cmd[] = '--chunk-size=' . $this->option('chunk-size');
128+
$cmd[] = '--max-context-items=' . $this->option('max-context-items');
129+
if ($this->option('show-prompt')) {
130+
$cmd[] = '--show-prompt';
131+
}
132+
$cmd[] = '--no-interaction';
133+
134+
return $cmd;
135+
}
136+
}

src/ServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Kargnas\LaravelAiTranslator\Console\TranslateCrowdin;
88
use Kargnas\LaravelAiTranslator\Console\TranslateStrings;
99
use Kargnas\LaravelAiTranslator\Console\TranslateStringsParallel;
10+
use Kargnas\LaravelAiTranslator\Console\TranslateCrowdinParallel;
1011
use Kargnas\LaravelAiTranslator\Console\TranslateFileCommand;
1112

1213
class ServiceProvider extends \Illuminate\Support\ServiceProvider
@@ -28,6 +29,7 @@ public function register(): void
2829
$this->commands([
2930
TranslateStrings::class,
3031
TranslateStringsParallel::class,
32+
TranslateCrowdinParallel::class,
3133
TranslateCrowdin::class,
3234
TestTranslateCommand::class,
3335
TranslateFileCommand::class,

0 commit comments

Comments
 (0)