Skip to content

Commit 86be16e

Browse files
authored
Merge pull request #120 from dimtrovich/translations
feat: Support for localized strings
2 parents cd0152f + be819d4 commit 86be16e

15 files changed

+171
-54
lines changed

README.md

+23-1
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,29 @@ Whenever an exception is caught by `Application::handle()`, it will show a beaut
828828

829829
![Exception Preview](https://user-images.githubusercontent.com/2908547/44401057-8b350880-a577-11e8-8ca6-20508d593d98.png "Exception trace")
830830

831-
### Autocompletion
831+
## I18n Support
832+
833+
**adhocore/cli** also supports internationalisation. This is particularly useful if you are not very comfortable with English or if you are creating a framework or CLI application that could be used by people from a variety of backgrounds.
834+
835+
By default, all the texts generated by our system are in English. But you can easily modify them by defining your translations as follows
836+
837+
```php
838+
\Ahc\Application::addLocale('fr', [
839+
'Only last argument can be variadic' => 'Seul le dernier argument peut être variadique',
840+
], true);
841+
```
842+
843+
You can also change the default English text to make the description more explicit if you wish.
844+
845+
```php
846+
\Ahc\Application::addLocale('en', [
847+
'Show help' => 'Shows helpful information about a command',
848+
]);
849+
```
850+
851+
vous pouvez trouver toutes les clés de traduction supportées par le paquet dans cette gist : https://gist.github.com/dimtrovich/1597c16d5c74334e68eef15a4e7ba3fd
852+
853+
## Autocompletion
832854

833855
Any console applications that are built on top of **adhocore/cli** can entertain autocomplete of commands and options in zsh shell with oh-my-zsh.
834856

composer.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
"autoload": {
3030
"psr-4": {
3131
"Ahc\\Cli\\": "src/"
32-
}
32+
},
33+
"files": [
34+
"src/functions.php"
35+
]
3336
},
3437
"autoload-dev": {
3538
"psr-4": {

src/Application.php

+31-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Ahc\Cli;
1313

1414
use Ahc\Cli\Exception\InvalidArgumentException;
15+
use Ahc\Cli\Helper\InflectsString;
1516
use Ahc\Cli\Helper\OutputHelper;
1617
use Ahc\Cli\Input\Command;
1718
use Ahc\Cli\IO\Interactor;
@@ -28,7 +29,6 @@
2829
use function is_array;
2930
use function is_int;
3031
use function method_exists;
31-
use function sprintf;
3232

3333
/**
3434
* A cli application.
@@ -40,6 +40,23 @@
4040
*/
4141
class Application
4242
{
43+
/**
44+
* Locale of CLI.
45+
*/
46+
public static $locale = 'en';
47+
48+
/**
49+
* list of translations for each supported locale
50+
*
51+
* @var array<string, array>
52+
*
53+
* @example
54+
* ```php
55+
* ['locale' => ['key1' => 'value1', 'key2' => 'value2']]
56+
* ```
57+
*/
58+
public static $locales = [];
59+
4360
/** @var Command[] */
4461
protected array $commands = [];
4562

@@ -130,6 +147,15 @@ public function logo(?string $logo = null)
130147
return $this;
131148
}
132149

150+
public static function addLocale(string $locale, array $texts, bool $default = false)
151+
{
152+
if ($default) {
153+
self::$locale = $locale;
154+
}
155+
156+
self::$locales[$locale] = $texts;
157+
}
158+
133159
/**
134160
* Add a command by its name desc alias etc and return command.
135161
*/
@@ -161,7 +187,7 @@ public function add(Command $command, string $alias = '', bool $default = false)
161187
$this->aliases[$alias] ??
162188
null
163189
) {
164-
throw new InvalidArgumentException(sprintf('Command "%s" already added', $name));
190+
throw new InvalidArgumentException(t('Command "%s" already added', [$name]));
165191
}
166192

167193
if ($alias) {
@@ -190,7 +216,7 @@ public function add(Command $command, string $alias = '', bool $default = false)
190216
public function defaultCommand(string $commandName): self
191217
{
192218
if (!isset($this->commands[$commandName])) {
193-
throw new InvalidArgumentException(sprintf('Command "%s" does not exist', $commandName));
219+
throw new InvalidArgumentException(t('Command "%s" does not exist', [$commandName]));
194220
}
195221

196222
$this->default = $commandName;
@@ -386,8 +412,8 @@ public function showHelp(): mixed
386412
public function showDefaultHelp(): mixed
387413
{
388414
$writer = $this->io()->writer();
389-
$header = "{$this->name}, version {$this->version}";
390-
$footer = 'Run `<command> --help` for specific help';
415+
$header = "{$this->name}, " . t('version') . " {$this->version}";
416+
$footer = t('Run `<command> --help` for specific help');
391417

392418
if ($this->logo) {
393419
$writer->logo($this->logo, true);

src/Helper/OutputHelper.php

+11-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Ahc\Cli\Output\Writer;
2121
use Throwable;
2222

23+
use function Ahc\Cli\t;
2324
use function array_map;
2425
use function array_shift;
2526
use function asort;
@@ -58,6 +59,8 @@
5859
*/
5960
class OutputHelper
6061
{
62+
use InflectsString;
63+
6164
protected Writer $writer;
6265

6366
/** @var int Max width of command name */
@@ -77,7 +80,7 @@ public function printTrace(Throwable $e): void
7780

7881
$this->writer->colors(
7982
"{$eClass} <red>{$e->getMessage()}</end><eol/>" .
80-
"(thrown in <yellow>{$e->getFile()}</end><white>:{$e->getLine()})</end>"
83+
'(' . t('thrown in') . " <yellow>{$e->getFile()}</end><white>:{$e->getLine()})</end>"
8184
);
8285

8386
// @codeCoverageIgnoreStart
@@ -87,7 +90,7 @@ public function printTrace(Throwable $e): void
8790
}
8891
// @codeCoverageIgnoreEnd
8992

90-
$traceStr = '<eol/><eol/><bold>Stack Trace:</end><eol/><eol/>';
93+
$traceStr = '<eol/><eol/><bold>' . t('Stack Trace') . ':</end><eol/><eol/>';
9194

9295
foreach ($e->getTrace() as $i => $trace) {
9396
$trace += ['class' => '', 'type' => '', 'function' => '', 'file' => '', 'line' => '', 'args' => []];
@@ -97,7 +100,7 @@ public function printTrace(Throwable $e): void
97100
$traceStr .= " <comment>$i)</end> <red>$symbol</end><comment>($args)</end>";
98101
if ('' !== $trace['file']) {
99102
$file = realpath($trace['file']);
100-
$traceStr .= "<eol/> <yellow>at $file</end><white>:{$trace['line']}</end><eol/>";
103+
$traceStr .= "<eol/> <yellow>" . t('at') . " $file</end><white>:{$trace['line']}</end><eol/>";
101104
}
102105
}
103106

@@ -185,7 +188,7 @@ protected function showHelp(string $for, array $items, string $header = '', stri
185188
$this->writer->help_header($header, true);
186189
}
187190

188-
$this->writer->eol()->help_category($for . ':', true);
191+
$this->writer->eol()->help_category(t($for) . ':', true);
189192

190193
if (empty($items)) {
191194
$this->writer->help_text(' (n/a)', true);
@@ -229,7 +232,7 @@ public function showUsage(string $usage): self
229232
$usage = str_replace('$0', $_SERVER['argv'][0] ?? '[cmd]', $usage);
230233

231234
if (!str_contains($usage, ' ## ')) {
232-
$this->writer->eol()->help_category('Usage Examples:', true)->colors($usage)->eol();
235+
$this->writer->eol()->help_category(t('Usage Examples') . ':', true)->colors($usage)->eol();
233236

234237
return $this;
235238
}
@@ -246,7 +249,7 @@ public function showUsage(string $usage): self
246249
return str_pad('# ', $maxlen - array_shift($lines), ' ', STR_PAD_LEFT);
247250
}, $usage);
248251

249-
$this->writer->eol()->help_category('Usage Examples:', true)->colors($usage)->eol();
252+
$this->writer->eol()->help_category(t('Usage Examples') . ':', true)->colors($usage)->eol();
250253

251254
return $this;
252255
}
@@ -261,11 +264,11 @@ public function showCommandNotFound(string $attempted, array $available): self
261264
}
262265
}
263266

264-
$this->writer->error("Command $attempted not found", true);
267+
$this->writer->error(t('Command %s not found', [$attempted]), true);
265268
if ($closest) {
266269
asort($closest);
267270
$closest = key($closest);
268-
$this->writer->bgRed("Did you mean $closest?", true);
271+
$this->writer->bgRed(t('Did you mean %s?', [$closest]), true);
269272
}
270273

271274
return $this;

src/Helper/Shell.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Ahc\Cli\Exception\RuntimeException;
1515

16+
use function Ahc\Cli\t;
1617
use function fclose;
1718
use function function_exists;
1819
use function fwrite;
@@ -99,7 +100,7 @@ public function __construct(protected string $command, protected ?string $input
99100
{
100101
// @codeCoverageIgnoreStart
101102
if (!function_exists('proc_open')) {
102-
throw new RuntimeException('Required proc_open could not be found in your PHP setup.');
103+
throw new RuntimeException(t('Required proc_open could not be found in your PHP setup.'));
103104
}
104105
// @codeCoverageIgnoreEnd
105106

@@ -181,7 +182,7 @@ protected function checkTimeout(): void
181182
if ($executionDuration > $this->processTimeout) {
182183
$this->kill();
183184

184-
throw new RuntimeException('Timeout occurred, process terminated.');
185+
throw new RuntimeException(t('Timeout occurred, process terminated.'));
185186
}
186187
// @codeCoverageIgnoreStart
187188
}
@@ -216,7 +217,7 @@ public function setOptions(
216217
public function execute(bool $async = false, ?array $stdin = null, ?array $stdout = null, ?array $stderr = null): self
217218
{
218219
if ($this->isRunning()) {
219-
throw new RuntimeException('Process is already running.');
220+
throw new RuntimeException(t('Process is already running.'));
220221
}
221222

222223
$this->descriptors = $this->prepareDescriptors($stdin, $stdout, $stderr);
@@ -234,7 +235,7 @@ public function execute(bool $async = false, ?array $stdin = null, ?array $stdou
234235

235236
// @codeCoverageIgnoreStart
236237
if (!is_resource($this->process)) {
237-
throw new RuntimeException('Bad program could not be started.');
238+
throw new RuntimeException(t('Bad program could not be started.'));
238239
}
239240
// @codeCoverageIgnoreEnd
240241

src/IO/Interactor.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Ahc\Cli\Output\Writer;
1616
use Throwable;
1717

18+
use function Ahc\Cli\t;
1819
use function array_keys;
1920
use function array_map;
2021
use function count;
@@ -312,7 +313,7 @@ public function choices(string $text, array $choices, $default = null, bool $cas
312313
*/
313314
public function prompt(string $text, $default = null, ?callable $fn = null, int $retry = 3): mixed
314315
{
315-
$error = 'Invalid value. Please try again!';
316+
$error = t('Invalid value. Please try again!');
316317
$hidden = func_get_args()[4] ?? false;
317318
$readFn = ['read', 'readHidden'][(int) $hidden];
318319

@@ -370,7 +371,7 @@ protected function listOptions(array $choices, $default = null, bool $multi = fa
370371
$this->writer->eol()->choice(str_pad(" [$choice]", $maxLen + 6))->answer($desc);
371372
}
372373

373-
$label = $multi ? 'Choices (comma separated)' : 'Choice';
374+
$label = t($multi ? 'Choices (comma separated)' : 'Choice');
374375

375376
$this->writer->eol()->question($label);
376377

src/Input/Command.php

+9-11
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
use Ahc\Cli\Output\Writer;
2222
use Closure;
2323

24+
use function Ahc\Cli\t;
2425
use function array_filter;
2526
use function array_keys;
2627
use function end;
2728
use function explode;
2829
use function func_num_args;
29-
use function sprintf;
3030
use function str_contains;
3131
use function strstr;
3232

@@ -83,9 +83,9 @@ public function __construct(
8383
*/
8484
protected function defaults(): self
8585
{
86-
$this->option('-h, --help', 'Show help')->on([$this, 'showHelp']);
87-
$this->option('-V, --version', 'Show version')->on([$this, 'showVersion']);
88-
$this->option('-v, --verbosity', 'Verbosity level', null, 0)->on(
86+
$this->option('-h, --help', t('Show help'))->on([$this, 'showHelp']);
87+
$this->option('-V, --version', t('Show version'))->on([$this, 'showVersion']);
88+
$this->option('-v, --verbosity', t('Verbosity level'), null, 0)->on(
8989
fn () => $this->set('verbosity', ($this->verbosity ?? 0) + 1) && false
9090
);
9191

@@ -196,7 +196,7 @@ public function argument(string $raw, string $desc = '', $default = null): self
196196
$argument = new Argument($raw, $desc, $default);
197197

198198
if ($this->_argVariadic) {
199-
throw new InvalidParameterException('Only last argument can be variadic');
199+
throw new InvalidParameterException(t('Only last argument can be variadic'));
200200
}
201201

202202
if ($argument->variadic()) {
@@ -303,9 +303,7 @@ protected function handleUnknown(string $arg, ?string $value = null): mixed
303303

304304
// Has some value, error!
305305
if ($values) {
306-
throw new RuntimeException(
307-
sprintf('Option "%s" not registered', $arg)
308-
);
306+
throw new RuntimeException(t('Option "%s" not registered', [$arg]));
309307
}
310308

311309
// Has no value, show help!
@@ -358,13 +356,13 @@ public function showDefaultHelp(): mixed
358356
$io->logo($logo, true);
359357
}
360358

361-
$io->help_header("Command {$this->_name}, version {$this->_version}", true)->eol();
359+
$io->help_header(t('Command') . " {$this->_name}, " . t('version') . " {$this->_version}", true)->eol();
362360
$io->help_summary($this->_desc, true)->eol();
363-
$io->help_text('Usage: ')->help_example("{$this->_name} [OPTIONS...] [ARGUMENTS...]", true);
361+
$io->help_text(t('Usage') . ': ')->help_example("{$this->_name} " . t('[OPTIONS...] [ARGUMENTS...]'), true);
364362

365363
$helper
366364
->showArgumentsHelp($this->allArguments())
367-
->showOptionsHelp($this->allOptions(), '', 'Legend: <required> [optional] variadic...');
365+
->showOptionsHelp($this->allOptions(), '', t('Legend: <required> [optional] variadic...'));
368366

369367
if ($this->_usage) {
370368
$helper->showUsage($this->_usage);

src/Input/Parameter.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414
use Ahc\Cli\Helper\InflectsString;
1515

16+
use function Ahc\Cli\t;
1617
use function json_encode;
1718
use function ltrim;
1819
use function strpos;
19-
use function sprintf;
2020

2121
/**
2222
* Cli Parameter.
@@ -84,7 +84,7 @@ public function desc(bool $withDefault = false): string
8484
return $this->desc;
8585
}
8686

87-
return ltrim(sprintf('%s [default: %s]', $this->desc, json_encode($this->default)));
87+
return ltrim(t('%1$s [default: %2$s]', [$this->desc, json_encode($this->default)]));
8888
}
8989

9090
/**

0 commit comments

Comments
 (0)