diff --git a/config/locales.php b/config/locales.php index 6122d9a3..2af55968 100644 --- a/config/locales.php +++ b/config/locales.php @@ -8,15 +8,15 @@ ], 'cs' => [ - 'date' => 'dd.MM.yy', - 'time' => 'H:mm', + 'date' => 'dd.MM.y', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'cs_CZ' => [ - 'date' => 'dd.MM.yy', - 'time' => 'H:mm', + 'date' => 'dd.MM.y', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], @@ -43,84 +43,84 @@ ], 'de' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'de_AT' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'de_BE' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'de_CH' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'de_DE' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'de_IT' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'de_LI' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'de_LU' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'el' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => true, ], 'el_CY' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'el_GR' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'en' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => true, @@ -148,7 +148,7 @@ ], 'en_AS' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -162,7 +162,7 @@ ], 'en_AU' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -176,14 +176,14 @@ ], 'en_BE' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'en_BI' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -204,14 +204,14 @@ ], 'en_BW' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'en_BZ' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -265,6 +265,13 @@ 'rtl' => false, 'translation' => false, ], + 'en_CZ' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_DE' => [ 'date' => 'dd/MM/y', @@ -293,10 +300,17 @@ 'rtl' => false, 'translation' => false, ], + 'en_ES' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_FI' => [ 'date' => 'dd/MM/y', - 'time' => 'H.mm', + 'time' => 'HH.mm', 'rtl' => false, 'translation' => false, ], @@ -321,6 +335,13 @@ 'rtl' => false, 'translation' => false, ], + 'en_FR' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_GB' => [ 'date' => 'dd/MM/y', @@ -363,9 +384,16 @@ 'rtl' => false, 'translation' => false, ], + 'en_GS' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_GU' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -384,6 +412,13 @@ 'rtl' => false, 'translation' => false, ], + 'en_HU' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_ID' => [ 'date' => 'dd/MM/y', @@ -401,7 +436,7 @@ 'en_IL' => [ 'date' => 'dd/MM/y', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], @@ -414,7 +449,7 @@ ], 'en_IN' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -426,6 +461,13 @@ 'rtl' => false, 'translation' => false, ], + 'en_IT' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_JE' => [ 'date' => 'dd/MM/y', @@ -498,7 +540,7 @@ ], 'en_MH' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -512,7 +554,7 @@ ], 'en_MP' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -540,7 +582,7 @@ ], 'en_MV' => [ - 'date' => 'd-M-yy', + 'date' => 'd-M-y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -587,6 +629,13 @@ 'rtl' => false, 'translation' => false, ], + 'en_NO' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_NR' => [ 'date' => 'dd/MM/y', @@ -617,7 +666,7 @@ ], 'en_PH' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -629,6 +678,13 @@ 'rtl' => false, 'translation' => false, ], + 'en_PL' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_PN' => [ 'date' => 'dd/MM/y', @@ -638,11 +694,18 @@ ], 'en_PR' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], + 'en_PT' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_PW' => [ 'date' => 'dd/MM/y', @@ -650,6 +713,13 @@ 'rtl' => false, 'translation' => false, ], + 'en_RO' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_RW' => [ 'date' => 'dd/MM/y', @@ -687,7 +757,7 @@ ], 'en_SG' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -706,6 +776,13 @@ 'rtl' => false, 'translation' => false, ], + 'en_SK' => + [ + 'date' => 'M/d/y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ], 'en_SL' => [ 'date' => 'dd/MM/y', @@ -785,14 +862,14 @@ ], 'en_UM' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'en_US' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -813,7 +890,7 @@ ], 'en_VI' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -855,168 +932,168 @@ ], 'eo' => [ - 'date' => 'yy-MM-dd', + 'date' => 'y-MM-dd', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'es' => [ - 'date' => 'd/M/yy', - 'time' => 'H:mm', + 'date' => 'd/M/y', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'es_AR' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_BO' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_BR' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'es_BZ' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'es_CL' => [ - 'date' => 'dd-MM-yy', + 'date' => 'dd-MM-y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_CO' => [ - 'date' => 'd/MM/yy', + 'date' => 'd/MM/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_CR' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_CU' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_DO' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_EC' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_ES' => [ - 'date' => 'd/M/yy', - 'time' => 'H:mm', + 'date' => 'd/M/y', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'es_GQ' => [ - 'date' => 'd/M/yy', - 'time' => 'H:mm', + 'date' => 'd/M/y', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'es_GT' => [ - 'date' => 'd/MM/yy', + 'date' => 'd/MM/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_HN' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_MX' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_NI' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_PA' => [ - 'date' => 'MM/dd/yy', + 'date' => 'MM/dd/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_PE' => [ - 'date' => 'd/MM/yy', + 'date' => 'd/MM/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_PH' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_PR' => [ - 'date' => 'MM/dd/yy', + 'date' => 'MM/dd/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_PY' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_SV' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -1030,28 +1107,28 @@ ], 'es_UY' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'es_VE' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'eu' => [ - 'date' => 'yy/M/d', + 'date' => 'y/M/d', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'eu_ES' => [ - 'date' => 'yy/M/d', + 'date' => 'y/M/d', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1059,55 +1136,55 @@ 'fa' => [ 'date' => 'y/M/d', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => true, 'translation' => true, ], 'fa_AF' => [ 'date' => 'y/M/d', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => true, 'translation' => false, ], 'fa_IR' => [ 'date' => 'y/M/d', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => true, 'translation' => false, ], 'fi' => [ 'date' => 'd.M.y', - 'time' => 'H.mm', + 'time' => 'HH.mm', 'rtl' => false, 'translation' => true, ], 'fi_FI' => [ 'date' => 'd.M.y', - 'time' => 'H.mm', + 'time' => 'HH.mm', 'rtl' => false, 'translation' => false, ], 'fo' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'fo_DK' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'fo_FO' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1121,7 +1198,7 @@ ], 'fr_BE' => [ - 'date' => 'd/MM/yy', + 'date' => 'd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1184,7 +1261,7 @@ ], 'fr_CH' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1444,14 +1521,14 @@ 'he' => [ 'date' => 'd.M.y', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => true, 'translation' => true, ], 'he_IL' => [ 'date' => 'd.M.y', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => true, 'translation' => false, ], @@ -1464,7 +1541,7 @@ ], 'hr_BA' => [ - 'date' => 'd. M. yy.', + 'date' => 'd. M. y.', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1479,55 +1556,55 @@ 'hu' => [ 'date' => 'y. MM. dd.', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'hu_HU' => [ 'date' => 'y. MM. dd.', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'id' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH.mm', 'rtl' => false, 'translation' => true, ], 'it' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'it_CH' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'it_IT' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'it_SM' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'it_VA' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1535,41 +1612,41 @@ 'ja' => [ 'date' => 'y/MM/dd', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'ja_JP' => [ 'date' => 'y/MM/dd', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'ko' => [ - 'date' => 'yy. M. d.', + 'date' => 'y. M. d.', 'time' => 'h:mm a', 'rtl' => false, 'translation' => true, ], 'ko_CN' => [ - 'date' => 'yy. M. d.', + 'date' => 'y. M. d.', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'ko_KP' => [ - 'date' => 'yy. M. d.', + 'date' => 'y. M. d.', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'ko_KR' => [ - 'date' => 'yy. M. d.', + 'date' => 'y. M. d.', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, @@ -1639,7 +1716,7 @@ ], 'pa' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => true, @@ -1667,7 +1744,7 @@ ], 'pt_AO' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1681,70 +1758,70 @@ ], 'pt_CH' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'pt_CV' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'pt_GQ' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'pt_GW' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'pt_LU' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'pt_MO' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => false, ], 'pt_MZ' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'pt_PT' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'pt_ST' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'pt_TL' => [ - 'date' => 'dd/MM/yy', + 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1822,20 +1899,20 @@ 'sk' => [ 'date' => 'd. M. y', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'sk_SK' => [ 'date' => 'd. M. y', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'sl' => [ - 'date' => 'd. M. yy', + 'date' => 'd. M. y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, @@ -1870,7 +1947,7 @@ ], 'ta' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => true, @@ -1898,28 +1975,28 @@ ], 'uk' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'uk_UA' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, ], 'vi' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'vi_VN' => [ - 'date' => 'd/M/yy', + 'date' => 'd/M/y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => false, @@ -1934,14 +2011,14 @@ 'zh_Hant' => [ 'date' => 'y/M/d', - 'time' => 'Bh:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'zh_Hant_TW' => [ 'date' => 'y/M/d', - 'time' => 'Bh:mm', + 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], diff --git a/config/services.yaml b/config/services.yaml index 441b232c..3fec7dfc 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,7 +1,7 @@ parameters: locale: en # can be regenerated with: bin/console kimai:reset:locales - kimai_locales: ['ar', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'ta', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GU', 'en_GY', 'en_HK', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PN', 'en_PR', 'en_PW', 'en_RH', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN'] + kimai_locales: ['ar', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'ta', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_CZ', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_ES', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_FR', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GS', 'en_GU', 'en_GY', 'en_HK', 'en_HU', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_IT', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NO', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PL', 'en_PN', 'en_PR', 'en_PT', 'en_PW', 'en_RH', 'en_RO', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SK', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN'] services: # default configuration for services in *this* file diff --git a/src/Command/RegenerateLocalesCommand.php b/src/Command/RegenerateLocalesCommand.php index d0eb290d..a5147c93 100644 --- a/src/Command/RegenerateLocalesCommand.php +++ b/src/Command/RegenerateLocalesCommand.php @@ -10,6 +10,7 @@ namespace App\Command; use App\Configuration\LocaleService; +use App\Utils\LocaleFormatter; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -41,7 +42,7 @@ final class RegenerateLocalesCommand extends Command */ private array $noRegionCode = ['ar', 'id', 'pa', 'sl', 'ca', 'ta']; /** - * A list of locales that will be activated, no matter if translation files exist for them. + * A list of locales that will be activated no matter if translation files exist for them. * * @var string[] */ @@ -122,14 +123,17 @@ final class RegenerateLocalesCommand extends Command $appLocales[$locale] = LocaleService::DEFAULT_SETTINGS; } + $timeFormats = []; + $dateFormats = []; + // make sure all keys are registered for every locale foreach ($appLocales as $locale => $settings) { $settings['translation'] = \in_array($locale, $firstLevelLocales, true); // these are completely new since v2 // calculate everything with IntlFormatter - $shortDate = new \IntlDateFormatter($locale, \IntlDateFormatter::SHORT, \IntlDateFormatter::NONE); - $shortTime = new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, \IntlDateFormatter::SHORT); + $shortDate = new \IntlDateFormatter($locale, LocaleFormatter::DATE_PATTERN, \IntlDateFormatter::NONE); + $shortTime = new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, LocaleFormatter::TIME_PATTERN); $settings['date'] = $shortDate->getPattern(); if ($settings['date'] === false) { @@ -142,14 +146,28 @@ final class RegenerateLocalesCommand extends Command continue; } - // see https://github.com/kimai/kimai/issues/4402 - Korean time format failed parsing - // special case when time pattern starts with A / a => this will lead to an error + // CHINESE: contains the format character B - see https://github.com/kimai/kimai/issues/5496 + // It is an equivalent for "a" and acts like am/pm but will be prefixed instead of written after the time. + // This clashes with PHP Date format "B" (Swatch Internet time) and fails in other places, so we convert it into 24-hour format. + if (str_contains($settings['time'], 'Bh')) { + $settings['time'] = str_replace('Bh', 'H', $settings['time']); + } + + // KOREAN: time format failed parsing - see https://github.com/kimai/kimai/issues/4402 + // Special case where time-patterns start with A / a => this will lead to an error // \DateTimeImmutable::getLastErrors() => Meridian can only come after an hour has been found if (str_contains($settings['time'], 'a ')) { $settings['time'] = str_replace('a ', '', $settings['time']) . ' a'; } $settings['time'] = str_replace("\u{202f}", ' ', $settings['time']); + // keep it simple, we don't need to convert it during runtime + $settings['time'] = str_replace('HH', 'H', $settings['time']); + $settings['time'] = str_replace('H', 'HH', $settings['time']); + + // format the year always with 4 letters - ISO-8601 + $settings['date'] = str_replace('yy', 'y', $settings['date']); + // make sure that sub-locales of a RTL language are also flagged as RTL $rtlLocale = $locale; if (substr_count($rtlLocale, '_') === 1) { @@ -160,6 +178,9 @@ final class RegenerateLocalesCommand extends Command // pre-fill all formats with the default locale settings $appLocales[$locale] = $settings; + + $timeFormats[$settings['time']] = $settings['time']; + $dateFormats[$settings['date']] = $settings['date']; } $removableDuplicates = []; @@ -230,6 +251,12 @@ final class RegenerateLocalesCommand extends Command $io->success('Created new locale definition at: ' . $filename); + $io->writeln(\sprintf('Found %s date formats:', \count($dateFormats))); + $io->listing(array_keys($dateFormats)); + + $io->writeln(\sprintf('Found %s time formats:', \count($timeFormats))); + $io->listing(array_keys($timeFormats)); + return Command::SUCCESS; } } diff --git a/src/Configuration/LocaleService.php b/src/Configuration/LocaleService.php index 58c09051..240ff301 100644 --- a/src/Configuration/LocaleService.php +++ b/src/Configuration/LocaleService.php @@ -10,12 +10,13 @@ namespace App\Configuration; use App\Entity\User; +use App\Utils\Duration; final class LocaleService { public const DEFAULT_SETTINGS = [ 'date' => 'dd.MM.y', - 'time' => 'HH:mm', + 'time' => 'H:mm', 'rtl' => false, 'translation' => false, ]; @@ -55,10 +56,7 @@ final class LocaleService } /** - * Returns the locale specific date format, which should be used in combination with the twig filter "|date". - * - * @param string $locale - * @return string + * Returns the locale-specific date format, which should be used in combination with the twig filter "|date". */ public function getDateFormat(string $locale): string { @@ -66,10 +64,7 @@ final class LocaleService } /** - * Returns the locale specific time format, which should be used in combination with the twig filter "|time". - * - * @param string $locale - * @return string + * Returns the locale-specific time format, which should be used in combination with the twig filter "|time". */ public function getTimeFormat(string $locale): string { @@ -77,10 +72,7 @@ final class LocaleService } /** - * Returns the locale specific datetime format, which should be used in combination with the twig filter "|date". - * - * @param string $locale - * @return string + * Returns the locale-specific datetime format, which should be used in combination with the twig filter "|date". */ public function getDateTimeFormat(string $locale): string { @@ -89,13 +81,10 @@ final class LocaleService /** * Returns the format used in the "|duration" twig filter to display a Timesheet duration. - * - * @param string $locale - * @return string */ public function getDurationFormat(string $locale): string { - return '%h:%m'; + return Duration::FORMAT_DEFAULT; } public function isRightToLeft(string $locale): bool diff --git a/src/Controller/HelpController.php b/src/Controller/HelpController.php index 6aa09cd0..20936346 100644 --- a/src/Controller/HelpController.php +++ b/src/Controller/HelpController.php @@ -32,6 +32,8 @@ final class HelpController extends AbstractController $table = new DataTable('help_locales', new BaseQuery()); $table->addColumn('name', ['class' => 'alwaysVisible', 'orderBy' => false]); $table->addColumn('description', ['class' => 'd-none', 'orderBy' => false]); + $table->addColumn('date_format', ['class' => 'd-none w-min', 'orderBy' => false, 'title' => 'Date format']); + $table->addColumn('time_format', ['class' => 'd-none w-min', 'orderBy' => false, 'title' => 'Time format']); $table->addColumn('language', ['class' => 'd-none w-min', 'orderBy' => false]); $table->addColumn('date', ['class' => 'alwaysVisible w-min', 'orderBy' => false]); $table->addColumn('time', ['class' => 'alwaysVisible w-min text-center', 'orderBy' => false]); @@ -73,6 +75,8 @@ final class HelpController extends AbstractController 'language' => $locale, 'name' => Locales::getName($locale, $locale), 'description' => Locales::getName($locale, $requestLocale), + 'time_format' => $service->getTimeFormat($locale), + 'date_format' => $service->getDateFormat($locale), 'date' => $formatter->dateShort($now), 'time' => $formatter->time($now), 'duration' => $formatter->duration(46120), diff --git a/src/Utils/FormFormatConverter.php b/src/Utils/FormFormatConverter.php index 64f2fdc9..0ae55b37 100644 --- a/src/Utils/FormFormatConverter.php +++ b/src/Utils/FormFormatConverter.php @@ -56,6 +56,9 @@ final class FormFormatConverter $pattern = $format; + // special case for Chinese + $pattern = str_replace('B', '', $pattern); + // special case fr_CA $pattern = str_replace('\\\\h', '*****', $pattern); $pattern = str_replace('\\h', '*****', $pattern); diff --git a/src/Utils/LocaleFormatter.php b/src/Utils/LocaleFormatter.php index e7479b8f..8245846e 100644 --- a/src/Utils/LocaleFormatter.php +++ b/src/Utils/LocaleFormatter.php @@ -18,10 +18,33 @@ use NumberFormatter; use Symfony\Component\Intl\Currencies; /** - * Use this class to format values into locale specific representations. + * Use this class to format values into locale-specific representations. */ final class LocaleFormatter { + /** + * Special locales to test: + * + * - el h:mm a 9:41 π.μ. + * - fr_CA HH 'h' mm 09 h 41 + * - ko h:mm a 10:18 오전 + * - tr_CY h:mm a 9:41 ÖÖ + * - pa h:mm a 10:18 ਪੂ.ਦੁ. + * - pt_MO h:mm a 9:41 da manhã + * - zh_Hant Bh:mm 上午9:41 + * - zh_Hant_TW Bh:mm 上午9:41 + */ + // IntlDateFormatter::RELATIVE_* - not usable for times + // IntlDateFormatter::LONG - with seconds and timezone, and translation for h and min if locale supports it + // IntlDateFormatter::MEDIUM - with seconds, and translation for h and min if locale supports it + // IntlDateFormatter::SHORT - no seconds, no timezone - but translates e.g. am/pm to locale specific like zh_Hant + public const TIME_PATTERN = \IntlDateFormatter::SHORT; + // IntlDateFormatter::RELATIVE_* - translates words for "today" and "yesterday" + // IntlDateFormatter::LONG - translates the month name + // IntlDateFormatter::MEDIUM - date as we likely want to use it, but with translations if locale supports it, e.g. 2025年5月26日 + // IntlDateFormatter::SHORT - date with dots, but year with two numbers in some locales causing conflicts e.g., in invoices + public const DATE_PATTERN = \IntlDateFormatter::SHORT; + private ?Duration $durationFormatter = null; private ?IntlDateFormatter $dateFormatter = null; private ?IntlDateFormatter $dateTimeFormatter = null; @@ -44,10 +67,7 @@ final class LocaleFormatter return $this->durationDecimal($duration); } - return $this->formatDuration( - $this->getSecondsForDuration($duration), - $this->localeService->getDurationFormat($this->locale) - ); + return $this->formatDuration($this->getSecondsForDuration($duration)); } /** @@ -84,13 +104,13 @@ final class LocaleFormatter return (int) $duration; } - private function formatDuration(int $seconds, string $format): string + private function formatDuration(int $seconds): string { if ($this->durationFormatter === null) { $this->durationFormatter = new Duration(); } - return $this->durationFormatter->format($seconds, $format); + return $this->durationFormatter->format($seconds); } /** @@ -170,8 +190,8 @@ final class LocaleFormatter if (null === $this->dateFormatter) { $this->dateFormatter = new IntlDateFormatter( $this->locale, - IntlDateFormatter::MEDIUM, - IntlDateFormatter::MEDIUM, + self::DATE_PATTERN, + IntlDateFormatter::NONE, date_default_timezone_get(), IntlDateFormatter::GREGORIAN, $this->localeService->getDateFormat($this->locale) @@ -204,8 +224,8 @@ final class LocaleFormatter if (null === $this->dateTimeFormatter) { $this->dateTimeFormatter = new IntlDateFormatter( $this->locale, - IntlDateFormatter::MEDIUM, - IntlDateFormatter::MEDIUM, + self::DATE_PATTERN, + self::TIME_PATTERN, date_default_timezone_get(), IntlDateFormatter::GREGORIAN, $this->localeService->getDateTimeFormat($this->locale) @@ -255,8 +275,8 @@ final class LocaleFormatter if (null === $this->timeFormatter) { $this->timeFormatter = new IntlDateFormatter( $this->locale, - IntlDateFormatter::MEDIUM, - IntlDateFormatter::MEDIUM, + IntlDateFormatter::NONE, + self::TIME_PATTERN, date_default_timezone_get(), IntlDateFormatter::GREGORIAN, $this->localeService->getTimeFormat($this->locale) diff --git a/templates/help/index.html.twig b/templates/help/index.html.twig index 5b68bd85..b84a868d 100644 --- a/templates/help/index.html.twig +++ b/templates/help/index.html.twig @@ -1,6 +1,10 @@ {% extends 'datatable.html.twig' %} {% import "macros/widgets.html.twig" as widgets %} +{% block datatable_column_attr %} + {%- if entry.rtl %} dir="rtl"{% endif -%} +{% endblock %} + {% block datatable_column_value %} {% if column == 'name' %} {{ entry.name }} @@ -18,6 +22,10 @@ {{ entry.decimal }} {% elseif column == 'money' %} {{ entry.money }} + {% elseif column == 'time_format' %} + {{ entry.time_format }} + {% elseif column == 'date_format' %} + {{ entry.date_format }} {% elseif column == 'hour_24' %} {{ widgets.label_boolean(entry.hour_24) }} {% elseif column == 'rtl' %} diff --git a/tests/DependencyInjection/AppExtensionTest.php b/tests/DependencyInjection/AppExtensionTest.php index feb919da..a86b9f82 100644 --- a/tests/DependencyInjection/AppExtensionTest.php +++ b/tests/DependencyInjection/AppExtensionTest.php @@ -68,20 +68,20 @@ class AppExtensionTest extends TestCase 'kimai.data_dir' => '/tmp/', 'kimai.languages' => [ 'en' => [ - 'date' => 'M/d/yy', + 'date' => 'M/d/y', 'time' => 'h:mm a', 'rtl' => false, 'translation' => true, ], 'de' => [ - 'date' => 'dd.MM.yy', + 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, 'translation' => true, ], 'he' => [ 'date' => 'd.M.y', - 'time' => 'H:mm', + 'time' => 'HH:mm', 'rtl' => true, 'translation' => true, ],