Last active
February 1, 2024 15:54
-
-
Save putrasurya/396d90c207412cf0f9edf1c12bb491ca to your computer and use it in GitHub Desktop.
Laravel command script to generate a translation file by leveraging OpenAI capabilities.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Console\Commands; | |
use Illuminate\Console\Command; | |
use Illuminate\Support\Facades\File; | |
class GenerateTranslation extends Command | |
{ | |
protected $signature = 'app:generate-translation'; | |
protected $description = 'Generate language translation based on en.json and resulting <lang>.json'; | |
private string $code; | |
private string $lang; | |
public function handle() | |
{ | |
$this->checkReferenceFile(); | |
$this->code = $this->getCountryCode(); | |
$this->lang = $this->getLanguage(); | |
$this->info('We will use OpenAI to generate translation output, please choose GPT model and enter API Key below to continue...'); | |
$gptModel = $this->chooseGPTModel(); | |
$openaiKey = $this->ask('OpenAI Key: '); | |
$dictionaries = $this->loadDictionaries(); | |
$ai = $this->initializeOpenAI($openaiKey); | |
$messages = $this->prepareMessages($this->lang); | |
$chunkedDictionaries = array_chunk($dictionaries, 20, true); | |
$translatedDictionaries = $this->translateDictionaries($ai, $gptModel, $messages, $chunkedDictionaries, count($dictionaries)); | |
$this->storeTranslations($this->code, $translatedDictionaries); | |
} | |
protected function checkReferenceFile() | |
{ | |
$filePath = lang_path('en.json'); | |
if (!file_exists($filePath)) { | |
throw new \Exception('Error: reference file ' . $filePath . ' does not exist'); | |
} | |
} | |
protected function getCountryCode() | |
{ | |
$country = trim($this->ask('Enter language code (example: id/en/hi/or ja, please refer to ISO 639-1): ')); | |
if ('' === $country) { | |
throw new \Exception('Language code is required.'); | |
} | |
return $country; | |
} | |
protected function getLanguage() | |
{ | |
$language = \Locale::getDisplayLanguage($this->code); | |
if ($language == $this->code) { | |
throw new \Exception('It looks like you were inputting wrong language code. Please try again.'); | |
} | |
$confirm = $this->confirm('You are going to translating to '.$language.' language, and will be stored to '.$this->code.'.json, continue? (yes|no)[yes]', true); | |
if (!$confirm) { | |
throw new \Exception('Process is terminated.'); | |
} | |
return $language; | |
} | |
protected function chooseGPTModel() | |
{ | |
return $this->choice('GPT model: ', ['gpt-3.5-turbo', 'gpt-4'], 1, null, false); | |
} | |
protected function loadDictionaries() | |
{ | |
$dict = json_decode(file_get_contents(lang_path('en.json')), true); | |
$path = lang_path(strtolower($this->code).'.json'); | |
if (!file_exists($path)) { | |
return $dict; | |
} | |
return $this->intersectDictionaries($dict, json_decode(file_get_contents($path), true)); | |
} | |
protected function intersectDictionaries($dictA, $dictB) | |
{ | |
$dict = []; | |
foreach ($dictA as $key => $value) { | |
if (array_key_exists($key, $dictB)) continue; | |
$dict[$key] = $value; | |
} | |
return $dict; | |
} | |
protected function initializeOpenAI($apiKey) | |
{ | |
return \OpenAI::factory() | |
->withApiKey($apiKey) | |
->withOrganization('') | |
->withHttpClient(new \GuzzleHttp\Client(['timeout' => 500])) | |
->make(); | |
} | |
protected function prepareMessages($language) | |
{ | |
return [ | |
['role' => 'system', 'content' => 'You will be a translation assistant.'], | |
['role' => 'system', 'content' => 'The output from the assistant will always be in JSON format. Do not use any markdown syntax here.'], | |
['role' => 'assistant', 'content' => 'Please provide me with the JSON content in which English words are key, and the targeted translation is the value.'], | |
['role' => 'assistant', 'content' => 'I will try to translate the values from your JSON content into your desired language.'], | |
['role' => 'user', 'content' => 'I want to translate into the ' . $language . ' language.'], | |
]; | |
} | |
protected function translateDictionaries($ai, $gptModel, $messages, $chunkedDictionaries, $totalDictionaries) | |
{ | |
$translatedDictionaries = []; | |
$totalTranslated = 0; | |
$this->info('Translation in progress...'); | |
foreach ($chunkedDictionaries as $chunk) { | |
$totalTranslated += count($chunk); | |
$messagesToSend = $messages; | |
$messagesToSend[] = ['role' => 'user', 'content' => json_encode($chunk)]; | |
$this->info('translating '.$totalTranslated.'/'.$totalDictionaries.'...'); | |
$response = $ai->chat()->create([ | |
'model' => $gptModel, | |
'messages' => $messagesToSend | |
]); | |
$result = ''; | |
foreach ($response->choices as $choice) { | |
$result .= $choice->message->content; | |
} | |
$jsonResult = json_decode($result, true); | |
if (null === $jsonResult) { | |
echo $result; | |
throw new \Exception('Failed. Please try again.'); | |
} | |
$translatedDictionaries = array_merge($translatedDictionaries, $jsonResult); | |
} | |
return $translatedDictionaries; | |
} | |
protected function storeTranslations($country, $translatedDictionaries) | |
{ | |
$path = lang_path(strtolower($country) . '.json'); | |
if (0 === count($translatedDictionaries)) { | |
$this->info('There is no dictionaries needed to translate.'); | |
return; | |
} | |
if (!file_exists($path)) { | |
file_put_contents($path, json_encode($translatedDictionaries, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); | |
} else { | |
$currentDictionaries = json_decode(file_get_contents($path), true); | |
$addedNewDictionaries = array_merge($currentDictionaries, $translatedDictionaries); | |
file_put_contents($path, json_encode($addedNewDictionaries, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); | |
} | |
$this->info('Done! New translation file stored at ' . $path); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment