Skip to content

Instantly share code, notes, and snippets.

@putrasurya
Last active February 1, 2024 15:54
Show Gist options
  • Save putrasurya/396d90c207412cf0f9edf1c12bb491ca to your computer and use it in GitHub Desktop.
Save putrasurya/396d90c207412cf0f9edf1c12bb491ca to your computer and use it in GitHub Desktop.
Laravel command script to generate a translation file by leveraging OpenAI capabilities.
<?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