Jump to content
Sign in to follow this  
MilenKo

Multilanguage support for $page->created date

Recommended Posts

Hey all. After having completted a dozen of profiles now under ProcessWire I've realized that I never had to translate a date due to the fact that I was working on personal profiles and I used numbers time format. Well this is changed now and I have a function to show the date of the page being created which I would need to translate based on the language selected:

<?php echo date("F dS, Y", $page->created); ?>

I've read several other posts where people had an issue with page date field and that was resolved, however how would I achieve a similar functionality without a field and just echoing the date in some format?

Using the afore mentioned call works, however the month is not translated. Tried to use strftime function:

<?php echo strftime("%d %B %Y", strtotime($page->created)); ?>

and that returned '31 December 1969'

Maybe it worth mentioning that I have 3 other languages besides the English set by default (Russian, French & Bulgarian), however I did not add the language packs as I only need the multilanguage part to work for the frontend.

As per some suggestions, I've added the following code to my header.php:

switch($user->language->name) {
	case 'default':
		setlocale (LC_ALL, 'C');
		break;
	case 'french':
		setlocale(LC_ALL, 'fr_FR.utf8'); 
		break;
	case 'bulgarian':
		setlocale (LC_ALL, 'bg_BG.utf8');
		break;		
	case 'russian':
		setlocale(LC_ALL, 'ru_RU.UTF-8');
		break;
}

but the month names are still showing on the default language only. Tested to see if the setlocale is applied properly by echoing the language LC_All value and it showed promptly for every language. Where and what am I doing wrong is the million dollar question? 😉

 

Share this post


Link to post
Share on other sites

Hi @MilenKoI think strtotime() is not needed. $page->created should return a Unix timestamp that you can directly supply to strftime(). This is probably the reason you get 1969's date.

I tested the following on my site and it seems to be working. The site is multi-language, and the month is properly returned in both English and Italian, without the need to use setlocale():

echo strftime("%d %B %Y", $page->created);

and here is the output:

1937294769_ScreenShot2019-07-22at19_25_22.png.ef86f26567c60e46ad5bb5d702a0b13b.png

Regarding setlocale() in principle you should not need to set them. May be you can check for each language its individual configuration, in Core Translation Files:

1972468122_ScreenShot2019-07-22at19_12_45.thumb.png.4e624a283f527af20081aabdbd863acf.png

And add the locale in the field "C":

1085095213_ScreenShot2019-07-22at19_14_23.thumb.png.a9d736e704e362e4e4c0be119a043646.png

This should be repeated for every language including default one. Not sure this will help.

Share this post


Link to post
Share on other sites

Hi @MilenKo, forgotten to mention that formatting date created with strftime() has some limitations, in particular if you need a different date format for each locale you code will get more complex. And if in the future you will have to add a new language you will have to modify your code.

I believe you can achieve a more flexible result creating a DateTime field, where you will define the output format for each language. You will then add this datetime field to your template.  An hook on pages:saveReady can take care to automatically load your datetime field. 

Each time you will output it as $page->yourDateTimeField it will be formatted automatically, depending on the locale, as you defined during the field creation.

Share this post


Link to post
Share on other sites

Hi Edison,

I've tested your suggestion after removing the setlocale from my header.php and adding it to LanguageSupport.module C for every language.

Echoing the $page->created shows the time properly, however when testing with 

echo strftime("%d %B %Y", $page->created);

the date shows in English only no matter of the language.

I sort of like more your idea of having a DateTimeField however not sure how can I set it to have the $page->created populated automatically in it so that I don't have to type it manually? Also, any ideas about what could be the reason that strftime works for you, but not on my side?

Share this post


Link to post
Share on other sites

Hi @MilenKo, it's curious strftime is only showing the date in English, it looks like the locale is not switching. Can you test the following for each of your site languages:

echo setlocale(LC_ALL, 0); // Will return current locale

If locale is properly switching you should see the corresponding locale associated to each language. 

Regarding the datetime field I can try to put in place a small a tutorial.

Share this post


Link to post
Share on other sites

@Edison I echoed the setlocale and it does not change for the language showing this for all of them:

LC_COLLATE=C;LC_CTYPE=English_United States.1252;LC_MONETARY=C;LC_NUMERIC=C;LC_TIME=C

