How can I use the localization mechanism in WordPress to get access to an existing but not-current language string?
Background: I have a custom theme where I use locale 'en_US' as the default locale and translate through a PO file to locale 'es_ES' (Spanish).
Let us say I use the construction
__('Introduction', 'my_domain');
in my code, and that I have translated 'Introduction' to the Spanish 'Introducción´ in my PO file. All this works fine.
Now to the problem: I want to insert n records in my database with all existing translations of the string 'Introduction' - one for each language; so, n = 2 in my example.
Ideally, I would write something like this:
$site_id = 123;
// Get an array of all defined locales: ['en_US', 'es_ES']
$locales = util::get_all_locales();
// Add one new record in table details for each locale with the translated target string
foreach ($locales as $locale) {
db::insert_details($site_id, 'intro',
__('Introduction', 'my_domain', $locale), $locale);
}
Only, that the 3rd parameter in __() above is pure fantasy on my part. You can only validly write
__('Introduction', 'my_domain');
to get either 'Introduction' or 'Introducción' depending on the current locale.
The outcome of the code above would ideally be that I end up with two records in my table:
SITE_ID CAT TEXT LOCALE
123 intro Introduction en_US
123 intro Introducción es_ES
I am aware that I want something that requires loading all the MO files, where normally, only the MO file for the current language is required. Maybe use of the WordPress function load_textdomain is necessary - I was just hoping there already exists a solution.
Expanding on the question by including the plugin PolyLang: is it possible to use Custom Strings to achieve the above functionality? E.g. conceptually:
pll_('Introduction', $locale)
Old question I know, but here goes -
Starting with a simple example where you know exactly what locales to load and exactly where the MO files are, you could use the MO loader directly:
<?php
$locales = array( 'en_US', 'es_ES' );
foreach( $locales as $tmp_locale ){
$mo = new MO;
$mofile = get_template_directory().'/languages/'.$tmp_locale.'.mo';
$mo->import_from_file( $mofile );
// get what you need directly
$translation = $mo->translate('Introduction');
}
This assumes your MO files are all under the theme. If you wanted to put more of this logic through the WordPress's environment you could, but it's a bit nasty. Example:
<?php
global $locale;
// pull list of installed language codes
$locales = get_available_languages();
$locales[] = 'en_US';
// we need to know the Text Domain and path of what we're translating
$domain = 'my_domain';
$mopath = get_template_directory() . '/languages';
// iterate over locales, finally restoring the original en_US
foreach( $locales as $switch_locale ){
// hack the global locale variable (better to use a filter though)
$locale = $switch_locale;
// critical to unload domain before loading another
unload_textdomain( $domain );
// call the domain loader - here using the specific theme utility
load_theme_textdomain( $domain, $mopath );
// Use translation functions as normal
$translation = __('Introduction', $domain );
}
This method is nastier because it hacks globals and requires restoring your original locale afterwards, but it has the advantage of using WordPress's internal logic for loading your theme's translations. That would be useful if they were in different locations, or if their locations were subject to filters.
I also used get_available_languages in this example, but note that you'll need the core language packs to be installed for this. It won't pick up Spanish in your theme unless you've also installed the core Spanish files.
Related
I'm trying to import data from an Excel sheet into WordPress with the Pro version of WP All Import. We are using Polylang for multi language support. I wonder how to manage importing the content into the correct language versions. I discovered that there is a hidden taxonomy "language" which I can manually set to "de" for setting language to German. But how do I link corresponding translations? Any ideas how to solve this, maybe with some custom functions? Thank you in advance!
I have found a better solution.
if ( !function_exists( 'pll_save_post_translations' ) ) {
require_once '/include/api.php'; }
$arr = array();
$result = pll_save_post_translations(['en' => 21398, 'ro'=>21396]);
//where 21398 and 21396 are the post_id and 'en' and 'ro' the nicenames of the languages.
Put this code in your functions.php and run it only once (meaning you refresh the page only once as admin after you put it). Then delete the code from functions.php. It does not actually create errors, just don't risk it, don't run it twice.
Before you do that, you must import the products in both (in my case) languages. Meaning you import them in one language (you select one language from the top admin bar) and then in another session you import them in the other language (you select the other language) and also if you see "Show private taxonomies" in WP All import, put one language code there. One for each session. After the products in both languages have been imported, you run the code above to tell WordPress that "this post_id is the translation of that post_id".
I hope it helps.
Ah, and you get the post_id in a nice table which you can edit in excel by exporting the products with Wordpress Export (it is pre-installed when you install WordPress, I think). You find it in Dashboard in Tools/Export.
And put
$result = pll_save_post_translations(['en' => 56465, 'ro'=>654864]);
as many times as you need
$result = pll_save_post_translations(['en' => 9999, 'ro'=>34654]);
$result = pll_save_post_translations(['en' => 98641, 'ro'=>98651]); .
for each correlation.
You can do it easier in Excel, you have the correlating id's on two separate columns then put this formula on the next row
=concatenate("$result = pll_save_post_translations(['en' =>",CELL A1,"
'ro'=>",CELL B1,"]);")
Apply it downwards. Select the column you have just concatenated. Paste it under the code in functions.php. Save. Refresh. Delete code from functions.php. Refresh. Booyah.
Please note that I'm currently using both Polylang and Wp All Import/Export Pro versions.
Also, this is currently not 'outdated' code. It relies on an undocumented feature, that was suggested directly from the wp all import team. I'm speaking simply of the additional parameter $data passed by the pmxi_saved_post action.
That said, I know polylang devs are currently working on an addon for this specific issue. Anyway, I managed to do it in this way, for now:
1 -> when you export data, there are 2 polylang-related fields: languages and post_translations. Export them too.
2 -> when you import your data, on the wpallimport screen, create 2 more custom fields and save the 2 above. See attachment below. Now you've got the translations data with every post.
3 -> set this filter:
global $language_codes;
$language_codes = array( // sample lang data
'italiano' => 'it'
,'english' => 'en'
,'espanol' => 'es'
,'francais' => 'fr'
,'deutsch' => 'de'
);
add_action('pmxi_saved_post', 'set_imports_lang', 10, 2);
// using undocumented param $data
// action firm is the following (in fact, it passes 3 params): do_action( 'pmxi_saved_post', $pid, $rootNodes[$i], $is_update );
function set_imports_lang($post_id, $data){
global $language_codes;
// 'lingue' is italian, I guess it will be 'languages' in english: it's one of the 2 fields we saved before
pll_set_post_language($post_id, $language_codes[sanitize_title($data->lingue)]);
}
At this point you have just set each post's original language, nothing more. Now we have to link translations each other. We'll do this with a function that we have to run ONLY 1 TIME. It will run simply reloading any wp screen/page.
function set_imports_translations(){
global $wpdb;
global $language_codes;
// substitute 'enews' with your own post type
$list = $wpdb->get_results("
SELECT
m.post_id, m.meta_value pll, group_concat(concat_ws(':', m2.meta_value, m.post_id)) ids
FROM
$wpdb->posts p
INNER JOIN $wpdb->postmeta m ON p.ID = m.post_id
INNER JOIN $wpdb->postmeta m2 ON m.post_id = m2.post_id AND m2.meta_key = '_import_language_name'
WHERE
p.post_type = 'enews' AND m.meta_key = '_import_translations_id'
GROUP BY pll
");
// query results are something like this:
// 10258 pll_57e92254c445f 10258:Italiano,10259:English,10260:Español,10261:Français,10262:Deutsch
// 10263 pll_57e922552b8c6 10263:Italiano,10264:English,10265:Español,10266:Deutsch
// 10267 pll_57e9225587124 10267:Italiano
// extract data from the third column string
foreach($list as $item){
$ids = explode(',',$item->ids);
$list = array();
foreach($ids as $id){
$data = explode(':',$id);
$list[$language_codes[sanitize_title($data[0])]] = intval($data[1]);
}
//set the translations
pll_save_post_translations($list);
}
}
set_imports_translations();
That's all. :) Ah, the attachment mentioned above:
I have a few problems using Gedmo\DoctrineExtensions Translatable.
First is APY DataGrid - can't make it to show translated strings in grid on non default locale. If I use Translatable with default settings, all strings in grid are displayed in default language. If I implement Translatable in Entity and add annotations for table and other stuff, then I can see translated strings in grid, BUT after switching locales these stays the same. Seems like QueryCache is used, but can't find how to set not to use it. Here's a part for grid:
use APY\DataGridBundle\Grid\Source\Entity;
<...>
$source = new Entity('MainBundle:Entity');
$source->addHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, 'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker');
Of course it would be better to have translations without making annotations and separate entities for them.
Second problem is KNP pagination. Actually didn't dig into it, but similar problem.
The main problem is, when I run some query by translatable field of an entity. Let's say I have an Entity with name Krepšinis (it's basketball in Lithuanian) and I have translated this string to Basketball. Default language is LT. When in default language I perform a search, everything is ok, but if I change locale to EN and try searching for basket, it doesn't return any results and if I search for krep, it returns me Basketball. Code for search:
// Controller
$repository = $this
->getDoctrine()
->getManager()
->getRepository('MainBundle:Entity');
$query = $repository
->search($term)
->getQuery()
->useQueryCache(false)
->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, 'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker');
// Repository
public function search($query)
{
$qb = $this->createQueryBuilder('t');
$term = $qb->expr()->literal('%' . $query . '%');
$query = $qb->where($qb->expr()->like('t.name', $term));
return $query;
}
Any help is appreciated
if content is translated, you need to use query hints as you have already went through. But in order to have it cached for different locales, you need to set the locale hint to the query:
<?php
// hint the locale in order to make query cache id unique
$query->setHint(
\Gedmo\Translatable\TranslatableListener::HINT_TRANSLATABLE_LOCALE,
'en' // take locale from session or request
);
Which is described in the documentation read it carefully.
ORM query cache is based on DQL, query parameters and query hints.
I have an issue with triming a field before it is saved. I wanted to use substr(), or regex() with preg_match(). I have built a Drupal 7 module, but it can't work at all. I have tried using the trim plugin in feeds tamper module, but it doesn't seem to work. The data I am using is from a feed from Google Alerts. I have posted this issue here.
This is what I have done so far, and I know my regular expression is wrong; I was trying to get it do anything, just to see if I could get it to work, but I am pretty lost on how to add this type of function to a Drupal module.
function sub_node_save() {
$url = $node->field_web_screenhot['und'][0]['url'];
$url = preg_match('~^(http|ftp)(s)?\:\/\/((([a-z0-9\-]*)(\.))+[a-z0-9]*)($|/.*$)~i',$url );
$node->field_web_screenhot['und'][0]['url'] =$url;
return ;
}
I used the Devel module to get the field.
If there's an easy way to use substr(), I would consider that or something else.
Basically, I just want to take the Google redirect off the URL, so it is just the basic URL to the web site.
Depending on your question and later comments, I'd suggesting using node_presave hook (http://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_presave/7) for this.
It's called before both insert (new) and update ops so you will need extra validations to prevent it from executing on node updates if you want.
<?php
function MYMODULE_node_presave($node) {
// check if nodetype is "mytype"
if ($node->type == 'mytype'){
// PHP's parse_url to get params set to an array.
$parts = parse_url($node->field_web_screenhot['und'][0]['url']);
// Now we explode the params by "&" to get the URL.
$queryParts = explode('&', $parts['query']);
$params = array();
foreach ($queryParts as $param) {
$item = explode('=', $param);
$params[$item[0]] = $item[1];
}
//valid_url validates the URL (duh!), urldecode() makes the URL an actual one with fixing "//" in http, q is from the URL you provided.
if (valid_url(urldecode($parms['q']))){
$node->field_web_screenhot['und'][0]['url'] = urldecode($parms['q']);
}
}
}
I am trying to set up a Drupal 6 node to load blocks dynamically depending on the selected theme. I figured that I could use $theme_key to determine the name of the theme, and work from there. The weird thing is that if I have several checks on a page, the first one will return an empty value for $theme_key, but subsequent checks will work as expected.
For instance:
<?php
print "Theme: ". $theme_key;
if($theme_key =="foo"){
$viewName = 'theView';
$display_id = 'block_1';
print views_embed_view($viewName, $display_id);
}
else {
$viewName = 'theOtherView';
$display_id = 'block_1';
print views_embed_view($viewName, $display_id);
}
?>
If I have the above in the node multiple times with theme "foo" active (for testing purposes) - the first time will return a blank value for $theme_key, and display theOtherView, but the second time it will show Theme: foo and will display theView. All subsequent calls to $theme_key will be correct as well.
Any calls to $theme_key prior to the first block will return blank values.
I am declaring
<?php global $theme_key; ?>
at the beginning of the node content. (Before all of the conditional blocks...)
What am I doing wrong? Is there a better way to check the current theme?
Just looking at the init_theme() function the globals $theme and $theme_key contain exactly the same values so you could try using $theme (as yvan suggested), but as they contain the same data and are both globals set in the same function I'm not sure it would make any difference.
Is this code in a template file? If so you could be suffering from the age old Drupal theming problem whereby some variables aren't available when the template is built. You could try adding a hook_preprocess_node() function in a module/theme to set up the variable and pass it to your template file. Something like this:
function MYMODULE_preprocess_node(&$vars) {
global $theme_key;
$vars['current_theme_key'] = $theme_key;
}
And then in your template file you'll have access to the variable $current_theme_key, which should then have the right variable in it.
Hope that helps, I've come across these sort of problems with Drupal before and they're a nightmare to debug.
Edit to add more helpful function:
function MYMODULE_preprocess_node(&$vars) {
$node = $vars['node'];
if ($node->type == 'my_type') {
global $theme_key;
$view_name = $theme_key == 'foo' ? 'theView' : 'theOtherView';
$display_id = 'block_1';
$vars['my_custom_view'] = views_embed_view($view_name, $display_id);
}
}
Then in your node.tpl.php or node-type.tpl.php file you can use code like this:
if (isset($my_custom_view)):
echo $my_custom_view;
endif;
By constructing the variables in a preprocess function you shouldn't have any problems with the global $theme_key not being available any more.
Bear in mind you could also implement this in your theme (in the template.php file) by changing the function name to MYTHEME_preprocess_node.
Make sure you clear your Drupal cache once you've implemented the function (wherever you decide to put it) so the system will pick up the changes.
Hope that helps!
I am using Drupal 6.16 with a number of modules installed. I was trying to find out if there is a way to change the output of a node when a different file extension is added to the url. For example:
http://example.com/drupal?q=foo/bar - returns a normal drupal node
http://example.com/drupal?q=foo/bar.xml - returns xml output of the node
Is this even possible with Drupal? Do I have to hack the core code to get this working?
You should not need to hack the core code. There are probably several contributed modules that can do this for you.
To output an XML version of a node, check out the Views Bonus Pack module, which extends the Views module. It has basic export capabilities, including CSV, TXT, DOC, and XML. The documentation is brief, but there is a README.txt file in the views_bonus/export/ directory that gives the basic steps for creating a feed in a view that will output XML.
You can set the path for the feed, so while I don't believe the .xml extension will work, you could set up a path with an additional component like this:
http://example.com/drupal?q=foo/bar <-- normal output
http://example.com/drupal?q=foo/bar/xml <-- XML output
To change the template file that is used for a node based on the path, you can use a preprocess function in your template.php file to add a template suggestion based on the path. This takes a bit more understanding of how the template files work, but ultimately you'll have more control of the output than you will with a view.
Here is how I fixed this.
Add the custom_url_rewrite_inbound function to check for incoming request ending with .xml. If it finds a request ending with .xml it strips that off, so that the correct data can be located by the rest of the drupal machinery. It also sets 'subsite_xml_request' to true so that the appropriate theme template can be used later.
function custom_url_rewrite_inbound (&$result, $path, $path_language) {
if(preg_match('/\.xml$/', $path)) {
$search = preg_replace('/^(.*)\.xml$/', "$1", $path);
if ($src = drupal_lookup_path('source', $search, $path_language)) {
$_REQUEST['xml_request'] = true;
$result = $src;
}
}
Modify the phptemplate_preprocess_page function in your template.php to add additional '-xml' templates.
function phptemplate_preprocess_page(&$vars) {
if ($_REQUEST['xml_request']) {
if (module_exists('path')) {
$path = str_replace('/edit','',$_GET['q']);
$alias = drupal_get_path_alias($path);
if ($alias != $_GET['q']) {
$template_filename = 'page';
foreach (explode('/', $alias) as $path_part) {
$template_filename = $template_filename . '-' . $path_part;
$vars['template_files'][] = $template_filename . '-xml';
}
$vars['template_files'][] = 'page-xml';
}
}
}
}
Create the required page-xml.tpl.php