Issue 15553: Display dates in a language depending format

This commit is contained in:
Michael 2026-02-25 06:46:35 +00:00
commit 6461d2766e
10 changed files with 121 additions and 20 deletions

View file

@ -23,7 +23,7 @@ Wir planen, diese Einschränkung in einer zukünftigen Version zu beheben.
* Apache mit einer aktiverten mod-rewrite-Funktion und dem Eintrag "Options All", so dass du die lokale `.htaccess`-Datei nutzen kannst
* PHP 7.4+
* PHP *Kommandozeilen*-Zugang mit register_argc_argv auf "true" gesetzt in der php.ini-Datei
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar, IDN und OpenSSL-Erweiterung
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, Intl, IDN und OpenSSL-Erweiterung
* Das POSIX Modul muss aktiviert sein ([CentOS, RHEL](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) haben dies z.B. deaktiviert)
* Einen E-Mail Server, so dass PHP `mail()` funktioniert.
Wenn kein eigener E-Mail Server zur Verfügung steht, kann alternativ das [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) Addon mit einem externen SMTP Account verwendet werden.

View file

@ -30,7 +30,7 @@ Due to the large variety of operating systems and PHP platforms in existence we
* Apache with `mod_rewrite` enabled and "[AllowOverride All](https://httpd.apache.org/docs/2.4/mod/core.html#allowoverride)" so you can use a local `.htaccess` file
* PHP 7.4+
* PHP *command line* access with register_argc_argv set to true in the php.ini file
* Curl, GD, GMP, PDO, mbstring, MySQLi, xml, zip, IntlChar, IDN and OpenSSL extensions
* Curl, GD, GMP, PDO, mbstring, MySQLi, xml, zip, Intl, IDN and OpenSSL extensions
* The POSIX module of PHP needs to be activated (e.g. [RHEL, CentOS](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) have disabled it)
* Some form of email server or email gateway such that PHP mail() works.
If you cannot set up your own email server, you can use the [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) addon and use a remote SMTP server.

View file

@ -1588,9 +1588,9 @@ class Conversation
'categories' => $categories,
'folders' => $folders,
'text' => strip_tags($body_html),
'localtime' => DateTimeFormat::local($item['created'], 'r'),
'localtime' => $this->l10n->longDateTime($item['created'], 'r'),
'utc' => DateTimeFormat::utc($item['created'], 'c'),
'ago' => (($item['app']) ? $this->l10n->t('%s from %s', Temporal::getRelativeDate($item['created']), $item['app']) : Temporal::getRelativeDate($item['created'])),
'ago' => (($item['app']) ? $this->l10n->t('%s from %s', $this->l10n->relativeDateTime($item['created']), $item['app']) : $this->l10n->relativeDateTime($item['created'])),
'location_html' => $location_html,
'indent' => '',
'owner_name' => '',

View file

