A Multilingual Prestashop Online Store Using ChatGPT
Often when expanding the client base geographically, online stores need to hire translators to translate the UI interface of the online store.
Join the DZone community and get the full member experience.
Join For FreeToday, we will look at the example of the free engine Prestashop to translate content using the generative neural network ChatGPT4.
For the example, we will use Prestashop version 1.7.8.6.
First, in the /modules/ directory, we'll create a directory with the name of our module; I'll name my module "productlocal."
In this directory, create a .php file with the name of our module. In my case, it is: productlocal.php.
In this file, we will create the module class:
<?php
class productlocal extends Module
{
public function __construct()
{
$this->name = 'productlocal';
$this->tab = 'administration';
$this->version = '0.1';
$this->author = 'sinenko';
$this->need_instance = 0;
$this->ps_versions_compliancy = ['min' => '1.7.8.6', 'max' => '1.7.8.6'];
$this->bootstrap = true;
$this->controllerAdmin = 'AdminProductlocal';
$this->api_key = Configuration::get('GPT4INTEGRATION_API_KEY'); // API key
$this->model = Configuration::get('GPT4INTEGRATION_MODEL'); // GPT model
parent::__construct();
$this->displayName = 'ChatGPT products description translation';
$this->description = 'Translates product descriptions by ChatGPT';
$this->confirmUninstall = 'Are you sure you want to uninstall?';
}
public function install()
{
return parent::install();
}
public function uninstall()
{
return parent::uninstall();
}
This is a template class for a PrestaShop module. In the class constructor, we added the parameters "api_key
" and "model
," where the API key from OpenAI and the version of the chosen ChatGPT model will be stored. If you have access to ChatGPT 4, set the value of the variable $this->model = “gpt-4”
. If you don't have access, use the "gpt-3.5-turbo" model. It also translates text well and is much cheaper.
Prices:
GPT-3.5-turbo $0.002/1,000 tokens
GPT-4 $0.06/1,000 tokens of prompts $0.12/1,000 tokens of response
To obtain an API key, you need to go to this link and register.
After registration, you need to create a new API key and save it. This will be specified in the settings of the future module.
After creating the key, you can top up your OpenAI balance. If you are using the GPT-3.5-turbo model, $5 should be enough to translate about 600-1,000 products, depending on the length of the description.
To make a request to the API, we need to send a post request in JSON format to the following URL of the following type:
{
"model": "gpt-4", // Selected GPT model
"messages": [
{
"role": "system", // Role of the assistant
"content": "You must in this chat translates all text into Hindi" // Text of the request
},
{
"role": "user", // Role of the assistant
"content": "This is a test text with some kind of product description in English" // Text of the request
}
],
"max_tokens": 2000, // Maximum number of tokens in the response
"n": 1, // Number of text variants
"temperature": 0 // Temperature (creative component)
}
Let's get back to writing the module. We will make it so that you can translate the description directly while editing the product.
To test our module, I will add several languages to my Prestashop. To do this, I will go to the menu “International->Localization->India” and then click on the “Import” button.
After that, the languages Tamil, Hindi, and Bengali will be added to your system.
Now, when editing a product, you have a choice of several languages:
Now we need to add a button to the product editing interface. When this button is pressed, our module will send a request to the OpenAI API. First, let's design our button. In the directory with your module, create a .tpl template file. I will call it translate_button.tpl.
After that, add the following code:
<button id="translate-button" data-product-id="{$id_product|escape:'html':'UTF-8'}">Translate description</button>
<script>
$('#translate-button').click(function() {
this.disabled = true; //Disable the button
var id_product = {$id_product}; //Get the product id
var language_code = $('#form_switch_language').val(); //Get the selected language code
$.post('{$controller_url}&action=translateDescription', {
id_product: id_product,
language_code: language_code
}, function(response) {
alert(response); //Display the returned data in browser
});
});
</script>
This is a simple button that, when pressed, sends a request to the controller's address (handler) with the product id and the selected language, after which the controller provides a response. We don't have a controller yet; we will create it a bit later. It will take id_product
and language_code
, send the product description to ChatGPT, then save the received data and provide a response.
Now, in order for this button to appear on the product editing page, we need to add a hook to our module class that will be called when opening the product editing page.
For this, in the productlocal.php file, in the productlocal
class, add the following method:
public function hookDisplayAdminProductsExtra($params)
{
$this->context->smarty->assign(array(
'controller_url' => $this->context->link->getAdminLink($this->controllerAdmin), //Give the url for ajax query
'id_product' => $params['id_product'],//give the url for ajax query
));
return $this->display(__FILE__, 'translate_button.tpl');
}
But that's not enough. To properly set access rights to our module for different users, we need to create a new tab (menu item in the admin panel) for our module, and it's not necessary for this tab to be displayed.
To create a new tab when installing the module, you need to add the following methods to the productlocal
class:
public function installTab()
{
$tab = new Tab();
// Controller name must be the same as the name of the controller file
$tab->class_name = $this->controllerAdmin;
$tab->active = 0;
$tab->name = array();
foreach (Language::getLanguages(true) as $lang) {
$tab->name[$lang['id_lang']] = $this->name;
}
$tab->id_parent = -1;
$tab->module = $this->name;
return $tab->add();
}
public function uninstallTab()
{
$id_tab = (int)Tab::getIdFromClassName($this->controllerAdmin);
$tab = new Tab($id_tab);
if (Validate::isLoadedObject($tab)) {
return $tab->delete();
}
}
We also now need to modify the install and uninstall methods:
public function install()
{
return parent::install() && $this->installTab() &&
$this->registerHook('displayAdminProductsExtra');
}
public function uninstall()
{
return parent::uninstall() && $this->uninstallTab();
}
Now, after installing our module, on the product editing page, we should see our module's hook:
Click on the "configure" button, and after that, our button is displayed:
Great, we have the button. On click, JS sends a request to the controller. Now we need to create the controller. If you were attentive, you would have noticed at the beginning of the article that in the constructor code of our module class, I created a parameter:
$this->controllerAdmin = 'AdminProductlocal';
AdminProductlocal
will be the name of our controller. I also specified this controller in the code when creating the tab.
To create a new controller for the admin panel, we need to create a directory controllers/admin/ in the directory with our module.
In this directory, we create a file with the name of our controller, AdminProductlocalController.php. Note that the controller file should be named the same as the controller name but with the word "Controller" added at the end.
The code for our controller will be:
<?php
class AdminProductlocalController extends ModuleAdminController
{
public function __construct()
{
// Name of the module folder
$this->module = 'productlocal';
$this->bootstrap = true;
parent::__construct();
}
public function postProcess()
{
// Check if action is translateDescription
if (Tools::isSubmit('action') && Tools::getValue('action') == 'translateDescription') {
// Get id_product and language_code from ajax request
$id_product = Tools::getValue('id_product');
$language_code = Tools::getValue('language_code');
// Get instance of module
$module = Module::getInstanceByName('productlocal');
// Translate description with translateDescription method and return response
$response = $module->translateDescription($id_product, $language_code);
die($response);
}
}
}
Now, in our module class, all that remains is to create the translateDescription
method, which will take id_product
and language_code
as input parameters, translate the product description, and return the response.
But first, let's create a method that will exchange data with the OpenAI API. Let's make it so that this method takes the text, text type, and language to translate it into inputs and returns the translated text as output.
To translate the product name, we will use the following prompt request to ChatGPT:
Messages with name of product of online will be sent to this chat. They need to be translated into $language. If the language is unknown, simply respond with: {not-found}. If the message is already in $language, the response will be {already-done} |
And for translating the short and full product description, we will use the following prompt request to ChatGPT:
Messages with зкщвгсе вуыскшзешщт of online in HTML format will be sent to this chat. They need to be translated into $language, keeping the HTML formatting in its original form. If there are no HTML tags in the text, then they are not needed. If the language is unknown, simply respond with: {not-found}. If the message is already in $language, the response will be {already-done} |
We specifically ask ChatGPT to respond with {not-found}
or {already-done}
in case of errors so that it will be easier for us to process incorrect answers later.
const PRODUCT_LOCAL_DESCRIPTION = 'description of product';
const PRODUCT_LOCAL_SHORT_DESCRIPTION = 'short description of product';
const PRODUCT_LOCAL_NAME = 'name of product';
public function requestTranslateToChatGPT($description, $language, $type = self::PRODUCT_LOCAL_DESCRIPTION){
$api_key = $this->api_key;
$model = $this->model;
if (!$api_key || empty($api_key) || !$model || empty($model)) {
return null;
}
$url = 'https://api.openai.com/v1/chat/completions'; // URL GPT-4 API
// Prompt for ChatGPT, if type of translation is name of product, then prompt for ChatGPT is different, withot HTML tags and most sho
if($type == self::PRODUCT_LOCAL_NAME){
$prompt = 'Messages with '.$type.' of online will be sent to this chat. They need to be translated into '.$language.'. If the language is unknown, simply respond with: {not-found}. If the message is already in '.$language.', the response will be {already-done}';
} else {
$prompt = 'Messages with '.$type.' of online in HTML format will be sent to this chat. They need to be translated into '.$language.', keeping the HTML formatting in its original form. If there are no HTML tags in the text, then they are not needed. If the language is unknown, simply respond with: {not-found}. If the message is already in '.$language.', the response will be {already-done}';
}
$data = [
'model' => $model, // Selected GPT model
'messages' => [
[
'role' => 'system',
'content' => $prompt // Text of the request
],
[
'role' => 'user',
'content' => $description // Text for translate
],
],
'max_tokens' => 2000, // Maximum number of tokens in the response
'n' => 1, // Number of text variants
'temperature' => 0 // Temperature (creative component)
];
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $api_key
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$response_data = json_decode($response, true);
$result=null;
if(isset($response_data['error'])){
$result['error'] = $response_data['error']['message'];
}
if (isset($response_data['choices'][0]['message'])) {
// If the language is unknown, simply respond with: {not-found}
if ($response_data['choices'][0]['message']['content'] !== '{not-found}') {
// If the message is already in the selected language, the response will be {already-done}
if ($response_data['choices'][0]['message']['content'] !== '{already-done}') {
$result['content'] = $response_data['choices'][0]['message']['content'];
} else {
if(self::PRODUCT_LOCAL_NAME == $type){
$result['content'] = $description;
}
}
} else {
$result['error'] = 'Language not found';
}
}
return $result;
}
Now our method takes the text and language as input and, depending on the text type, constructs a prompt and sends a request to the API. It then returns the translated text.
It's time for the final and main method to which our controller refers, namely translateDescription
.
This method will take the id_product
of the edited product and the language, send translation requests for the product name and short and full descriptions using the requestTranslateToChatGPT
method, then replace the name, save the product, and provide a result of its operation to our controller. Alternatively, it will return an error in response.
The code for the translateDescription
method in our case will look something like this:
public function translateDescription($id_product, $language_code)
{
$product = new Product((int)$id_product);
$selectedLanguage = null;
$languages = Language::getLanguages(true, $this->context->shop->id); // Get all enabled languages for this shop
foreach($languages as $language) {
if ($language['iso_code'] == $language_code) {
$selectedLanguage = $language;
}
}
$default_lang_id = (int)Configuration::get('PS_LANG_DEFAULT'); // Get default language
// Get product description and translate it, if an error occurs, return the error message
$description = $product->description[$default_lang_id];
$description = $this->requestTranslateToChatGPT($description, $selectedLanguage['name'], self::PRODUCT_LOCAL_DESCRIPTION);
if (isset($description['error'])) {
return 'Description ERROR: '.$description['error'];
}
$product->description[$selectedLanguage['id_lang']] = $description['content'];
//Get product short description and translate it, if an error occurs, return the error message
$description_short = $product->description_short[$default_lang_id];
$description_short = $this->requestTranslateToChatGPT($description_short, $selectedLanguage['name'], self::PRODUCT_LOCAL_SHORT_DESCRIPTION);
if (isset($description_short['error'])) {
return 'Short description ERROR: '.$description_short['error'];
}
$product->description_short[$selectedLanguage['id_lang']] = $description_short['content'];
//Get product name and translate it, if an error occurs, return the error message
$name = $product->name[$default_lang_id];
$name = $this->requestTranslateToChatGPT($name, $selectedLanguage['name'], self::PRODUCT_LOCAL_NAME);
if (isset($name['error'])) {
return 'Name ERROR: '.$name['error'];
}
$product->name[$selectedLanguage['id_lang']] = $name['content'];
$product->update();
return 'Product successfully translated';
}
Our module is ready to go.
There's a small but optional touch left: we can add the ability to configure and change the parameters of our module via the admin panel, specifically the api_key
and model
. To do this, we need to add a couple of methods to our module: displayForm
— which displays the form for editing parameters, and the getContent
method, which saves the parameters when edited.
public function getContent()
{
$output = null;
if (Tools::isSubmit('submit'.$this->name)) {
$api_key = strval(Tools::getValue('GPT4INTEGRATION_API_KEY'));
if (!$api_key || empty($api_key)) {
$output .= $this->displayError($this->l('Invalid API key'));
} else {
Configuration::updateValue('GPT4INTEGRATION_API_KEY', $api_key);
$output .= $this->displayConfirmation($this->l('Settings updated'));
}
if (!Tools::getValue('GPT4INTEGRATION_MODEL')) {
$output .= $this->displayError($this->l('Invalid model'));
} else {
Configuration::updateValue('GPT4INTEGRATION_MODEL', strval(Tools::getValue('GPT4INTEGRATION_MODEL')));
}
}
return $output.$this->displayForm();
}
public function displayForm()
{
// Get default language
$default_lang = (int)Configuration::get('PS_LANG_DEFAULT');
// Form fields
$fields_form[0]['form'] = [
'legend' => [
'title' => $this->l('Settings'),
],
'input' => [
[
'type' => 'text',
'label' => $this->l('API key'),
'name' => 'GPT4INTEGRATION_API_KEY',
'size' => 64,
'required' => true
],
[
'type' => 'select',
'label' => $this->l('Model'),
'name' => 'GPT4INTEGRATION_MODEL',
'required' => true,
'options' => [
'query' => [
['id_option' => 'gpt-4', 'name' => 'gpt-4'],
['id_option' => 'gpt-3.5-turbo', 'name' => 'gpt-3.5-turbo'],
],
'id' => 'id_option',
'name' => 'name'
],
],
],
'submit' => [
'title' => $this->l('Save'),
'class' => 'btn btn-default pull-right'
],
];
$helper = new HelperForm();
// Module, token and currentIndex
$helper->module = $this;
$helper->name_controller = $this->name;
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name;
// Language
$helper->default_form_language = $default_lang;
$helper->allow_employee_form_lang = $default_lang;
// Title and toolbar
$helper->title = $this->displayName;
$helper->show_toolbar = true;
$helper->toolbar_scroll = true;
$helper->submit_action = 'submit'.$this->name;
$helper->toolbar_btn = [
'save' => [
'desc' => $this->l('Save'),
'href' => AdminController::$currentIndex.'&configure='.$this->name.'&save'.$this->name.
'&token='.Tools::getAdminTokenLite('AdminModules'),
],
'back' => [
'href' => AdminController::$currentIndex.'&token='.Tools::getAdminTokenLite('AdminModules'),
'desc' => $this->l('Back to list')
]
];
// Load current value
$helper->fields_value['GPT4INTEGRATION_API_KEY'] = Configuration::get('GPT4INTEGRATION_API_KEY');
// Load current value
$helper->fields_value['GPT4INTEGRATION_MODEL'] = Configuration::get('GPT4INTEGRATION_MODEL');
return $helper->generateForm($fields_form);
}
Now, as a test, let's install our module, select the Hindi language, and click on the "Translate Description" button. After a long wait, you will see the message "Product successfully translated."
Now you need to refresh the page. And after refreshing, you will see the translated description.
If you don't want to refresh the page each time manually, you can add the JS code for refreshing, location.reload();
, to the translate_button.tpl
file.
$.post('{$controller_url}&action=translateDescription', {
id_product: id_product,
language_code: language_code
}, function(response) {
alert(response); //Display the returned data in browser
location.reload();
});
That's it. And as always, you can expand the functionality of your module by, for instance, having ChatGPT check your text for errors or change the description to be shorter or longer. For this, you only need to modify the prompt requests.
You can see the complete code of the module in the GitHub repository.
Opinions expressed by DZone contributors are their own.
Comments