Translating or internationalizing an Ubercart store: Common problems & solutions

Translating or internationalizing an Ubercart store: Common problems & solutions

Posted on Tue, 2009/07/28 - 3:07am

Although Drupal 6 handles node translation with the i18n module very well, Ubercart seems to have a bit of difficulty with it. I believe that most of the problems are due to the nature of translated nodes, which are actually completely different nodes that are glued together with some metadata. This fact, for example, is the reason Ubercart will create a product translation as a new product separate from the original.

As I tried to setup a multi-lingual store (English/French) store with Ubercart, I encountered many problems that seemed to be all over Google, but had no resolution posted or the solutions to the problems were fragmented across different sites and support threads. So after a lot of searching, reading PHP backtraces and hacking the Ubercart core modules, I was finally able to have a good Ubercart catalog set up in two languages! I've compiled a list of problems and their solutions below if you are also having problems. Feel free to use any part or all of the code samples on your site.

Note: The code in this tutorial has been confirmed to work with Ubercart 2.2. It should also work with future versions, but may require some minor tweaks.

Problem: "add to cart" and other form buttons need to be translated

Ubercart handles form button texts like "add to cart" via multi-lingual variables. Multi-lingual variables differ from strings that use Drupal's t() function in that they will not appear in the site's Translate Interface utility. Instead, multi-lingual store a value for each of the site's languages and load the appropriate string depending on which language the user is currently browsing in.

In the case of "add to cart", this simply means that one must browse to Administer > Store Administration > Configuration > Product Settings and click on the Edit button. Switch the site language to the desired language and enter new values in the "Add to cart button text" configuration box. Once the values have been saved, you will be able to switch to a different language and enter new values for that language.

Problem: Ubercart does not synchronize product data across translated nodes (product information needs to be updated for each translation)

As I mentioned in the introduction, this is due to the fact that each translation is in fact a node to itself. Since Drupal assigns each node with content type "product" the standard Ubercart attribute set, each product node must be updated individually. Fortunately, there is a quick and easy was to solve this by creating a custom module:

Enable the i18nsync ("Synchronize Translations") module in Administer > Site building > Modules

Create a custom module which will hook into i18nsync:

Create the folder sites/all/modules/custommod

Paste the following into sites/all/modules/custommod/custommod.info:

name = Custom moduledescription = A custom module which helps with the translation of an Ubercart storecore = 6.x

Under Workflow Settings > Synchronize translations, select all fields you would like to synchronize. However, do not select the Taxonomy terms option (see below for why)

Repeat this step for any other product classes/content types whose product data you want synchronized

That's all! If you edit a node in one language, you should see the updated product data in all other languages as well. The only caveat is that product attributes and product options will still need to be added and updated on each translation.

Thanks to user gupa on Ubercart support forums for posting this fix (see reference 1)

Problem: Taxonomy terms are untranslated in Catalog block

This one had me puzzled for a long time. At first, I had enabled localized terms for the Catalog vocabulary. After I created some terms, I translated them using the Translate Interface tool as usual, but this did not work. Editing a product would show the localized term in the Catalog list, however viewing the product always resulted in the untranslated term being displayed on the Catalog block. The solution is to navigate to Administer > Content management > Taxonomy and edit the Catalog vocabulary, choosing Per language terms. Use the following procedure to define taxonomy term translations:

Browse to Administer > Content management > Taxonomy

Add a new term with its translations:

Add a new term to the Catalog vocabulary in the default language of your store and choose that language from the drop-down menu

Repeat the above step, but enter the desired translated texts for the term (including a translated term name)

Repeat the above step, but enter the desired translated texts for the term (including a translated term name)

Click on the Translation tab of the taxonomy interface

Click Create new translation and pair up the original term with its translations

Repeat the step above for each term that you created earlier.

This is why you do not want to synchronize taxonomy terms, with this setup each term is translated the same way nodes are - separate terms per node, linked together with some metadata. Synchronizing taxonomy terms would result in your products appearing in the right categories but in the wrong languages!

Problem: Adding items to the cart in one language, then switching to another language does not localize the cart contents

This is an unusual situation, but it's a very annoying problem; adding an item to the cart, switching languages and then click on that same item in the cart will open the product in first language, not the one the customer is browsing your website in. To solve this, hook_cart_item() will be used to override the default cart rendering with our own version which will replace node with translated nodes. Open the custom module file, sites/all/modules/custommod/custommod.module, which was created earlier and add before the closing ?> tag:

