configure am/pm time-format as user preference (#2789)
This commit is contained in:
34
.env.dist
34
.env.dist
@@ -1,30 +1,16 @@
|
|||||||
### DATABASE CONFIGURATION
|
# Configure your database connection and set the correct server version:
|
||||||
# Replace "user", "password" and "database" with your database connection.
|
# for MySQL "serverVersion=5.7" and for MariaDB "serverVersion=mariadb-10.5.8"
|
||||||
# Configure the server version, MariaDB requires the "mariadb-" prefix, eg:
|
|
||||||
# for MySQL "serverVersion=5.7" and for MariaDB "serverVersion=mariadb-10.5.8"
|
|
||||||
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7
|
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7
|
||||||
|
# Email will be sent with this address as sender
|
||||||
### EMAIL CONFIGURATION
|
|
||||||
# Emails will be sent "from":
|
|
||||||
MAILER_FROM=kimai@example.com
|
MAILER_FROM=kimai@example.com
|
||||||
|
# Email connection (disabled by default) more info at https://www.kimai.org/documentation/emails.html
|
||||||
# Email connection (disabled by default with MAILER_URL=null://null)
|
|
||||||
# SMTP: smtp://localhost:25?encryption=&auth_mode=
|
|
||||||
# Google: gmail://username:password@default
|
|
||||||
# Amazon: ses://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1
|
|
||||||
# Mailchimp: mandrill://KEY@default
|
|
||||||
# Mailgun: mailgun://KEY:DOMAIN@default
|
|
||||||
# Postmark: postmark://ID@default
|
|
||||||
# Sendgrid: sendgrid://KEY@default
|
|
||||||
# Disable emails: null://null
|
|
||||||
MAILER_URL=null://null
|
MAILER_URL=null://null
|
||||||
|
# do not change, unless you are developing for Kimai
|
||||||
### APPLICATION CONFIGURATION
|
|
||||||
APP_ENV=prod
|
APP_ENV=prod
|
||||||
|
# should be changed to a unique character sequence
|
||||||
APP_SECRET=change_this_to_something_unique
|
APP_SECRET=change_this_to_something_unique
|
||||||
|
# unlikely, that you need to change this one
|
||||||
# Running in a "special" environment, eg. behind reverse proxies?
|
|
||||||
# Check those:
|
|
||||||
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
|
|
||||||
# TRUSTED_HOSTS=localhost,example.com
|
|
||||||
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
|
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
|
||||||
|
# Running behind reverse proxies? Use those:
|
||||||
|
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
|
||||||
|
# TRUSTED_HOSTS=localhost,example.com
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ you can upgrade your Kimai installation to the latest stable release.
|
|||||||
Check below if there are more version specific steps required, which need to be executed after the normal update process.
|
Check below if there are more version specific steps required, which need to be executed after the normal update process.
|
||||||
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
|
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.16](https://github.com/kevinpapst/kimai2/releases/tag/1.16)
|
||||||
|
|
||||||
|
**DEVELOPER**
|
||||||
|
|
||||||
|
- Removed `formDateTime` field from API model `I18nConfig`
|
||||||
|
|
||||||
## [1.15](https://github.com/kevinpapst/kimai2/releases/tag/1.15)
|
## [1.15](https://github.com/kevinpapst/kimai2/releases/tag/1.15)
|
||||||
|
|
||||||
**Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
**Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
|
||||||
|
|||||||
@@ -165,7 +165,8 @@
|
|||||||
"bin/console lint:xliff translations",
|
"bin/console lint:xliff translations",
|
||||||
"bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction"
|
"bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction"
|
||||||
],
|
],
|
||||||
"kimai:tests": "vendor/bin/phpunit tests/",
|
"tests": "vendor/bin/phpunit tests/",
|
||||||
|
"kimai:tests": "@tests",
|
||||||
"kimai:tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
|
"kimai:tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
|
||||||
"kimai:tests-integration": "vendor/bin/phpunit --group integration tests/",
|
"kimai:tests-integration": "vendor/bin/phpunit --group integration tests/",
|
||||||
"kimai:phpstan": "@phpstan",
|
"kimai:phpstan": "@phpstan",
|
||||||
|
|||||||
@@ -214,91 +214,74 @@ kimai:
|
|||||||
# --------------------------------------------------------------------------------
|
# --------------------------------------------------------------------------------
|
||||||
languages:
|
languages:
|
||||||
cs:
|
cs:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m H:i'
|
date_time: 'd.m H:i'
|
||||||
da:
|
da:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
de:
|
de:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
de_AT:
|
de_AT:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
de_CH:
|
de_CH:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
el:
|
el:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
en:
|
en:
|
||||||
date_time_type: 'yyyy-MM-dd HH:mm'
|
|
||||||
date_type: 'yyyy-MM-dd'
|
date_type: 'yyyy-MM-dd'
|
||||||
date: 'Y-m-d'
|
date: 'Y-m-d'
|
||||||
date_time: 'm-d H:i'
|
date_time: 'm-d H:i'
|
||||||
duration: '%%h:%%m h'
|
duration: '%%h:%%m h'
|
||||||
es:
|
es:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
fi:
|
fi:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
fr:
|
fr:
|
||||||
date_time_type: 'dd/MM/yyyy HH:mm'
|
|
||||||
date_type: 'dd/MM/yyyy'
|
date_type: 'dd/MM/yyyy'
|
||||||
date: 'd/m/Y'
|
date: 'd/m/Y'
|
||||||
date_time: 'd/m H:i'
|
date_time: 'd/m H:i'
|
||||||
duration: '%%h h %%m'
|
duration: '%%h h %%m'
|
||||||
he:
|
he:
|
||||||
date_time_type: 'dd/MM/yyyy HH:mm'
|
|
||||||
date_type: 'dd/MM/yyyy'
|
date_type: 'dd/MM/yyyy'
|
||||||
date: 'd/m/Y'
|
date: 'd/m/Y'
|
||||||
date_time: 'd/m H:i'
|
date_time: 'd/m H:i'
|
||||||
duration: '%%h:%%m'
|
duration: '%%h:%%m'
|
||||||
hu:
|
hu:
|
||||||
date_time_type: 'yyyy.MM.dd. HH:mm'
|
|
||||||
date_type: 'yyyy.MM.dd.'
|
date_type: 'yyyy.MM.dd.'
|
||||||
date: 'Y.m.d.'
|
date: 'Y.m.d.'
|
||||||
date_time: 'm.d. H:i'
|
date_time: 'm.d. H:i'
|
||||||
it:
|
it:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
nl:
|
nl:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
duration: '%%hu%%m'
|
duration: '%%hu%%m'
|
||||||
pt_BR:
|
pt_BR:
|
||||||
date_time_type: 'dd-MM-yyyy HH:mm'
|
|
||||||
date_type: 'dd-MM-yyyy'
|
date_type: 'dd-MM-yyyy'
|
||||||
date: 'd-m-Y'
|
date: 'd-m-Y'
|
||||||
date_time: 'd-m H:i'
|
date_time: 'd-m H:i'
|
||||||
ru:
|
ru:
|
||||||
date_time_type: 'dd.MM.yyyy HH:mm'
|
|
||||||
date_type: 'dd.MM.yyyy'
|
date_type: 'dd.MM.yyyy'
|
||||||
date: 'd.m.Y'
|
date: 'd.m.Y'
|
||||||
date_time: 'd.m. H:i'
|
date_time: 'd.m. H:i'
|
||||||
sk:
|
sk:
|
||||||
date_time_type: 'dd. MM. yyyy HH:mm'
|
|
||||||
date_type: 'dd. MM. yyyy'
|
date_type: 'dd. MM. yyyy'
|
||||||
date: 'd. m. Y'
|
date: 'd. m. Y'
|
||||||
date_time: 'd. m. H:i'
|
date_time: 'd. m. H:i'
|
||||||
@@ -306,7 +289,6 @@ kimai:
|
|||||||
duration: '%%h:%%m tim'
|
duration: '%%h:%%m tim'
|
||||||
date_time: 'd/m H:i'
|
date_time: 'd/m H:i'
|
||||||
pl:
|
pl:
|
||||||
date_time_type: 'dd. MM. yyyy HH:mm'
|
|
||||||
date_type: 'dd. MM. yyyy'
|
date_type: 'dd. MM. yyyy'
|
||||||
date: 'd. m. Y'
|
date: 'd. m. Y'
|
||||||
date_time: 'd. m. H:i'
|
date_time: 'd. m. H:i'
|
||||||
|
|||||||
@@ -64,13 +64,12 @@ final class ConfigurationController extends BaseApiController
|
|||||||
|
|
||||||
$model = new I18nConfig();
|
$model = new I18nConfig();
|
||||||
$model
|
$model
|
||||||
->setFormDateTime($formats->getDateTimeTypeFormat($locale))
|
|
||||||
->setFormDate($formats->getDateTypeFormat($locale))
|
->setFormDate($formats->getDateTypeFormat($locale))
|
||||||
->setDateTime($formats->getDateTimeFormat($locale))
|
->setDateTime($formats->getDateTimeFormat($locale))
|
||||||
->setDate($formats->getDateFormat($locale))
|
->setDate($formats->getDateFormat($locale))
|
||||||
->setDuration($formats->getDurationFormat($locale))
|
->setDuration($formats->getDurationFormat($locale))
|
||||||
->setTime($formats->getTimeFormat($locale))
|
->setTime($formats->getTimeFormat($locale))
|
||||||
->setIs24hours($formats->isTwentyFourHours($locale))
|
->setIs24hours($user->is24Hour())
|
||||||
->setNow($this->getDateTimeFactory()->createDateTime())
|
->setNow($this->getDateTimeFactory()->createDateTime())
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -18,17 +18,6 @@ use JMS\Serializer\Annotation as Serializer;
|
|||||||
*/
|
*/
|
||||||
final class I18nConfig
|
final class I18nConfig
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Format used for 'begin' and 'end'
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*
|
|
||||||
* @Serializer\Expose()
|
|
||||||
* @Serializer\Groups({"Default"})
|
|
||||||
* @Serializer\Type(name="string")
|
|
||||||
* @phpstan-ignore-next-line
|
|
||||||
*/
|
|
||||||
private $formDateTime = '';
|
|
||||||
/**
|
/**
|
||||||
* Format used for toolbar queries
|
* Format used for toolbar queries
|
||||||
*
|
*
|
||||||
@@ -114,13 +103,6 @@ final class I18nConfig
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setFormDateTime(string $formDateTime): I18nConfig
|
|
||||||
{
|
|
||||||
$this->formDateTime = $formDateTime;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFormDate(string $formDate): I18nConfig
|
public function setFormDate(string $formDate): I18nConfig
|
||||||
{
|
{
|
||||||
$this->formDate = $formDate;
|
$this->formDate = $formDate;
|
||||||
|
|||||||
@@ -60,28 +60,6 @@ final class LanguageFormattings
|
|||||||
return $this->momentFormatter->convert($this->getDateTypeFormat($locale));
|
return $this->momentFormatter->convert($this->getDateTypeFormat($locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the format which is used by the form component to handle datetime values.
|
|
||||||
*
|
|
||||||
* @param string $locale
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDateTimeTypeFormat(string $locale): string
|
|
||||||
{
|
|
||||||
return $this->getConfig('date_time_type', $locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the format which is used by the Javascript component to handle datetime values.
|
|
||||||
*
|
|
||||||
* @param string $locale
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDateTimePickerFormat(string $locale): string
|
|
||||||
{
|
|
||||||
return $this->momentFormatter->convert($this->getDateTimeTypeFormat($locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the locale specific date format, which should be used in combination with the twig filter "|date".
|
* Returns the locale specific date format, which should be used in combination with the twig filter "|date".
|
||||||
*
|
*
|
||||||
@@ -126,17 +104,6 @@ final class LanguageFormattings
|
|||||||
return $this->getConfig('duration', $locale);
|
return $this->getConfig('duration', $locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether this locale uses the 24 hour format.
|
|
||||||
*
|
|
||||||
* @param string $locale
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isTwentyFourHours(string $locale): bool
|
|
||||||
{
|
|
||||||
return (bool) $this->getConfig('24_hours', $locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @param string $locale
|
* @param string $locale
|
||||||
|
|||||||
@@ -329,13 +329,19 @@ class Configuration implements ConfigurationInterface
|
|||||||
->useAttributeAsKey('name', false) // see https://github.com/symfony/symfony/issues/18988
|
->useAttributeAsKey('name', false) // see https://github.com/symfony/symfony/issues/18988
|
||||||
->arrayPrototype()
|
->arrayPrototype()
|
||||||
->children()
|
->children()
|
||||||
->scalarNode('date_time_type')->defaultValue('yyyy-MM-dd HH:mm')->end() // for DateTimeType
|
->scalarNode('date_time_type') // for DateTimeType
|
||||||
|
->defaultValue('yyyy-MM-dd HH:mm')
|
||||||
|
->setDeprecated('date_time_type is deprecated since 1.16 and was replaced by the 24 user configuration')
|
||||||
|
->end()
|
||||||
->scalarNode('date_type')->defaultValue('yyyy-MM-dd')->end() // for DateType
|
->scalarNode('date_type')->defaultValue('yyyy-MM-dd')->end() // for DateType
|
||||||
->scalarNode('date')->defaultValue('Y-m-d')->end() // for display via twig
|
->scalarNode('date')->defaultValue('Y-m-d')->end() // for display via twig
|
||||||
->scalarNode('date_time')->defaultValue('m-d H:i')->end() // for display via twig
|
->scalarNode('date_time')->defaultValue('m-d H:i')->end() // for display via twig
|
||||||
->scalarNode('duration')->defaultValue('%%h:%%m h')->end() // for display via twig
|
->scalarNode('duration')->defaultValue('%%h:%%m h')->end() // for display via twig
|
||||||
->scalarNode('time')->defaultValue('H:i')->end() // for display via twig
|
->scalarNode('time')->defaultValue('H:i')->end() // for display via twig
|
||||||
->booleanNode('24_hours')->defaultTrue()->end() // for DateTimeType JS component
|
->booleanNode('24_hours') // for DateTimeType JS component
|
||||||
|
->defaultTrue()
|
||||||
|
->setDeprecated('24_hours is deprecated since 1.16 and a user configuration now')
|
||||||
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -430,6 +430,20 @@ class User implements UserInterface, EquatableInterface, \Serializable
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTimeFormat(): string
|
||||||
|
{
|
||||||
|
if ($this->is24Hour()) {
|
||||||
|
return 'H:i';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'h:i A';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function is24Hour(): bool
|
||||||
|
{
|
||||||
|
return (bool) $this->getPreferenceValue(UserPreference::HOUR_24, true);
|
||||||
|
}
|
||||||
|
|
||||||
public function getLocale(): string
|
public function getLocale(): string
|
||||||
{
|
{
|
||||||
return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE);
|
return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class UserPreference
|
|||||||
public const INTERNAL_RATE = 'internal_rate';
|
public const INTERNAL_RATE = 'internal_rate';
|
||||||
public const SKIN = 'skin';
|
public const SKIN = 'skin';
|
||||||
public const LOCALE = 'language';
|
public const LOCALE = 'language';
|
||||||
|
public const HOUR_24 = 'hours_24';
|
||||||
public const TIMEZONE = 'timezone';
|
public const TIMEZONE = 'timezone';
|
||||||
public const FIRST_WEEKDAY = 'first_weekday';
|
public const FIRST_WEEKDAY = 'first_weekday';
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ final class UserPreferenceSubscriber implements EventSubscriberInterface
|
|||||||
->setSection('locale')
|
->setSection('locale')
|
||||||
->setType(FirstWeekDayType::class),
|
->setType(FirstWeekDayType::class),
|
||||||
|
|
||||||
|
(new UserPreference())
|
||||||
|
->setName(UserPreference::HOUR_24)
|
||||||
|
->setValue(true)
|
||||||
|
->setOrder(305)
|
||||||
|
->setSection('locale')
|
||||||
|
->setType(CheckboxType::class),
|
||||||
|
|
||||||
(new UserPreference())
|
(new UserPreference())
|
||||||
->setName(UserPreference::SKIN)
|
->setName(UserPreference::SKIN)
|
||||||
->setValue($this->configuration->getUserDefaultTheme())
|
->setValue($this->configuration->getUserDefaultTheme())
|
||||||
|
|||||||
@@ -10,11 +10,15 @@
|
|||||||
namespace App\Form\Type;
|
namespace App\Form\Type;
|
||||||
|
|
||||||
use App\API\BaseApiController;
|
use App\API\BaseApiController;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Utils\DateFormatConverter;
|
||||||
use App\Utils\LocaleSettings;
|
use App\Utils\LocaleSettings;
|
||||||
|
use App\Utils\MomentFormatConverter;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\Form\FormView;
|
use Symfony\Component\Form\FormView;
|
||||||
|
use Symfony\Component\OptionsResolver\Options;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,9 +38,6 @@ class DateTimePickerType extends AbstractType
|
|||||||
*/
|
*/
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
$dateTimePicker = $this->localeSettings->getDateTimePickerFormat();
|
|
||||||
$dateTimeFormat = $this->localeSettings->getDateTimeTypeFormat();
|
|
||||||
|
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'documentation' => [
|
'documentation' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
@@ -46,8 +47,18 @@ class DateTimePickerType extends AbstractType
|
|||||||
'label' => 'label.begin',
|
'label' => 'label.begin',
|
||||||
'widget' => 'single_text',
|
'widget' => 'single_text',
|
||||||
'html5' => false,
|
'html5' => false,
|
||||||
'format' => $dateTimeFormat,
|
'format' => function (Options $options) {
|
||||||
'format_picker' => $dateTimePicker,
|
/** @var User $user */
|
||||||
|
$user = $options['user'];
|
||||||
|
$converter = new DateFormatConverter();
|
||||||
|
|
||||||
|
return $this->localeSettings->getDateTypeFormat() . ' ' . $converter->convert($user->getTimeFormat()); // PHP
|
||||||
|
},
|
||||||
|
'format_picker' => function (Options $options) {
|
||||||
|
$converter = new MomentFormatConverter();
|
||||||
|
|
||||||
|
return $converter->convert($options['format']); // JS
|
||||||
|
},
|
||||||
'with_seconds' => false,
|
'with_seconds' => false,
|
||||||
'time_increment' => 1,
|
'time_increment' => 1,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use App\Entity\User;
|
|||||||
use App\Utils\LocaleFormats;
|
use App\Utils\LocaleFormats;
|
||||||
use App\Utils\LocaleFormatter;
|
use App\Utils\LocaleFormatter;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Twig\Extension\AbstractExtension;
|
use Twig\Extension\AbstractExtension;
|
||||||
use Twig\TwigFilter;
|
use Twig\TwigFilter;
|
||||||
use Twig\TwigFunction;
|
use Twig\TwigFunction;
|
||||||
@@ -22,10 +23,9 @@ use Twig\TwigTest;
|
|||||||
|
|
||||||
final class LocaleFormatExtensions extends AbstractExtension
|
final class LocaleFormatExtensions extends AbstractExtension
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var LanguageFormattings|null
|
|
||||||
*/
|
|
||||||
private $formats;
|
private $formats;
|
||||||
|
private $security;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var LocaleFormats|null
|
* @var LocaleFormats|null
|
||||||
*/
|
*/
|
||||||
@@ -38,10 +38,12 @@ final class LocaleFormatExtensions extends AbstractExtension
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $locale;
|
private $locale;
|
||||||
|
private $userFormat;
|
||||||
|
|
||||||
public function __construct(LanguageFormattings $formats)
|
public function __construct(LanguageFormattings $formats, Security $security)
|
||||||
{
|
{
|
||||||
$this->formats = $formats;
|
$this->formats = $formats;
|
||||||
|
$this->security = $security;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,7 +171,18 @@ final class LocaleFormatExtensions extends AbstractExtension
|
|||||||
*/
|
*/
|
||||||
public function dateTimeFull($date, bool $stripMidnight = false)
|
public function dateTimeFull($date, bool $stripMidnight = false)
|
||||||
{
|
{
|
||||||
return $this->getFormatter()->dateTimeFull($date, $stripMidnight);
|
return $this->getFormatter()->dateTimeFull($date, $this->getUserTimeFormat(), $stripMidnight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUserTimeFormat(): string
|
||||||
|
{
|
||||||
|
if ($this->userFormat === null) {
|
||||||
|
/** @var User|null $user */
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
$this->userFormat = $user !== null ? $user->getTimeFormat() : 'H:i';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->userFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createDate(string $date, ?User $user = null): \DateTime
|
public function createDate(string $date, ?User $user = null): \DateTime
|
||||||
@@ -201,7 +214,7 @@ final class LocaleFormatExtensions extends AbstractExtension
|
|||||||
*/
|
*/
|
||||||
public function time($date)
|
public function time($date)
|
||||||
{
|
{
|
||||||
return $this->getFormatter()->time($date);
|
return $this->getFormatter()->time($date, $this->getUserTimeFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -240,7 +253,16 @@ final class LocaleFormatExtensions extends AbstractExtension
|
|||||||
*/
|
*/
|
||||||
public function hour24($twentyFour, $twelveHour)
|
public function hour24($twentyFour, $twelveHour)
|
||||||
{
|
{
|
||||||
return $this->getFormatter()->hour24($twentyFour, $twelveHour);
|
@trigger_error('Twig filter "hour24" is deprecated, use app.user.is24Hour() instead', E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
/** @var User|null $user */
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if (null === $user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user->is24Hour();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDurationFormat(): string
|
public function getDurationFormat(): string
|
||||||
|
|||||||
33
src/Utils/DateFormatConverter.php
Normal file
33
src/Utils/DateFormatConverter.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Utils;
|
||||||
|
|
||||||
|
class DateFormatConverter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This defines the mapping between PHP date format (key) and ICU date format (value).
|
||||||
|
* https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static $formatConvertRules = [
|
||||||
|
// hours
|
||||||
|
'h' => 'hh', 'H' => 'HH',
|
||||||
|
// minutes
|
||||||
|
'i' => 'mm',
|
||||||
|
// am/pm to AM/PM
|
||||||
|
'A' => 'a'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function convert(string $format): string
|
||||||
|
{
|
||||||
|
return strtr($format, self::$formatConvertRules);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,21 +75,12 @@ class LocaleFormats
|
|||||||
/**
|
/**
|
||||||
* Returns the format which is used by the form component to handle datetime values.
|
* Returns the format which is used by the form component to handle datetime values.
|
||||||
*
|
*
|
||||||
|
* @deprecated since 1.16
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getDateTimeTypeFormat(): string
|
public function getDateTimeTypeFormat(): string
|
||||||
{
|
{
|
||||||
return $this->formats->getDateTimeTypeFormat($this->getLocale());
|
return $this->formats->getDateTypeFormat($this->getLocale()) . ' HH:mm';
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the format which is used by the Javascript component to handle datetime values.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDateTimePickerFormat(): string
|
|
||||||
{
|
|
||||||
return $this->formats->getDateTimePickerFormat($this->getLocale());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,14 +122,4 @@ class LocaleFormats
|
|||||||
{
|
{
|
||||||
return $this->formats->getDurationFormat($this->getLocale());
|
return $this->formats->getDurationFormat($this->getLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether this locale uses the 24 hour format.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isTwentyFourHours(): bool
|
|
||||||
{
|
|
||||||
return $this->formats->isTwentyFourHours($this->getLocale());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ final class LocaleFormatter
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $dateTimeFormat = null;
|
private $dateTimeFormat = null;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $dateTypeFormat = null;
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
@@ -54,10 +58,6 @@ final class LocaleFormatter
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $timeFormat = null;
|
private $timeFormat = null;
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $isTwentyFourHour = null;
|
|
||||||
|
|
||||||
public function __construct(LanguageFormattings $formats, string $locale)
|
public function __construct(LanguageFormattings $formats, string $locale)
|
||||||
{
|
{
|
||||||
@@ -216,6 +216,15 @@ final class LocaleFormatter
|
|||||||
return $date->format($this->dateFormat);
|
return $date->format($this->dateFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getDateTypeFormat(): string
|
||||||
|
{
|
||||||
|
if (null === $this->dateTypeFormat) {
|
||||||
|
$this->dateTypeFormat = $this->localeFormats->getDateTypeFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->dateTypeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DateTime|string $date
|
* @param DateTime|string $date
|
||||||
* @return string
|
* @return string
|
||||||
@@ -239,13 +248,15 @@ final class LocaleFormatter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DateTime|string $date
|
* @param DateTime|string $date
|
||||||
|
* @param string $timeFormat
|
||||||
* @param bool $stripMidnight
|
* @param bool $stripMidnight
|
||||||
* @return bool|false|string
|
* @return bool|false|string
|
||||||
*/
|
*/
|
||||||
public function dateTimeFull($date, bool $stripMidnight = false)
|
public function dateTimeFull($date, string $timeFormat, bool $stripMidnight = false)
|
||||||
{
|
{
|
||||||
if (null === $this->dateTimeTypeFormat) {
|
if (null === $this->dateTimeTypeFormat) {
|
||||||
$this->dateTimeTypeFormat = $this->localeFormats->getDateTimeTypeFormat();
|
$converter = new DateFormatConverter();
|
||||||
|
$this->dateTimeTypeFormat = $this->getDateTypeFormat() . ' ' . $converter->convert($timeFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$date instanceof DateTime) {
|
if (!$date instanceof DateTime) {
|
||||||
@@ -298,7 +309,7 @@ final class LocaleFormatter
|
|||||||
* @return string
|
* @return string
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function time($date)
|
public function time($date, string $format = null)
|
||||||
{
|
{
|
||||||
if (null === $this->timeFormat) {
|
if (null === $this->timeFormat) {
|
||||||
$this->timeFormat = $this->localeFormats->getTimeFormat();
|
$this->timeFormat = $this->localeFormats->getTimeFormat();
|
||||||
@@ -308,7 +319,7 @@ final class LocaleFormatter
|
|||||||
$date = new DateTime($date);
|
$date = new DateTime($date);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $date->format($this->timeFormat);
|
return $date->format($format ?? $this->timeFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -342,22 +353,4 @@ final class LocaleFormatter
|
|||||||
{
|
{
|
||||||
return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE'));
|
return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $twentyFour
|
|
||||||
* @param mixed $twelveHour
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function hour24($twentyFour, $twelveHour)
|
|
||||||
{
|
|
||||||
if (null === $this->isTwentyFourHour) {
|
|
||||||
$this->isTwentyFourHour = $this->localeFormats->isTwentyFourHours();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (true === $this->isTwentyFourHour) {
|
|
||||||
return $twentyFour;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $twelveHour;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ class MomentFormatConverter
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This defines the mapping between PHP ICU date format (key) and moment.js date format (value)
|
* This defines the mapping between PHP ICU date format (key) and moment.js date format (value)
|
||||||
* For ICU formats see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
|
* For ICU formats see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
|
||||||
* For Moment formats see http://momentjs.com/docs/#/displaying/format/.
|
* For Moment formats see http://momentjs.com/docs/#/displaying/format/
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@@ -34,6 +34,8 @@ class MomentFormatConverter
|
|||||||
'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ',
|
'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ',
|
||||||
// letter 'T'
|
// letter 'T'
|
||||||
'\'T\'' => 'T',
|
'\'T\'' => 'T',
|
||||||
|
// am/pm to AM/PM
|
||||||
|
'a' => 'A',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -296,7 +296,7 @@
|
|||||||
login: '{{ path('fos_user_security_login') }}',
|
login: '{{ path('fos_user_security_login') }}',
|
||||||
locale: '{{ app.request.locale }}',
|
locale: '{{ app.request.locale }}',
|
||||||
first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }},
|
first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }},
|
||||||
twentyFourHours: {{ 'true'|hour24('false') }},
|
twentyFourHours: {{ app.user.is24Hour() ? 'true' : 'false' }},
|
||||||
autoComplete: {{ kimai_config.themeAutocompleteCharacters }},
|
autoComplete: {{ kimai_config.themeAutocompleteCharacters }},
|
||||||
defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}',
|
defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}',
|
||||||
updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %}
|
updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %}
|
||||||
|
|||||||
@@ -309,7 +309,7 @@
|
|||||||
|
|
|
|
||||||
<label class="control-label" for="begin-format">
|
<label class="control-label" for="begin-format">
|
||||||
{{ 'label.begin'|trans }}:
|
{{ 'label.begin'|trans }}:
|
||||||
{% set demo_date = create_date('2020-01-01 13:00:00') %}
|
{% set demo_date = create_date('2020-01-01 11:00:00') %}
|
||||||
<select id="begin-format" name="begin-format">
|
<select id="begin-format" name="begin-format">
|
||||||
<option value="plain">{{ demo_date|date_format('H:i') }}</option>
|
<option value="plain">{{ demo_date|date_format('H:i') }}</option>
|
||||||
<option value="time">{{ demo_date|date_time }}</option>
|
<option value="time">{{ demo_date|date_time }}</option>
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ class ConfigurationControllerTest extends APIControllerBaseTest
|
|||||||
|
|
||||||
$this->assertIsArray($result);
|
$this->assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
$this->assertEquals(8, \count($result));
|
$this->assertEquals(7, \count($result));
|
||||||
$this->assertI18nStructure($result);
|
$this->assertI18nStructure($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertI18nStructure(array $result)
|
protected function assertI18nStructure(array $result)
|
||||||
{
|
{
|
||||||
$expectedKeys = ['date', 'dateTime', 'duration', 'formDate', 'formDateTime', 'is24hours', 'time', 'now'];
|
$expectedKeys = ['date', 'dateTime', 'duration', 'formDate', 'is24hours', 'time', 'now'];
|
||||||
$actual = array_keys($result);
|
$actual = array_keys($result);
|
||||||
sort($actual);
|
sort($actual);
|
||||||
sort($expectedKeys);
|
sort($expectedKeys);
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class I18nConfigTest extends TestCase
|
|||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setDate('bar'));
|
$this->assertInstanceOf(I18nConfig::class, $sut->setDate('bar'));
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setDateTime('hello'));
|
$this->assertInstanceOf(I18nConfig::class, $sut->setDateTime('hello'));
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDate('world'));
|
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDate('world'));
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDateTime('testing'));
|
|
||||||
$this->assertInstanceOf(I18nConfig::class, $sut->setTime('fun'));
|
$this->assertInstanceOf(I18nConfig::class, $sut->setTime('fun'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,61 +26,50 @@ class LanguageFormattingsTest extends TestCase
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'de' => [
|
'de' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'date_time' => 'd.m. H:i',
|
'date_time' => 'd.m. H:i',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
'time' => 'H:i',
|
'time' => 'H:i',
|
||||||
'24_hours' => true,
|
|
||||||
],
|
],
|
||||||
'en' => [
|
'en' => [
|
||||||
'date_time_type' => 'yyyy-MM-dd HH:mm',
|
|
||||||
'date_type' => 'yyyy-MM-dd',
|
'date_type' => 'yyyy-MM-dd',
|
||||||
'date' => 'Y-m-d',
|
'date' => 'Y-m-d',
|
||||||
'date_time' => 'm-d H:i',
|
'date_time' => 'm-d H:i',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
'time' => 'H:i:s',
|
'time' => 'H:i:s',
|
||||||
'24_hours' => false,
|
|
||||||
],
|
],
|
||||||
'pt_BR' => [
|
'pt_BR' => [
|
||||||
'date_time_type' => 'dd-MM-yyyy HH:mm',
|
|
||||||
'date_type' => 'dd-MM-yyyy',
|
'date_type' => 'dd-MM-yyyy',
|
||||||
'date' => 'd-m-Y',
|
'date' => 'd-m-Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'it' => [
|
'it' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'fr' => [
|
'fr' => [
|
||||||
'date_time_type' => 'dd/MM/yyyy HH:mm',
|
|
||||||
'date_type' => 'dd/MM/yyyy',
|
'date_type' => 'dd/MM/yyyy',
|
||||||
'date' => 'd/m/Y',
|
'date' => 'd/m/Y',
|
||||||
'duration' => '%h h %m',
|
'duration' => '%h h %m',
|
||||||
],
|
],
|
||||||
'es' => [
|
'es' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'ru' => [
|
'ru' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'ar' => [
|
'ar' => [
|
||||||
'date_time_type' => 'yyyy-MM-dd HH:mm',
|
|
||||||
'date_type' => 'yyyy-MM-dd',
|
'date_type' => 'yyyy-MM-dd',
|
||||||
'date' => 'Y-m-d',
|
'date' => 'Y-m-d',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'hu' => [
|
'hu' => [
|
||||||
'date_time_type' => 'yyyy.MM.dd HH:mm',
|
|
||||||
'date_type' => 'yyyy.MM.dd',
|
'date_type' => 'yyyy.MM.dd',
|
||||||
'date' => 'Y.m.d.',
|
'date' => 'Y.m.d.',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
@@ -136,40 +125,10 @@ class LanguageFormattingsTest extends TestCase
|
|||||||
$this->assertEquals('DD.MM.YYYY', $sut->getDatePickerFormat('de'));
|
$this->assertEquals('DD.MM.YYYY', $sut->getDatePickerFormat('de'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetDateTimeTypeFormat()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut($this->getDefaultSettings());
|
|
||||||
$this->assertEquals('dd.MM.yyyy HH:mm', $sut->getDateTimeTypeFormat('de'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetDateTimePickerFormat()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut($this->getDefaultSettings());
|
|
||||||
$this->assertEquals('DD.MM.YYYY HH:mm', $sut->getDateTimePickerFormat('de'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testIs24Hours()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut($this->getDefaultSettings());
|
|
||||||
$this->assertTrue($sut->isTwentyFourHours('de'));
|
|
||||||
$this->assertFalse($sut->isTwentyFourHours('en'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetTimeFormat()
|
public function testGetTimeFormat()
|
||||||
{
|
{
|
||||||
$sut = $this->getSut($this->getDefaultSettings());
|
$sut = $this->getSut($this->getDefaultSettings());
|
||||||
$this->assertEquals('H:i', $sut->getTimeFormat('de'));
|
$this->assertEquals('H:i', $sut->getTimeFormat('de'));
|
||||||
$this->assertEquals('H:i:s', $sut->getTimeFormat('en'));
|
$this->assertEquals('H:i:s', $sut->getTimeFormat('en'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnknownSetting()
|
|
||||||
{
|
|
||||||
$this->expectException(\InvalidArgumentException::class);
|
|
||||||
$this->expectExceptionMessage('Unknown setting for locale en: date_time_type');
|
|
||||||
|
|
||||||
$sut = $this->getSut(['en' => [
|
|
||||||
'xxx' => 'dd.MM.yyyy HH:mm',
|
|
||||||
]]);
|
|
||||||
$sut->getDateTimePickerFormat('en');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ class ProfileControllerTest extends ControllerBaseTest
|
|||||||
2 => ['name' => UserPreference::TIMEZONE, 'value' => 'America/Creston'],
|
2 => ['name' => UserPreference::TIMEZONE, 'value' => 'America/Creston'],
|
||||||
3 => ['name' => UserPreference::LOCALE, 'value' => 'ar'],
|
3 => ['name' => UserPreference::LOCALE, 'value' => 'ar'],
|
||||||
4 => ['name' => UserPreference::FIRST_WEEKDAY, 'value' => 'sunday'],
|
4 => ['name' => UserPreference::FIRST_WEEKDAY, 'value' => 'sunday'],
|
||||||
5 => ['name' => UserPreference::SKIN, 'value' => 'blue'],
|
6 => ['name' => UserPreference::SKIN, 'value' => 'blue'],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class UserTest extends TestCase
|
|||||||
self::assertFalse($user->canSeeAllData());
|
self::assertFalse($user->canSeeAllData());
|
||||||
self::assertFalse($user->isSmallLayout());
|
self::assertFalse($user->isSmallLayout());
|
||||||
self::assertFalse($user->isExportDecimal());
|
self::assertFalse($user->isExportDecimal());
|
||||||
|
self::assertTrue($user->is24Hour());
|
||||||
|
|
||||||
$user->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y');
|
$user->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y');
|
||||||
self::assertEquals('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', $user->getAvatar());
|
self::assertEquals('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', $user->getAvatar());
|
||||||
@@ -481,4 +482,15 @@ class UserTest extends TestCase
|
|||||||
$member->setUser(new User());
|
$member->setUser(new User());
|
||||||
$sut->addMembership($member);
|
$sut->addMembership($member);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test24Hour()
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
self::assertTrue($user->is24Hour());
|
||||||
|
self::assertEquals('H:i', $user->getTimeFormat());
|
||||||
|
|
||||||
|
$user->setPreferenceValue(UserPreference::HOUR_24, false);
|
||||||
|
self::assertFalse($user->is24Hour());
|
||||||
|
self::assertEquals('h:i A', $user->getTimeFormat());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class UserPreferenceSubscriberTest extends TestCase
|
|||||||
'language',
|
'language',
|
||||||
'first_weekday',
|
'first_weekday',
|
||||||
'skin',
|
'skin',
|
||||||
|
'hours_24',
|
||||||
'theme.layout',
|
'theme.layout',
|
||||||
'theme.collapsed_sidebar',
|
'theme.collapsed_sidebar',
|
||||||
'theme.update_browser_title',
|
'theme.update_browser_title',
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
|
|||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
abstract class AbstractRendererTest extends KernelTestCase
|
abstract class AbstractRendererTest extends KernelTestCase
|
||||||
@@ -52,8 +53,11 @@ abstract class AbstractRendererTest extends KernelTestCase
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->expects($this->any())->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
$translator = $this->createMock(TranslatorInterface::class);
|
$translator = $this->createMock(TranslatorInterface::class);
|
||||||
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages));
|
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages), $security);
|
||||||
|
|
||||||
$dispatcher = new EventDispatcher();
|
$dispatcher = new EventDispatcher();
|
||||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
||||||
|
|||||||
@@ -10,10 +10,14 @@
|
|||||||
namespace App\Tests\Export\Renderer;
|
namespace App\Tests\Export\Renderer;
|
||||||
|
|
||||||
use App\Activity\ActivityStatisticService;
|
use App\Activity\ActivityStatisticService;
|
||||||
|
use App\Entity\User;
|
||||||
use App\Export\Renderer\HtmlRenderer;
|
use App\Export\Renderer\HtmlRenderer;
|
||||||
use App\Project\ProjectStatisticService;
|
use App\Project\ProjectStatisticService;
|
||||||
|
use Symfony\Bridge\Twig\AppVariable;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +47,16 @@ class HtmlRendererTest extends AbstractRendererTest
|
|||||||
$kernel = self::bootKernel();
|
$kernel = self::bootKernel();
|
||||||
/** @var Environment $twig */
|
/** @var Environment $twig */
|
||||||
$twig = $kernel->getContainer()->get('twig');
|
$twig = $kernel->getContainer()->get('twig');
|
||||||
|
|
||||||
|
$token = $this->createMock(TokenInterface::class);
|
||||||
|
$token->expects($this->any())->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$tokenStorage = new TokenStorage();
|
||||||
|
$tokenStorage->setToken($token);
|
||||||
|
/** @var AppVariable $app */
|
||||||
|
$app = $twig->getGlobals()['app'];
|
||||||
|
$twig->addGlobal('app', $app);
|
||||||
|
$app->setTokenStorage($tokenStorage);
|
||||||
$stack = $kernel->getContainer()->get('request_stack');
|
$stack = $kernel->getContainer()->get('request_stack');
|
||||||
$request = new Request();
|
$request = new Request();
|
||||||
$request->setLocale('en');
|
$request->setLocale('en');
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
|
|||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
abstract class AbstractRendererTest extends KernelTestCase
|
abstract class AbstractRendererTest extends KernelTestCase
|
||||||
@@ -51,8 +52,11 @@ abstract class AbstractRendererTest extends KernelTestCase
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->expects($this->any())->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
|
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
|
||||||
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages));
|
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages), $security);
|
||||||
|
|
||||||
$dispatcher = new EventDispatcher();
|
$dispatcher = new EventDispatcher();
|
||||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use App\Entity\User;
|
|||||||
use App\Twig\LocaleFormatExtensions;
|
use App\Twig\LocaleFormatExtensions;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Twig\TwigFilter;
|
use Twig\TwigFilter;
|
||||||
use Twig\TwigFunction;
|
use Twig\TwigFunction;
|
||||||
use Twig\TwigTest;
|
use Twig\TwigTest;
|
||||||
@@ -43,7 +44,11 @@ class LocaleFormatExtensionsTest extends TestCase
|
|||||||
$language = $dateSettings;
|
$language = $dateSettings;
|
||||||
$dateSettings = $locale;
|
$dateSettings = $locale;
|
||||||
}
|
}
|
||||||
$sut = new LocaleFormatExtensions(new LanguageFormattings($dateSettings));
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->expects($this->any())->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$sut = new LocaleFormatExtensions(new LanguageFormattings($dateSettings), $security);
|
||||||
$sut->setLocale($language);
|
$sut->setLocale($language);
|
||||||
|
|
||||||
return $sut;
|
return $sut;
|
||||||
@@ -220,31 +225,21 @@ class LocaleFormatExtensionsTest extends TestCase
|
|||||||
$this->assertEquals('17:53', $sut->time('2016-06-23 17:53'));
|
$this->assertEquals('17:53', $sut->time('2016-06-23 17:53'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testHour24()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut('en', [
|
|
||||||
'en' => ['24_hours' => false],
|
|
||||||
]);
|
|
||||||
$this->assertEquals('bar', $sut->hour24('foo', 'bar'));
|
|
||||||
|
|
||||||
$sut = $this->getSut('de', [
|
|
||||||
'de' => ['24_hours' => true],
|
|
||||||
]);
|
|
||||||
$this->assertEquals('foo', $sut->hour24('foo', 'bar'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testDateTimeFull()
|
public function testDateTimeFull()
|
||||||
{
|
{
|
||||||
$sut = $this->getSut('en', [
|
$sut = $this->getSut('en', [
|
||||||
'en' => ['date_time_type' => 'yyyy-MM-dd HH:mm:ss'],
|
'en' => ['date_type' => 'dd-yyyy-MM-'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dateTime = new \DateTime('2019-08-17 12:29:47', new \DateTimeZone(date_default_timezone_get()));
|
$dateTime = new \DateTime('2019-08-17 12:29:47', new \DateTimeZone(date_default_timezone_get()));
|
||||||
$dateTime->setDate(2019, 8, 17);
|
$dateTime->setDate(2019, 8, 17);
|
||||||
$dateTime->setTime(12, 29, 47);
|
$dateTime->setTime(12, 29, 47);
|
||||||
|
|
||||||
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull($dateTime));
|
$this->assertEquals('17-2019-08- 12:29', $sut->dateTimeFull($dateTime));
|
||||||
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull('2019-08-17 12:29:47'));
|
$this->assertEquals('17-2019-08- 12:29', $sut->dateTimeFull('2019-08-17 12:29:47'));
|
||||||
|
|
||||||
|
$dateTime = new \DateTime('2019-08-17 00:00:00');
|
||||||
|
$this->assertEquals('17-2019-08-', $sut->dateTimeFull($dateTime, true));
|
||||||
|
|
||||||
// next test checks the fallback for errors while converting the date
|
// next test checks the fallback for errors while converting the date
|
||||||
/* @phpstan-ignore-next-line */
|
/* @phpstan-ignore-next-line */
|
||||||
|
|||||||
@@ -28,61 +28,50 @@ class LocaleFormatsTest extends TestCase
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'de' => [
|
'de' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'date_time' => 'd.m. H:i',
|
'date_time' => 'd.m. H:i',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
'time' => 'H:i',
|
'time' => 'H:i',
|
||||||
'24_hours' => true,
|
|
||||||
],
|
],
|
||||||
'en' => [
|
'en' => [
|
||||||
'date_time_type' => 'yyyy-MM-dd HH:mm',
|
|
||||||
'date_type' => 'yyyy-MM-dd',
|
'date_type' => 'yyyy-MM-dd',
|
||||||
'date' => 'Y-m-d',
|
'date' => 'Y-m-d',
|
||||||
'date_time' => 'm-d H:i',
|
'date_time' => 'm-d H:i',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
'time' => 'H:i:s',
|
'time' => 'H:i:s',
|
||||||
'24_hours' => false,
|
|
||||||
],
|
],
|
||||||
'pt_BR' => [
|
'pt_BR' => [
|
||||||
'date_time_type' => 'dd-MM-yyyy HH:mm',
|
|
||||||
'date_type' => 'dd-MM-yyyy',
|
'date_type' => 'dd-MM-yyyy',
|
||||||
'date' => 'd-m-Y',
|
'date' => 'd-m-Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'it' => [
|
'it' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'fr' => [
|
'fr' => [
|
||||||
'date_time_type' => 'dd/MM/yyyy HH:mm',
|
|
||||||
'date_type' => 'dd/MM/yyyy',
|
'date_type' => 'dd/MM/yyyy',
|
||||||
'date' => 'd/m/Y',
|
'date' => 'd/m/Y',
|
||||||
'duration' => '%h h %m',
|
'duration' => '%h h %m',
|
||||||
],
|
],
|
||||||
'es' => [
|
'es' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'ru' => [
|
'ru' => [
|
||||||
'date_time_type' => 'dd.MM.yyyy HH:mm',
|
|
||||||
'date_type' => 'dd.MM.yyyy',
|
'date_type' => 'dd.MM.yyyy',
|
||||||
'date' => 'd.m.Y',
|
'date' => 'd.m.Y',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'ar' => [
|
'ar' => [
|
||||||
'date_time_type' => 'yyyy-MM-dd HH:mm',
|
|
||||||
'date_type' => 'yyyy-MM-dd',
|
'date_type' => 'yyyy-MM-dd',
|
||||||
'date' => 'Y-m-d',
|
'date' => 'Y-m-d',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
],
|
],
|
||||||
'hu' => [
|
'hu' => [
|
||||||
'date_time_type' => 'yyyy.MM.dd HH:mm',
|
|
||||||
'date_type' => 'yyyy.MM.dd',
|
'date_type' => 'yyyy.MM.dd',
|
||||||
'date' => 'Y.m.d.',
|
'date' => 'Y.m.d.',
|
||||||
'duration' => '%h:%m h',
|
'duration' => '%h:%m h',
|
||||||
@@ -160,32 +149,9 @@ class LocaleFormatsTest extends TestCase
|
|||||||
$this->assertEquals('yyyy-MM-dd HH:mm', $sut->getDateTimeTypeFormat());
|
$this->assertEquals('yyyy-MM-dd HH:mm', $sut->getDateTimeTypeFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetDateTimePickerFormat()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut('en', $this->getDefaultSettings());
|
|
||||||
$this->assertEquals('YYYY-MM-DD HH:mm', $sut->getDateTimePickerFormat());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testIs24Hours()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut('en', $this->getDefaultSettings());
|
|
||||||
$this->assertFalse($sut->isTwentyFourHours());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetTimeFormat()
|
public function testGetTimeFormat()
|
||||||
{
|
{
|
||||||
$sut = $this->getSut('en', $this->getDefaultSettings());
|
$sut = $this->getSut('en', $this->getDefaultSettings());
|
||||||
$this->assertEquals('H:i:s', $sut->getTimeFormat());
|
$this->assertEquals('H:i:s', $sut->getTimeFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnknownSetting()
|
|
||||||
{
|
|
||||||
$this->expectException(\InvalidArgumentException::class);
|
|
||||||
$this->expectExceptionMessage('Unknown setting for locale en: date_time_type');
|
|
||||||
|
|
||||||
$sut = $this->getSut('en', ['en' => [
|
|
||||||
'xxx' => 'dd.MM.yyyy HH:mm',
|
|
||||||
]]);
|
|
||||||
$sut->getDateTimePickerFormat();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1089,7 +1089,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="crrlEUA" resname="preview.skipped_rows">
|
<trans-unit id="crrlEUA" resname="preview.skipped_rows">
|
||||||
<source>preview.skipped_rows</source>
|
<source>preview.skipped_rows</source>
|
||||||
<target>Überspringe Vorschau von %rows% weiteren Zeilen …</target>
|
<target>Überspringe Vorschau von %rows% weiteren Zeilen …</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<!--
|
<!--
|
||||||
Export
|
Export
|
||||||
@@ -1283,6 +1283,10 @@
|
|||||||
<source>quick_entry.title</source>
|
<source>quick_entry.title</source>
|
||||||
<target>Wochen-Arbeitsstunden</target>
|
<target>Wochen-Arbeitsstunden</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="61p_ckr" resname="label.hours_24">
|
||||||
|
<source>label.hours_24</source>
|
||||||
|
<target>24 Stunden</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -1283,6 +1283,10 @@
|
|||||||
<source>quick_entry.title</source>
|
<source>quick_entry.title</source>
|
||||||
<target>Weekly working hours</target>
|
<target>Weekly working hours</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="61p_ckr" resname="label.hours_24">
|
||||||
|
<source>label.hours_24</source>
|
||||||
|
<target>24 hours</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
Reference in New Issue
Block a user