Files
nexusphp/app/Console/Commands/TranslateLang.php

275 lines
9.3 KiB
PHP
Raw Normal View History

2025-04-17 01:39:40 +07:00
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Stichoza\GoogleTranslate\GoogleTranslate;
2025-04-20 18:13:46 +07:00
/**
* use google translate
*/
2025-04-17 01:39:40 +07:00
class TranslateLang extends Command
{
2025-04-19 02:06:51 +07:00
protected $signature = 'lang:translate {runEnv} {source} {target}
2025-04-17 01:39:40 +07:00
{filename? : Optional file to translate (php file or json)}
{--dry-run : Only print translations without writing files}
{--ignore= : Comma-separated keys to ignore (e.g. key1,key2)}
{--json : Also translate JSON language files}';
protected $description = 'Translate Laravel language files (PHP or JSON) using Google Translate';
protected $tr;
protected $ignoreKeys = [];
protected $cache = [];
protected $cachePath;
2025-04-19 02:06:51 +07:00
protected $langPath;
protected $runEnv;
const RUN_ENV_NEXUS = 'nexus';
const RUN_ENV_LARAVEL = 'laravel';
private static array $runEnvToLangPathMaps = [
self::RUN_ENV_NEXUS => ROOT_PATH . "lang",
self::RUN_ENV_LARAVEL => ROOT_PATH . "resources/lang",
];
2025-04-17 01:39:40 +07:00
public function handle()
{
$source = $this->argument('source');
$target = $this->argument('target');
$filename = $this->argument('filename');
2025-04-19 02:06:51 +07:00
$runEnv = $this->runEnv = $this->argument('runEnv');
2025-04-17 01:39:40 +07:00
$this->ignoreKeys = array_filter(explode(',', $this->option('ignore')));
$this->cachePath = storage_path("framework/lang-translate-cache.{$source}.{$target}.json");
$this->loadCache();
$this->tr = new GoogleTranslate();
2025-04-19 02:06:51 +07:00
$supportLanguages = $this->tr->languages();
if (!in_array($source, $supportLanguages)) {
$this->error("source $source is not supported by Google Translate");
return 1;
}
if (!in_array($target, $supportLanguages)) {
$this->error("target $target is not supported by Google Translate");
return 1;
}
2025-04-17 01:39:40 +07:00
$this->tr->setSource($source);
$this->tr->setTarget($target);
2025-04-19 02:06:51 +07:00
$langPath = $this->langPath = $this->getLangPath($runEnv);
2025-04-17 01:39:40 +07:00
2025-04-19 02:06:51 +07:00
//谷歌使用的是 - 本地按照 ISO 15897 标准使用 _
2025-04-17 01:39:40 +07:00
$source = str_replace("-", "_", $source);
$target = str_replace("-", "_", $target);
2025-04-19 02:06:51 +07:00
$realSourceDir = $source;
if ($runEnv == self::RUN_ENV_NEXUS) {
$realSourceDir = $this->langToDirName($source);
}
$sourceDir = "{$langPath}/" . $realSourceDir;
2025-04-17 01:39:40 +07:00
if ($filename) {
// 👇 指定具体文件翻译
2025-04-19 02:06:51 +07:00
$this->translateSpecificFile($filename, $realSourceDir, $target);
2025-04-17 01:39:40 +07:00
} else {
// 👇 未指定时,用户确认是否翻译所有文件
2025-04-19 02:06:51 +07:00
$answer = $this->ask("你没有指定文件名,是否翻译目录 $sourceDir 下所有语言文件到 $target ?请输入 yes 确认");
2025-04-17 01:39:40 +07:00
if (strtolower($answer) === 'yes') {
2025-04-19 02:06:51 +07:00
foreach (File::files($sourceDir) as $file) {
2025-04-17 01:39:40 +07:00
if ($file->getExtension() === 'php') {
$this->translatePhpFile($file->getPathname(), $source, $target);
}
}
if ($this->option('json')) {
$jsonFile = "{$langPath}/{$source}.json";
if (file_exists($jsonFile)) {
$this->translateJsonFile($jsonFile, $source, $target);
}
}
} else {
// 👇 用户的输入被当作 filename 处理
$this->translateSpecificFile($answer, $source, $target);
}
}
$this->saveCache();
$this->info("🎉 $source => $target Done !");
return 0;
}
protected function translatePhpFile($sourceFile, $sourceLang, $targetLang)
{
$relativePath = basename($sourceFile);
2025-04-19 02:06:51 +07:00
$targetFile = $this->langPath . "/{$targetLang}/{$relativePath}";
if ($this->runEnv == self::RUN_ENV_LARAVEL) {
$data = require $sourceFile;
} else {
require $sourceFile;
$var = str_replace([".php", "lang_", "-"], "", $relativePath);
$var = "lang_$var";
$data = $$var;
}
2025-04-17 01:39:40 +07:00
$translated = $this->translateArray($data);
$export = var_export($translated, true);
if ($this->option('dry-run')) {
$this->line("🔍 Would write to: $targetFile\n$export\n");
} else {
if (!file_exists(dirname($targetFile))) {
mkdir(dirname($targetFile), 0755, true);
}
2025-04-19 02:06:51 +07:00
if ($this->runEnv == self::RUN_ENV_LARAVEL) {
$contents = "<?php\n\nreturn $export;\n";
} else {
$contents = sprintf("<?php\n\n$%s = %s;\n", $var, $export);
}
file_put_contents($targetFile, $contents);
2025-04-17 01:39:40 +07:00
$this->info("✅ Wrote translated file: $targetFile");
}
}
protected function translateJsonFile($jsonFile, $sourceLang, $targetLang)
{
2025-04-19 02:06:51 +07:00
// $targetFile = resource_path("lang/{$targetLang}.json");
$targetFile = $this->langPath . "/{$targetLang}/{$jsonFile}";
2025-04-17 01:39:40 +07:00
$content = json_decode(file_get_contents($jsonFile), true);
$translated = [];
foreach ($content as $key => $value) {
if (in_array($key, $this->ignoreKeys)) {
$translated[$key] = $value;
continue;
}
$translated[$key] = $this->translateText($value);
}
$pretty = $this->json_encode_pretty($translated);
if ($this->option('dry-run')) {
$this->line("🔍 Would write to: $targetFile\n$pretty\n");
} else {
file_put_contents($targetFile, $pretty);
$this->info("✅ Wrote translated JSON: $targetFile");
}
}
protected function translateArray(array $data)
{
$result = [];
foreach ($data as $key => $value) {
if (in_array($key, $this->ignoreKeys)) {
$result[$key] = $value;
continue;
}
if (is_array($value)) {
$result[$key] = $this->translateArray($value);
} else {
$result[$key] = $this->translateText($value);
}
}
return $result;
}
protected function translateText(string $text): string
{
if (isset($this->cache[$text])) {
$this->line("⚡️ Cached: $text => {$this->cache[$text]}");
return $this->cache[$text];
}
try {
$translated = $this->tr->translate($text);
$this->cache[$text] = $translated;
$this->line("🌍 $text => $translated");
return $translated;
} catch (\Exception $e) {
$this->warn("❌ Failed to translate: $text");
return $text;
}
}
protected function translateSpecificFile($filename, $source, $target)
{
2025-04-19 02:06:51 +07:00
$langPath = $this->langPath;
2025-04-17 01:39:40 +07:00
if (str_ends_with($filename, '.json')) {
$jsonPath = "{$langPath}/{$filename}";
if (!file_exists($jsonPath)) {
$jsonPath = "{$langPath}/{$source}.json";
}
if (file_exists($jsonPath)) {
$this->translateJsonFile($jsonPath, $source, $target);
} else {
2025-04-19 02:06:51 +07:00
$this->error("❌ JSON 文件未找到:$jsonPath");
exit();
2025-04-17 01:39:40 +07:00
}
} else {
$phpPath = "{$langPath}/{$source}/$filename";
if (!str_ends_with($filename, '.php')) {
$phpPath .= '.php';
}
if (file_exists($phpPath)) {
$this->translatePhpFile($phpPath, $source, $target);
} else {
2025-04-19 02:06:51 +07:00
$this->error("❌ PHP 语言文件未找到:$phpPath");
exit();
2025-04-17 01:39:40 +07:00
}
}
}
protected function loadCache()
{
if (file_exists($this->cachePath)) {
$this->cache = json_decode(file_get_contents($this->cachePath), true);
}
}
protected function saveCache()
{
if (!$this->option('dry-run')) {
file_put_contents($this->cachePath, json_encode($this->cache, JSON_UNESCAPED_UNICODE));
}
}
protected function json_encode_pretty($data, int $indentSize = 4): string
{
// 默认格式化PHP 默认是 2 空格)
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// 自定义缩进
$indentChar = str_repeat(' ', $indentSize);
// 将默认的 2 空格缩进替换为自定义缩进
$formatted = preg_replace_callback('/^( +)/m', function ($matches) use ($indentChar) {
$level = strlen($matches[1]) / 2;
return str_repeat($indentChar, (int)$level);
}, $json);
return $formatted;
}
2025-04-19 02:06:51 +07:00
private function langToDirName($lang): string
{
$map = [
'zh_CN' => 'chs',
'zh_TW' => 'cht',
];
return $map[$lang] ?? $lang;
}
private function getLangPath($runEnv): string
{
if (isset(self::$runEnvToLangPathMaps[$runEnv])) {
return self::$runEnvToLangPathMaps[$runEnv];
} else {
throw new \InvalidArgumentException("invalid runEnv: $runEnv, allowed values: " . implode(', ', array_keys(self::$runEnvToLangPathMaps)));
}
}
2025-04-17 01:39:40 +07:00
}