<?php/** * Implementation of hook_add_to_cart(). */function custommod_add_to_cart($nid, $qty, $data) {/* Due to Drupal's use of multiple nodes for product translations, the same * product in a different language is treated as a different product entirely. * This is problematic as the same product in different languages can be added * to the cart simultaneously. This function works around that problem by * always using the tnid/original node. As a result, the cart must be * localized as it is displayed. */$node = node_load($nid);// Determine if this node is the source node or a translated one // Remember: tnid is 0 if there are no translations$is_source = ($node->nid == $node->tnid || $node->tnid == 0) ? 1 : 0; if ($is_source) {// If it is the source, then all is well…$result[] = array('success' => TRUE); } else {/* If we are not the source node, then fail to add this product silently and * call uc_cart_add_item() to add the source node's product instead. It will * be localized later - see custommod_cart_item() */uc_cart_add_item($node->tnid, $qty, $data);$result[] = array('success' => FALSE, 'silent' => TRUE); }// Remember: We need an array in an array herereturn $result;}

/** * Implementation of hook_cart_item(). */function custommod_cart_item($op, &$item) {/* hook_cart_display() isn't really a hook, it's mostly for internal use. * However, we do need to access later. Setting $item->module forces * a module_invoke() call in uc_cart.module to call custommod_cart_display() * instead of the default uc_product_cart_display(). We will call * uc_product_cart_display() inside our function to ensure things work as * usual in future versions. */$item->module = "custommod";/* Note that although it is possible to use check for case 'load' in $op and * then override the $item->nid and $item->title values, this will cause bugs * when attempting to add or remove products in different languages. To * resolve these bugs, we are forcing the use of custommod_cart_display() and * rewriting the code for the title, img, and anchors to localize the cart. */}/** * Implementation of hook_cart_display(). */function custommod_cart_display($item) {/* Call uc_product_cart_display() to get things setup as usual and to ensure * this hack still works even if uc_product_cart_display changes at some point * in the future. */$display_item = uc_product_cart_display($item);// Get the translations, if any.$node = node_load($item->nid); global $language;$translations = translation_node_get_translations($node->tnid); if ($translations[$language->language]) {// Reminder: NEVER override the nid. That is what causes the bugs!$tnode = node_load($translations[$language->language]->nid);$display_item["title"]["#value"] = node_access('view', $tnode) ? l($tnode->title, 'node/'. $tnode->nid) : check_plain($tnode->title);$display_item["image"]["#value"] = uc_product_get_picture($tnode->nid, 'cart'); } return $display_item;}?>

After reloading the Custom module at Administer > Site building > Modules, the cart should behave properly when switching languages. As well, adding a product to the cart in one language, switching languages, then adding it again should not result in two different products being added to the cart. Instead, the quantity of the product will increase by 1.

Credit for this solution goes to user Docc at Ubercart forums, who posted the code sample (see reference 3)

Problem: Ubercart does not localize products during checkout

This one was a bit trickier to solve, since there's no real elegant way to trick ubercart into localizing the products while using the standard checkout pane. As a result, we will have to disable the stock checkout pane and use the replacement provided by the code below instead (the replacement pane is called "Your order").

<?php/** * Implementation of hook_checkout_pane(). */function custommod_checkout_pane() {/* Replacement for standard cart contents pane. Although hook_cart_item() can * be used to localize the checkout pane, then we get into trouble while * trying to localize the cart display (see the comments above). The best way * that I can think of to work around this is to disable the stock cart * contents pane and enable this one instead. */$panes[] = array('id' => 'custommod_cart','callback' => 'custommod_checkout_pane_custommod_cart','title' => t('Your order'),'desc' => t('Display the (localized) contents of a customer\'s shopping cart.'),'weight' => 2,'process' => TRUE,'collapsible' => FALSE, ); return $panes;}

Comments

I was wondering how to implement your solution. I made a new module with your code and I enabled the module. But if I translate a product it doesn't synchronize the attributes and options. I guess I'm doing something wrong :)

Have you installed the i18n module? You can get it here: http://drupal.org/project/i18n
"custommod" (or whatever you chose to name the module) requires the i18n module to perform many of the translations. Once you enable i18n, everything should start working.

Thank you mm_mike - I can confirm that this works perfectly. If anyone else is looking to implement this on their site, just remember that the function name should be yourmodule_nodeapi since the hook it uses is hook_nodeapi.

To the original author - I'm sure it would help a lot of people if this code was added to the main body of the article somewhere.

Thank you to you both, the article and comments have save me a lot of time!

I've got an addition - to translate "Add to cart" for product nodes (both teasers and full view) you also need to make the t() trick in uc_product_add_to_cart_form() for the uc_product_add_to_cart_text variable
...'#value' => t(variable_get('uc_product_add_to_cart_text', 'Add to cart'))...

I am using UC 1.5-2.10 and found that the 'review' switch option in custommod_checkout_pane_custommod_cart() was not called. The only options called are 'view' and 'theme'.

To still be able to localize the product list, I took some of the code in the 'review' branch of custommod_checkout_pane_custommod_cart() and created my own theming function in my template.php file. The code below is an adjusted version of the theme_cart_review_table() function in ubercart/uc_cart/uc_cart_checkout_pane.inc. With this solution, I did not need to create a new pane 'Your order'.

Before this function is used by UC/D6, I did a 'clear cache' and visited the template admin screen to refresh everything.

I came to the conclusion that the fields that need synchronization are a bit different. The length, width, and height fields have different names. The custommod_i18nsync_fields_alter() function needs the following array:

I am using Ubercart 2.2 & the dimension doesn't sync, I found that the variable names for length, width and height are different from your codes.
So for everybody got problem w/ sync in dimension fields, pls try to change below code in custommod.module from

This is one of the grestest post I've come across. Being a one-year-old drupal developper & not a programmer (I can barely read & write code), this kind of information in Manna from heaven! Makes me feel smart. That's how good it is. KUDOS TO ALL!

I see that most .module files do not include the closing "?>" tag.
I read it's part of best practices.
Is it required for the custommod module listed above or not?

It's not strictly required and in fact, the Zend framework even makes it prohibited! The reason is that if you include the closing tag in a PHP file, any text after it (such as line breaks) get parsed as text. So to avoid unwanted line breaks in output, it is recommended to omit the closing PHP tag.

I've just noticed that the invoice emails that UC sends out are mostly translated but the section that lists the products (the line items section) is not translated and the product titles are always in English.

The strings in the Ubercart invoices are using the t() function, so create a test order to get an invoice sent and then you will be able to translate the strings in the invoice via the Translate Interface tool. You may want to check out localize.drupal.org as well, as many users have already uploaded translated strings and it's just a matter of copy/pasting them into your installation.

Not at the moment, however it should be fairly easy to achieve. custommod_i18nsync_fields_alter() synchronizes other product attributes, so it would be a matter of either extending this function or using another [ubercart] hook to perform the same action and synchronize stock values across translated nodes.

It depends on what behaviour you're look for as well - you can do the one updates all approach where setting the stock on a product in any language will update all of the node's other translations immediately, or you can have one language serve as the primary one and product stocks are always using its value, ignoring their own.

Firstly - thank you so much for centralising all this great information and advice. It is proving to be really helpful.

I have implemented the code you list above but seem to be having a problem with some (not all) of the cart-related code. Basically, the '/cart' localizes correctly. I can cross between languages, and it shows the other version of the item in the cart properly (i.e. translated). I can add the same item to the cart in both languages and it displays the sum of the two separate additions in the selected languages as one item. So that bit is spot on.

When I move to the 'cart/checkout' page the cart item list is not translated into the associated language, or in my case, it just stays in English. If I then advance to the 'cart'checkout/review' page however, it is localized correctly.

I am using UC 2.4, btw.

Just wondered which function I should be looking at to try and find the solution to my problem. I will keep looking in the iterim but if you could point me in the right direction it would be greated appreciated.

The code posted in custommod_checkout_pane_custommod_cart() adds a new checkout pane that is identical to the stock one but localizes product names - the caveat is that it has to be enabled manually. In your store settings you should enable the "Your order" pane and disable the stock checkout pane.

I had enabled the custom pane, hence at the '/cart' stage it had a correctly translated cart. The '/cart/checkout' stage it dropped back into the 'untranslated' cart and then as I move through to the '/cart/checkout/review' it is translated again. This does seem strange to me as all views of the cart use the same function to perform the translation, no? I did also echo the language to the screen to see if there was anything strange going on there either but there wasn't - the correct language was identified on the 'untranslated' cart page.

This issue seems to be only happening for me (assumption) so I will go back and check my custom module to make sure it is identical to the contents on this page and report back the results.

OK, a little more information. Within the 'custommod_checkout_pane_custommod_cart' function their is a case structure. When the $op variable equals 'view' an array of cart contents is returned for theming. It is the contents of this array that are not translated. When $op equals 'review' it runs the code that does the translation etc. and that all works fine.

Hi, and thx for your contributions.
In order to get options translated i've added t() here and there in uc_attribute.module around $option->name.
But I still cant find my string while performing a searching in 'translate interface'. No match.
Would it be possible to manually add the translation in some .po ? And which one?
This problem is very accurate about options. I dont really know Drupal code so I would be very glad if any idea come up to go around these intranslatable darn options . With code, module or $$ or anything...
:(

Items won't turn up in Translate Interface (even if wrapped in t()) until they've been displayed once (so that Drupal can add the string to the database). Try viewing a product that uses these options once in the different languages and then checking the translate interface tool again.

Please note however: hacking (in the sense of modifying) on core files is not recommended since your changes will be erased the next time you upgrade your Drupal installation! I would recommend that instead of wrapping t(), look into creating a hook that translates the options dynamically when displaying the node. Using t() here would be fine, as even though it has the exact same effect, you've created an add-on module that won't be erased when you upgrade Ubercart!

Unfortunately, the Attribute module doesn't provide any hooks to change the attribute names on-the-fly so the best way we can currently work around it is by changing the attribute names as they get loaded - but that means attaching the translation code to hooks for the products, cart and more. You can find the hook documentation here:http://www.ubercart.org/docs/api

On second thought, I think that the best approach here would be a minimal core hack that adds a hook to the attributes module, allowing you to change the attribute titles prior to them being loaded into the cart or product pages. I think that this could be justified for several reasons:

(Proper) hooks do not yet exist for what you are trying to do

You would only be adding a hook, not changing large portions of code, so worst case scenario is the translation stops working until the core hack is uploaded again

The new hook could be submitted upstream

The changes required are very simple:

In the file uc_attribute.module, function uc_attribute_load_multiple(), the last line is return $attributes. Before this line, add the following:

module_invoke_all('attribute_alter', $attributes);

In the file uc_attribute.module, function uc_attribute_load(), the last line is return $attribute. Before this line, add the following:

module_invoke_all('attribute_alter', $attribute);

Notice that it is identical to the first case, except for the singular $attribute and not $attributes.

I have tried the first issue following your instructions and run into this thing -
When editing my English product I have only a cck with the ILS (Hebrew) curency symbol, so after finishing edit and save I see the price in ILS and not in USD format.
When going to admin/store/settings/store/edit/format it config to ILS.

I'm not sure what you mean - are you trying to have different currencies (and therefore prices) depending on the language/country the user selects? If so, that isn't covered in my tutorial - it gets a bit trickier since the multicurrency project has halted development after Ubercart changed the way price formatting is handled.

Hi everybody
I'm silently following along this capital threads about internationalisation. I'd like to congratulate everyone who contributes to this great step for humanity, despite the overlooking of Ubercar team for the matter. I'm planning to go to Drupalcon Paris on june the 18th, and I long to be there!
I'm using from a long time custommod which saved me hours (I've got 8 langages).
I cannot enough emphasize how this problem is crucial. For Drupal 6 in Europe, firstly. We're going to need more and more to deal with a pretty f. bunch of langages and Taxes. Only for France, at the very minimum level, we've got a lot of Turkishes, and Chineses living here. Without talking of our million of Portugueses, and a few other of Arabians. The latters are here for 30 years and pretty well talking and reading French, so they can buy online. The formers, my neighbours for the most, doesn't even speak French! Sometimes, one in a couple talks French, the other relying on him. Sometimes children fill in administrative papers, sometimes they ask me (I'm native from Paris).
And I think there is great opportunity for Drupal Webdesigners to get some job. Others e-commerce like Prestashop, OsCommerce, Magento, etc, really suck. For example, Drupal is the only one to be able to cleanly manage downloadable products, with multi SKUs. Without talking of SEO.
So translating products, attributes, and options is essential. But I've got in this direction another huge problem.
I'm planning to sell Websites to restaurants. Some have 50 products. It's long, manually. Chinese menus, contains easily 500-1000 products! It'd be a nightmare, in only 2 langages.
The entire process would involve :
- creation of a setup profile
- whichone loads a full dump SQL schema of a website
- then importing a csv of UC formatted products, with their options and languages.
In order to do this, I used Node Import. It creates pretty well Products. I didnt try to plug alltogether attributes, options, translations. I'm beginning...
In this direction, I'd like to understand the UC shema.
I've tried to recreate attributes and options tables, which seems to work. But I cannot read those mean SQL queries that are spitted by dev module when you add options to a product. Specially about cache table...
It would be great if one could begin to build the schema involved in the creation and translation of product nodes, product classes, and product kits...
I imagine it could help everyone to contribute.
(Maybe UC shema is published ? But I didnt find...)
It must be possible to create a special thread for this. We could use Excel...
I thing of things like that
------------------|
uc_attributes |
-----------------|
aid |
name |
... |
------------ -----|
with some arrows between tables...
It would really be great...

Thanks for all the great work. I would love to use these solutions, there is one problem though...

I am working on a webshop using ubercart, in 3 languages. Since I need stock management, I consider using Language Sections as the only way out. Did you manage to solve the stock management problem with the Internationalization module yet?
Thanks in advance for your answer!!

I have been fortunate enough in that inventory did not need to be managed for the online stores I created, so I didn't run into this issue. I haven't confirmed this, but just off the top of my head I think the stock field is just a CCK field added by the stock module, just like price or weight... It should be trivial to add it to the list of fields that get synchronized so that updating one product will update all others.