I just noticed that I have a lang="en" in my html opening tag. Could it be because of that?

Share this post


Link to post
Share on other sites

Hi @MilenKo, I may be wrong but it looks like it's returning a php default locale. It may sound like a stupid question, but are your languages locales installed on your server? If you have access to your server you should check the file /etc/default/locale. Running a sudo cat /etc/default/locale you get the list of installed locales. I am asking that as I recall I had first to install locales on the server before I could use them.

The html language directive is normally used by the browser or search engines to classify the page by language, so should not be relevant for this issue. Anyway you may wish to set the proper lang using this code snippet where html is generated:

<html lang='<?= wire('user')->language->code ?>'>

EDIT: sorry too easy ... the above snippet works if you create the following hook in ready.php:

// call it using $user->language->code or wire('user')->language->code or user()->language->code
$wire->addHookProperty('Language::code', function(HookEvent $event) {
    $language = $event->object;
    $event->return = $language->name === 'default' ? 'en' : $language->name;
});

 

Share this post


Link to post
Share on other sites

@Edison There are no stupid questions especially when someone is trying to help without knowing all the facts. Unfortunately, even though I have full access to my server, I can't check /etc/default/locale as I am on a Windows server using OSPanel as my webserver.

To make my life easier and to learn something new, I've added a new field called: date_created and set it as DateTimeField. Now the only thing left is to figure it out how can I save the value of $page->created to that field and to test the translation there.

As I've mentioned earlier, maybe the issue is that I did not install any of the language packs available but only created the languages and added my _strings.php for frontend translation. As the owner of the website would be a super user, he does not want to have his admin and modules translated.

I've added this code to my template footer:

    /* turn of output formating so PW do not give an error when we change the value */
    $page->of(false);
	
	$page->date_created = time();
     
    /* save the date_created field */
    $page->save('date_created');
    
    /* turn on output formating so PW work as it should */
    $page->of(true);

but for some reason it does not want to populate the value of $page->created to the date_created field...

P.S. Since I am working on a testing profile, I can mark the option "default to todays date" and it would start saving the date/time, however if this was a profile with hundreds of pages that should preserve their creation date then I will have to populate somehow the value of $page->created to my DateTime field..

Share this post


Link to post
Share on other sites

Hi @MilenKo, you could try with an hook in ready.php. In this example Pages:saveReady is called right before the page is saved, so this can be used to fill some fields right before save will be executed. Assumption is that this is triggered when you are saving the page from within the Admin area.

$wire->addHookBefore('Pages::saveReady', function($event) {

    $page = $event->arguments(0);

	if($page->hasField('date_created')) {
		$page->date_created = time();
	}

    $event->arguments(0, $page);
});

I quickly wrote it in the browser w/o testing

Please note, this is more a date_modified as it will be overwritten at the next save, but you can easily make it a "true" date_created, with something like that:

if(empty($page->date_created)) $page->date_created = time();

Sorry, of course I thought Linux-wise ... as you have a Windows server when you have sometime have look if php locales are installed.

  • Thanks 1

Share this post


Link to post
Share on other sites

@Edison The code returned an error:

Spoiler

image.png.0167854dde33b77a622ecf5398651481.png

P.S. With a bit of more digging, I was able to populate the value of $page->creaded by inserting the following code to my news-inner template:

<?php

$page->of(false);

// date_created is my custom DateTime field for storing the value
// To avoid date changes every time I edit the page, a check if the field is empty is added
if($page->date_created == '') $page->set('date_created', $page->created);

$page->save();

?>

Now I will test for multilanguage support and if it works, will report back 🙂

Share this post


Link to post
Share on other sites

Hi @MilenKo, the function closing round parenthesis and semicolon were missing, I have edited it above. Please note I had no time to test it, it may be necessary to tune it

Share this post


Link to post
Share on other sites

@MilenKo, you need to add the round bracket and semicolon ad the end.  The last line must be "});"

Share this post


Link to post
Share on other sites

@Edison Will try that once I get back to my place in a bit as I decided to step back from the problem and then hit it with fresh power and some caffeine 🙂

With the page->save approach all my dates were properly populated, so step one is completed, however when I echoed the date, it still does not change with the language switch. Starting to think that the issue might be caused by not adding the language packs or some field settings I've missed... 