@ -7,10 +7,13 @@
namespace Friendica\Core;
use DateTime;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Session\Capability\IHandleSessions;
use Friendica\Database\Database;
use Friendica\Util\Strings;
use IntlDateFormatter;
use Locale;
/**
* Provide Language, Translation, and Localization functions to the application
@ -68,6 +71,8 @@ class L10n
*/
private $lang = '';
private string $locale = '';
/**
* An array of translation strings whose key is the neutral english message.
*
@ -83,13 +88,16 @@ class L10n
* @var IManageConfigValues
*/
private $config;
private IHandleSessions $session;
public function __construct(IManageConfigValues $config, Database $dba, IHandleSessions $session, array $server, array $get)
{
$this->dba = $dba;
$this->config = $config;
$this->dba = $dba;
$this->config = $config;
$this->session = $session;
$this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', self::DEFAULT)));
$this->setLocale($server);
$this->setSessionVariable($session);
$this->setLangFromSession($session);
}
@ -104,6 +112,24 @@ class L10n
return $this->lang;
}
/**
* Set the instance locale based on the HTTP Accept-Language header.
*
* Reads the `HTTP_ACCEPT_LANGUAGE` value from the provided server array
* and stores the accepted locale string in `$this->locale` using
* `Locale::acceptFromHttp()`.
*
* @param array $server The $_SERVER-like array containing HTTP headers
* @return void
*/
private function setLocale(array $server)
{
if (!isset($server['HTTP_ACCEPT_LANGUAGE'])) {
return;
}
$this->locale = Locale::acceptFromHttp($server['HTTP_ACCEPT_LANGUAGE']);
}
/**
* Sets the language session variable
*/
@ -111,6 +137,7 @@ class L10n
{
if ($session->get('authenticated') && !$session->get('language')) {
$session->set('language', $this->lang);
$session->set('locale', $this->locale);
// we haven't loaded user data yet, but we need user language
if ($session->get('uid')) {
$user = $this->dba->selectFirst('user', ['language'], ['uid' => $_SESSION['uid']]);
@ -575,4 +602,79 @@ class L10n
$newL10n->loadTranslationTable($lang);
return $newL10n;
}
/**
* Format a date/time string using RELATIVE_FULL date and MEDIUM time.
*
* This will produce relative strings where supported by the ICU implementation
* (for example "yesterday", "in 2 days") according to the current locale.
*
* @param string $datestring Date/time string (e.g. ISO 8601)
* @return string Formatted relative date/time string
* @throws \Exception If the date string cannot be parsed into a DateTime
*/
public function relativeDateTime(string $datestring): string
{
return $this->formatDateTime($datestring, IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::MEDIUM);
}
/**
* Format a date/time string using FULL date and MEDIUM time according to current locale.
*
* @param string $datestring Date/time string (e.g. ISO 8601)
* @return string Formatted date/time string
* @throws \Exception If the date string cannot be parsed into a DateTime
*/
public function longDateTime(string $datestring): string
{
return $this->formatDateTime($datestring, IntlDateFormatter::FULL, IntlDateFormatter::MEDIUM);
}
/**
* Format a date/time string using MEDIUM date and SHORT time according to current locale.
*
* @param string $datestring Date/time string
* @return string Formatted date/time string
* @throws \Exception If the date string cannot be parsed into a DateTime
*/
public function dateTime(string $datestring): string
{
return $this->formatDateTime($datestring, IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT);
}
/**
* Format a date string (date only) using FULL date format according to current locale.
*
* @param string $datestring Date string
* @return string Formatted date string
* @throws \Exception If the date string cannot be parsed into a DateTime
*/
public function longDate(string $datestring): string
{
return $this->formatDateTime($datestring, IntlDateFormatter::FULL, IntlDateFormatter::NONE);
}
/**
* General date/time formatting helper.
*
* Creates an IntlDateFormatter using the instance locale and the timezone
* stored in session (if any) and formats the provided date/time string.
*
* @param string $datestring Date/time string (e.g. ISO 8601)
* @param int $dateType One of IntlDateFormatter::SHORT|MEDIUM|LONG|FULL|NONE
* @param int $timeType One of IntlDateFormatter::SHORT|MEDIUM|LONG|FULL|NONE
* @return string Formatted date/time string
* @throws \Exception If the date string cannot be parsed into a DateTime
*/
public function formatDateTime(string $datestring, int $dateType, int $timeType): string
{
$formatter = new IntlDateFormatter(
$this->session->get('language') ?? $this->locale ?: 'en_US',
$dateType,
$timeType,
$this->session->get('timezone') ?? null
);
return $formatter->format(new DateTime($datestring));
}
}

View file

@ -23,6 +23,7 @@ use Friendica\Util\Map;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
use IntlDateFormatter;
/**
* functions for interacting with the event database table
@ -37,12 +38,10 @@ class Event
$uriid = $event['uri-id'] ?? $uriid;
$bd_format = DI::l10n()->t('l F d, Y \@ g:i A \G\M\TP (e)'); // Friday October 29, 2021 @ 9:15 AM GMT-04:00 (America/New_York)
$event_start = DI::l10n()->getDay(DateTimeFormat::local($event['start'], $bd_format));
$event_start = DI::l10n()->formatDateTime($event['start'], IntlDateFormatter::FULL, IntlDateFormatter::LONG);
if (!empty($event['finish'])) {
$event_end = DI::l10n()->getDay(DateTimeFormat::local($event['finish'], $bd_format));
$event_end = DI::l10n()->formatDateTime($event['finish'], IntlDateFormatter::FULL, IntlDateFormatter::LONG);
} else {
$event_end = '';
}

View file

@ -3686,11 +3686,11 @@ class Item
}
if (!empty($question['voters']) && !empty($question['endtime'])) {
$summary = DI::l10n()->tt('%d voter. Poll end: %s', '%d voters. Poll end: %s', $question['voters'] ?? 0, Temporal::getRelativeDate($question['endtime']));
$summary = DI::l10n()->tt('%d voter. Poll end: %s', '%d voters. Poll end: %s', $question['voters'] ?? 0, DI::l10n()->relativeDateTime($question['endtime']));
} elseif (!empty($question['voters'])) {
$summary = DI::l10n()->tt('%d voter.', '%d voters.', $question['voters'] ?? 0);
} elseif (!empty($question['endtime'])) {
$summary = DI::l10n()->t('Poll end: %s', Temporal::getRelativeDate($question['endtime']));
$summary = DI::l10n()->t('Poll end: %s', DI::l10n()->relativeDateTime($question['endtime']));
} else {
$summary = '';
}

View file

@ -184,7 +184,7 @@ class Profile extends BaseProfile
$basic_fields += self::buildField(
'membersince',
$this->t('Joined:'),
DateTimeFormat::local($profile['register_date']),
$this->l10n->longDate($profile['register_date']),
);
}

View file

@ -478,7 +478,7 @@ class Display extends BaseSettings
'$channel_languages' => ['channel_languages[]', $this->t('Channel languages:'), $channel_languages, $this->t('Select all the languages you want to see in your channels. "Unspecified" describes all posts for which no language information was detected (e.g. posts with just an image or too little text to be sure of the language). If you want to see all languages, you will need to select all items in the list.'), $languages, 'multiple'],
'$timeline_channels' => ['timeline_channels[]', $this->t('Timeline channels:'), $timeline_channels, $this->t('Select all the channels that you want to see in your network timeline.'), $channels, 'multiple'],
'$has_timeline_channels' => !empty($channels),
'$filter_channels' => ['filter_channels[]', $this->t('Filter channels:'), $filter_channels, $this->t('Select all the channels that you want to use as a filter for your network timeline. All posts from these channels will be hidden. For technical reasons postings that are older than %s will not be filtered.', DateTimeFormat::local(Engagement::getCreationDateLimit(false)), 'r'), $filter, 'multiple'],
'$filter_channels' => ['filter_channels[]', $this->t('Filter channels:'), $filter_channels, $this->t('Select all the channels that you want to use as a filter for your network timeline. All posts from these channels will be hidden. For technical reasons postings that are older than %s will not be filtered.', $this->l10n->dateTime(Engagement::getCreationDateLimit(false)), 'r'), $filter, 'multiple'],
'$has_filter_channels' => !empty($filter),
'$first_day_of_week' => ['first_day_of_week', $this->t('Beginning of week:'), $first_day_of_week, '', $weekdays, false],

View file

@ -23,7 +23,6 @@ use Friendica\Protocol\Activity;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use GuzzleHttp\Psr7\Uri;
use InvalidArgumentException;
@ -165,8 +164,8 @@ class Post
if (strtotime($item['edited']) - strtotime($item['created']) > 1) {
$edited = [
'label' => DI::l10n()->t('This entry was edited'),
'date' => DateTimeFormat::local($item['edited'], 'r'),
'relative' => Temporal::getRelativeDate($item['edited']),
'date' => DI::l10n()->longDateTime($item['edited']),
'relative' => DI::l10n()->relativeDateTime($item['edited']),
];
}
$sparkle = '';
@ -485,8 +484,8 @@ class Post
$tags = Tag::populateFromItem($item);
$ago = Temporal::getRelativeDate($item['created']);
$ago_received = Temporal::getRelativeDate($item['received']);
$ago = DI::l10n()->relativeDateTime($item['created']);
$ago_received = DI::l10n()->relativeDateTime($item['received']);
if (DI::config()->get('system', 'show_received') && (abs(strtotime($item['created']) - strtotime($item['received'])) > DI::config()->get('system', 'show_received_seconds')) && ($ago != $ago_received)) {
$ago = DI::l10n()->t('%s (Received %s)', $ago, $ago_received);
}
@ -567,7 +566,7 @@ class Post
'sparkle' => $sparkle,
'title' => $item['title'],
'summary' => $item['content-warning'],
'localtime' => DateTimeFormat::local($item['created'], 'r'),
'localtime' => DI::l10n()->longDateTime($item['created']),
'utc' => DateTimeFormat::utc($item['created']),
'ago' => $item['app'] ? DI::l10n()->t('%s from %s', $ago, $item['app']) : $ago,
'app' => $item['app'],

View file

@ -337,6 +337,7 @@ class Authentication
if (strlen($user_record['timezone'])) {
$this->appHelper->setTimeZone($user_record['timezone']);
$this->session->set('timezone', $user_record['timezone']);
}
$contact = $this->dba->selectFirst('contact', ['id'], ['uid' => $user_record['uid'], 'self' => true]);