So I was reading on Issue #338 alot. AFAIK if I use the |date Filter on a Carbon or DateTime Object it should also be translated, shouldn't it?
I did a fresh install of OctoberCMS (build 465) and installed the Translate Plugin. I did the same test @munxar did:
use Illuminate\Support\Carbon as IlluminateCarbon;
use Carbon\Carbon as NesbotCarbon;
use October\Rain\Argon\Argon;
function onStart() {
trace_log('Creating dates');
trace_log('NesbotCarbon locale is ' . NesbotCarbon::getLocale());
trace_log('IlluminateCarbon locale is ' . IlluminateCarbon::getLocale());
$this['illcarbon_date'] = IlluminateCarbon::parse('2020-01-11 12:34:56');
$this['carbon_date'] = NesbotCarbon::parse('2020-02-12 12:34:56');
$this['argon_date'] = Argon::parse('2020-03-12 12:34:56');
}
The output for the _de locale_ comes to:
Illuminate Carbon Date: January 11, 2020 12:34
Nesbot Carbon Date: February 12, 2020 12:34
Argon Date: M盲rz 12, 2020 12:34
I did some tracing and figured out that the Argon Object extends the JessengersDate Class and gets its locale set correctly after switching with the localePicker
However:
The NesbotCarbon listens only to LocaleUpdated Events if they come from the Laravel Event Dispatcher
use Carbon\Carbon;
use Illuminate\Events\Dispatcher;
use Illuminate\Events\EventDispatcher;
use Illuminate\Translation\Translator as IlluminateTranslator;
use Symfony\Component\Translation\Translator;
class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
public function boot()
{
$service = $this;
$events = $this->app['events'];
if ($events instanceof EventDispatcher || $events instanceof Dispatcher) {
$events->listen(class_exists('Illuminate\Foundation\Events\LocaleUpdated') ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed', function () use ($service) {
$service->updateLocale();
});
$service->updateLocale();
}
}
Since the Event Dispatcher is of class OctoberRainEventsDispatcher the NesbotCarbon locale is never set.
I tried to figure out how JensseggersDate package translates the dates. I couldn't figured it really out. I know it overwrites the function format($format) from Carbon and includes the translation.
With Argon I understand it, because Argon extends JensseggersDate.
My biggest question is:
Should the RainLab.Translate Plugin translate any date which goes through the |date Twig filter?
If more information or traces are required to help out, I can provide them.
@arustler thanks for the thorough investigation on this, I'll look into this as soon as possible.
@mjauvin thx... let me know if I can provide any more information since I tried to dig into it.
As far as I traced the mechanism:
public function setLocale($locale, $remember = true)public function setLocale($locale)IlluminateFoundationApplication Application.php public function setLocale($locale)
This will fire the LocaleUpdated Event with The IlluminateFoundationEvents class but with dispatcher OctoberRainEventsDispatcher
$this['events']->dispatch(new Events\LocaleUpdated($locale));
OctoberRainFoundationApplication Application.php fires also
$this['events']->fire('locale.changed', [$locale]);
JensseggersDateDateServiceProvider
JensseggesrDate listens to:
public function boot()
{
$localeChangedEvent = class_exists('\\Illuminate\\Foundation\\Events\\LocaleUpdated')
? \Illuminate\Foundation\Events\LocaleUpdated::class
: 'locale.changed';
$this->app['events']->listen($localeChangedEvent, function () {
$this->setLocale();
});
$this->setLocale();
}
This will also set the translator from the JensseggersDate Object to the correct locale
I think this is so far enough for Argon since it extends JensseggersDate class
CarbonLaravelServiceProvider
NesbotCarbon listens to:
use Illuminate\Events\Dispatcher;
use Illuminate\Events\EventDispatcher;
.
.
.
public function boot()
{
$service = $this;
$events = $this->app['events'];
if ($events instanceof EventDispatcher || $events instanceof Dispatcher) {
$events->listen(class_exists('Illuminate\Foundation\Events\LocaleUpdated') ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed', function () use ($service) {
$service->updateLocale();
});
$service->updateLocale();
}
}
If I change the file so it also listens to the Dispatcher of October, the correct locale gets set but it won't be translated.
Next what I investigated was the Jensseggers Translations mechanism.
The object passed to the Twig filter |date will be returned with the format function
CoreExtension.php from TwigTwig:
function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null)
{
if (null === $format) {
$formats = $env->getExtension(CoreExtension::class)->getDateFormat();
$format = $date instanceof \DateInterval ? $formats[1] : $formats[0];
}
if ($date instanceof \DateInterval) {
return $date->format($format);
}
return twig_date_converter($env, $date, $timezone)->format($format);
}
From here I get a little lost. The format function of the Object if it extends the JensseggersDate class will use the translation (like Argon) I can't figure out how NesbotCarbon used to be translated.
I looked further into this and the date twig filter uses DateTime php module which does not support locale.
We'd need to override the date twig filter in here to solve this.
I started work on this and it's promising, although not complete. I'll include my so far in here so you can test.
public function registerMarkupTags()
{
return [
'filters' => [
'ldate' => function($date, $format = '%A, %e %B %Y %H:%M') {
$timezone = \Config::get('cms.backendTimezone', 'UTC');
date_default_timezone_set($timezone);
$locale = \App::getLocale();
$parts = preg_split('/[-_]/', $locale);
if (count($parts) === 1) {
// todo: get default country from a config or environment
$parts[1] = 'ca';
}
$locale = sprintf('%s_%s', strtolower($parts[0]), strtoupper($parts[1]));
setlocale(LC_TIME, $locale);
if (is_string($date)) {
$ts = strtotime($date);
} else {
$ts = $date->timestamp;
}
return utf8_encode(strftime($format, $ts));
},
];
}
Note: the above filter does not use the same date format as the default date twig filter.
I looked further into this and the date twig filter uses DateTime php module which does not support locale.
We'd need to override the date twig filter in here to solve this.
I started work on this and it's promising, although not complete. I'll include my so far in here so you can test.
@mjauvin I like that idea to overwrite the date filter. This made me think. How about we overwrite it in such a manner that we parse the incoming date with Argon and then just resend it to the original date filter since any Argon date extends the JensseggersDate class it will be translated. Then we wouldn't do all this locale stuff
If I have time tomorrow I will try this approach.
Or, here's a thought 馃槃
Pass Argon instances to the date filter like it's designed to handle instead of doing all this other work.
Best version so far:
'ldate' => function($date, $format='F j, Y h:i') {
$timestamp = is_string($date) ? strtotime($date) : $date->timestamp;
$date = \Argon\Argon::createFromTimestamp( $timestamp );
return $date->format($format);
},
Whatever input is used, you get a localized date.
Or, here's a thought
Pass Argon instances to the date filter like it's designed to handle instead of doing all this other work.
Depending on where the date comes from, it's not always possible. The new filter above will convert everything back to Argon, though.
I'd rather people just made sure they were using Argon instances though, since they will work everywhere in the application, not just after being run through a filter.
We can consider adding a filter like you described @mjauvin if it turns out there's a lot of demand for it but right now I don't think it's necessary.
I've added it to my filters plugin.
So I had time to try my approach and came up with this solution
public function registerMarkupTags()
{
$twig = $this->app->make('twig.environment');
$dateTwig = $twig->getFilter('date')->getCallable();
return [
'filters' => [
'_' => [$this, 'translateString'],
'__' => [$this, 'translatePlural'],
'localeUrl' => [$this, 'localeUrl'],
'date' => function($date, $format = null, $timezone = null ) use ($twig, $dateTwig) {
$date = Argon::parse($date);
return $dateTwig($twig, $date, $format, $timezone);
}
]
];
}
To make it failsafe Argon::parse could go in a try block and pass the $date value directly to the original Twig filter
This is really neat @arustler. Unfortunately, @LukeTowers prefers we keep the current behavior.
Personally, I think this would make RainLab.Translate provide a much needed improvement to the default twig date filter.
That could constitute a breaking change to the date filter as now something that worked in |date might barf when Argon::parse() tries to handle it. We'll leave it as it is right now (either pass Argon instances to | date or use @mjauvin's filters plugin with the dedicated Argon processing | ldate filter unless I see a lot of demand for it to be a core functionality.
@arustler Just wanted to let you know the following call is causing problems with MailTemplates:
$dateTwig = $twig->getFilter('date')->getCallable();
I suggest assigning a static value if you don't want to have trouble:
$dateTwig = "twig_date_format_filter";
If you want to test what I mean by "MailTemplates problems", under settings->Mail Templates, click on a mail template and then click the "send test email" and you'll see what I mean...
Most helpful comment