Share this post


Link to post
Share on other sites

@Edison Hey man, adding the brackets and semicolon did the trick. Now the date gets populated during the saving and I don't have to add the earlier code to the footer of the templates.

However, this did not help much to have the month translated and it still shows the month on English for any of the languages. Will try to move the profile to my real host which uses linux so that I can be sure that setlocale is setup and working properly. Maybe there is no issue but just the local server is messing things up. 

Moving the site to my live host did not solve the issue. There has to be something else that is messing up the things.

P.S. Double checked that the DateTime field has the date format for every language as well as that the setlocale is properly set for every language as:

bg_BG.utf-8, ru_RU.UTF-8, fr_FR.utf-8 and C (last one for English)

Strange enough, only the date translation is not working but all other translated strings do work.

Spoiler

image.png.bb7aecc712722428dea01377d9c50733.png

 

Share this post


Link to post
Share on other sites

After a decent night sleep and my morning coffee I had some progress towards resolving my issue. While searching the Internet I found some useful pieces of code that I was able to put together to test my server setlocale. As it appears, it is all running properly with only one issue where for the cyrillic letters to appear properly, I needed to use iconv to convert them to UTF-8:

Spoiler

<?php

header('Content-type: text/html; charset=utf-8');

$locale_time = setlocale (LC_TIME, 'French_Canada.1252');

function strf_time($format, $timestamp, $locale)
{
    $date_str = strftime($format, $timestamp);
    if (strpos($locale, '1251') !== false)
    {
        return iconv('cp1251', 'utf-8', $date_str);
    }
    else
    {
        return $date_str;
    }
}

$locale_time = setlocale (LC_TIME, 'French_Canada.1252');
echo strf_time("%A, %B %d", time(), $locale_time);
echo "<br />";

$locale_time = setlocale (LC_TIME, 'Russian_Russia.1251');
echo strf_time("%A, %B %d", time(), $locale_time);
echo "<br />";

$locale_time = setlocale (LC_TIME, 'Bulgarian_Bulgaria.1251');
echo strf_time("%A, %B %d", time(), $locale_time);
echo "<br />";

$locale_time = setlocale (LC_TIME, 'English_Australia.1252');
echo strf_time("%A, %B %d", time(), $locale_time);
echo "<br />";

 

The result of the script gave me this:

mardi, juillet 23
вторник, Июль 23
вторник, юли 23
Tuesday, July 23

So now I will look for a way to use the function and apply it to all locales which should not affect the French/English date but will make the proper appearance of the cyrillic chars. Since I am on a Windows host, I had to use the Windows locales instead of the unix format, however running the code on my unix host returned the proper values as well so it seems like this solution is multiplatform one 😉

Share this post


Link to post
Share on other sites

Alright, I got it all sorted out and is working on my Unix & Windows hosts both locally and online.

In case someone stumbles upon the same issue, here is what worked for me to have cyrillic languages and 1252 ones working fine:

1. Add the following code to _functions.php or other file that you use for your functions:

/**
 * Convert date/time stamp from different characters to utf-8
 */
function strf_time($format, $timestamp, $locale)
{
    $date_str = strftime($format, $timestamp);
    if (strpos($locale, '1251') !== false)
    {
        return iconv('cp1251', 'utf-8', $date_str);
    }
    else
    {
        return $date_str;
    }
}

2. Add this code to your head.inc (note that head.inc is included in all my templates)

<?php
	// Check the user language name
	// Assign the language locale to $locale
	// Windows locale source: https://docs.moodle.org/dev/Table_of_locales
	switch($user->language->name) {
		
		// Default language is English
		case 'default':
			$locale = setlocale (LC_ALL, 'en_US.UTF-8', '-parent en_utf8 used-');
			break;
		
		// Bulgarian language locale
		case 'bulgarian':
			$locale = setlocale (LC_ALL, 'bg_BG.utf8', 'Bulgarian_Bulgaria.1251');
			break;
			
		// Russian language locale
		case 'russian':
			$locale = setlocale (LC_ALL, 'ru_RU.utf8', 'Russian_Russia.1251');
			break;	
			
		// French language locale	
		case 'french':
			$locale = setlocale (LC_TIME, 'fr_CA.UTF-8', 'French_Canada.1252');
			break;		

	} 
?>

3. Add a field to your templates that you would like the date to appear (set the field as DateTime) and no date/time format. In my scenario I called it 'date_created

Spoiler

image.thumb.png.7a6b760baa438e6c18708f57bdea5103.png

4. Assign the field to all templates where the custom date would be needed (in my scenario I assigned it to news-inner.php as I am grabbing some news pages to show on the main page). Note that I assigned the date field to the inner page of every news, but not to the parent or Home.

5. Add this code to your ready.php (if you don't have one create it at ../site/ready.php (where your config.php resign 😉 ). Inside of this file add the following hook (Thanks to @Edison for the code!)

$wire->addHookBefore('Pages::saveReady', function($event) {

    $page = $event->arguments(0);

	if($page->hasField('date_created')) {
		$page->date_created = time();
	}

    $event->arguments(0, $page);
});

Please note that you need to replace 'date_created' with your custom date field (unless you follow steps 1x1).

6. In your markup where the MULTILANGUAGE ENABLED DATE is needed, use the following call:

<?php echo strf_time("%d %B %Y", $l->date_created, $locale);?>

Note: Feel free to change the date appearance format using PHP strftime function format

Well, that is it, it works fine with me and showing all the languages properly now. If you would like to go further, some of the months that are having more than 3-4 letters in the abbreviation could have a dot after the name, however for this you should create an array with the shorter months and show the dot if it is not one of those or do some magic counting the letters of the month and if the result is higher than 3-4 to show it, but I did not go that way as it is working as the original markup 😉

Another issue I noticed is the Declination of the names of the months when displaying the date in Russian. I found a function that could heal that, however I hightly doubt I would need it. In case someone wants to play with it and adapt it for PW, here it is . Note, that the page is in Russian so you might need a translator or just read the code.

image.png

Share this post


Link to post
Share on other sites

After giving it some thought, It really messes up the things when one see the month in Russian language without the proper termination so I will have to find a way to translate the month name to the proper one in order to make it gramatically correct.

It would be great if some of the russian techies could share their experience 😉

Share this post


Link to post
Share on other sites

OK, folks. I got a prototype of fully working month names Russian ranslation that works perfectly by creating an array with the proper month names and then showing the array result for the number of the present month. Simple and elegant, but I decided to go further now and create an unified version of translation function which would check the user selected language, set the proper locale and echo the month name either from the array for the Russian translation or directly. I am surprised that I did not see any solutions for that here yet 🙂

Having a universal function would allow me to use it over and over again for any websites that would have the Russian in the languages list simply by inserting it to my _functions.php 😉

Share this post


Link to post
Share on other sites

Hey everyone, I got some great news about the proper date showing of ProcessWire in Russian. There are different approaches depending on the format to present the date. Here are the two best fitting the purpose:

Approach 1:

Spoiler

	// Create an array with the proper names of the month
	// (Roditelen)
	$arr = [
	  'января',
	  'февраля',
	  'марта',
	  'апреля',
	  'мая',
	  'июня',
	  'июля',
	  'августа',
	  'сентября',
	  'октября',
	  'ноября',
	  'декабря'
	];

	// Check the user language name
	// Assign the language locale to $locale
	// Windows locale source: https://docs.moodle.org/dev/Table_of_locales
	switch($user->language->name) {
		
		// Default language is English
		case 'default':
			$locale = setlocale (LC_ALL, 'en_US.UTF-8', '-parent en_utf8 used-');
			break;
		
		// Bulgarian language locale
		case 'bulgarian':
			$locale = setlocale (LC_ALL, 'bg_BG.utf8', 'Bulgarian_Bulgaria.1251');
			break;
			
		// Russian language locale
		case 'russian':
			$locale = setlocale (LC_ALL, 'ru_RU.utf8', 'Russian_Russia.1251');
			break;	
			
		// French language locale	
		case 'french':
			$locale = setlocale (LC_TIME, 'fr_CA.UTF-8', 'French_Canada.1252');
			break;		

	} 


	// Check the required PHP date format
	// date('d.m.Y', $page->published)
	if ($date = 'date') {
		
		// Date() PHP Function
		$day = date('d', $date);
		$month = date('n', $date) - 1;
		$year = date('Y', $date);
		
	} else {
		
		// STRFTIME PHP Function
		// strf_time("%d %B %Y", $page->published, $locale)
		$day = strftime('%d', $date);
		$month = strftime('%m', $date) - 1;
		$year = strftime('%Y', $date);
		
	}
	
	echo $day . " " . $arr[$month] . " " . $year;

 

 

Approach 2:

Spoiler

// Months list for the replacement
$monthsList = array(
	".01." => "Января", 
	".02." => "Февраля",
	".03." => "Марта",
	".04." => "Апреля",
	".05." => "Мая",
	".06." => "Июня", 
	".07." => "Июля",
	".08." => "Августа",
	".09." => "Сентября",
	".10." => "Октября",
	".11." => "Ноября",
	".12." => "Декабря"
);
 
// Current date
// $currentDate format would be turned to: 31.07.2019
$currentDate = date("d.m.Y", $page->created);


// Since the task is to show the russian month name properly conjugated
// we will replace the month number with the proper name

// Month number for the replacement
$month_number = date(".m.");

// Now the value of $currentDate would be in format 31 июля 2019
$currentDate = str_replace($month_number, " ".$monthsList[$month_number]." ", $currentDate);


echo $currentDate;

 

Even though I personally liked the second approach better since it just use the proper month number that would return the same value in any language, for the Russian proper date appearance I took much simpler solution where all I had to do was to swap the day and the month since plenty of websites were using it already. In a scenario where the month is first and then the date, there is ABSOLUTELY NO NEED to conjugate the month name and the date is appearing still gramatically and esthetically correct: Июль 31, 2019

To allow different date formatting per language (the default way of setting the locales did not work on my server) I've modified a bit the code I've added earlier to my header.inc:

Spoiler

	// Check the user language name
	// Assign the language locale to $locale
	// Windows locale source: https://docs.moodle.org/dev/Table_of_locales
	switch($user->language->name) {
		
		// Default language is English
		case 'default':
			$locale = setlocale (LC_ALL, 'en_US.UTF-8', '-parent en_utf8 used-');
			$date_format = "%d %B, %Y";
			break;
		
		// Bulgarian language locale
		case 'bulgarian':
			$locale = setlocale (LC_ALL, 'bg_BG.utf8', 'Bulgarian_Bulgaria.1251');
			$date_format = "%d %B, %Y";
			break;
			
		// Russian language locale
		case 'russian':
			$locale = setlocale (LC_ALL, 'ru_RU.utf8', 'Russian_Russia.1251');
			$date_format = "%B %d, %Y";
			break;	
			
		// French language locale	
		case 'french':
			$locale = setlocale (LC_TIME, 'fr_CA.UTF-8', 'French_Canada.1252');
			$date_format = "%d %B, %Y";
			break;		

	} 

 

And was able to finally have the date properly showing in all selected languages by calling it inside the HTML markup: 

<?php 

	// STRFTIME FUNCTION DATE FORMAT: 
	// https://www.php.net/manual/en/function.strftime.php
	// $date_format - defined in /sites/includes/head.php for every language
	// $l->published/created shows the date the page was published/created
	// $locale - defined in head.php as well setting the proper locale for every language
	echo strf_time("{$date_format}", $l->published, $locale);

?>

Please note that this approach would work for date() and strftime and there is actually no need of adding the extra DateTime field nor is necessary the code at /site/ready.php , however I've decided to leave it as it is for the learning curve and if a need be, to have a better option to manipulate the date. As far as the head.inc is included in every template of the profile but I would only need it to a few that would show the creation/publishing date, I might add a check for the page ID and include the header to only those templates that need it, however these things are some simple steps so I won't go any deeper there.

Hope it helps to anyone needing to present a date in Russian or any other language that would require some names that differ from the standart PHP date/time function result. Case closed now!

P.S. Forgot to mention that the strf_time is a function that I've used to my _func.php to convert the month name to utf-8 which I use by default in my profile:

/**
 * Convert date/time stamp from different characters to utf-8
 */
function strf_time($format, $timestamp, $locale)
{
    $date_str = strftime($format, $timestamp);
    if (strpos($locale, '1251') !== false)
    {
        return iconv('cp1251', 'utf-8', $date_str);
    }
    else
    {
        return $date_str;
    }
}

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...