diff --git a/config/locales.php b/config/locales.php index 838dd580..c9f671c2 100644 --- a/config/locales.php +++ b/config/locales.php @@ -4,1619 +4,1266 @@ 'date' => 'd‏/M‏/y', 'time' => 'h:mm a', 'rtl' => true, + 'translation' => true, ], 'cs' => [ 'date' => 'dd.MM.yy', 'time' => 'H:mm', 'rtl' => false, - ], - 'cs_CZ' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'H:mm', - 'rtl' => false, + 'translation' => true, ], 'da' => [ 'date' => 'dd.MM.y', 'time' => 'HH.mm', 'rtl' => false, - ], - 'da_DK' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH.mm', - 'rtl' => false, - ], - 'da_GL' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH.mm', - 'rtl' => false, + 'translation' => true, ], 'de' => [ 'date' => 'dd.MM.yy', 'time' => 'HH:mm', 'rtl' => false, - ], - 'de_AT' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'de_BE' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'de_CH' => [ 'date' => 'dd.MM.yy', 'time' => 'HH:mm', 'rtl' => false, - ], - 'de_DE' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'de_IT' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'de_LI' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'de_LU' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'el' => [ 'date' => 'd/M/yy', 'time' => 'h:mm a', 'rtl' => false, - ], - 'el_CY' => - [ - 'date' => 'd/M/yy', - 'time' => 'h:mm a', - 'rtl' => false, - ], - 'el_GR' => - [ - 'date' => 'd/M/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => true, ], 'en' => [ 'date' => 'M/d/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => true, ], 'en_AE' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_AG' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_AI' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'en_AS' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => false, ], 'en_AT' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_AU' => [ 'date' => 'd/M/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_BB' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_BE' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_BI' => [ 'date' => 'M/d/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_BM' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_BS' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_BW' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_BZ' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_CA' => [ 'date' => 'y-MM-dd', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_CC' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_CH' => [ 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_CK' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_CM' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_CX' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_CY' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_DE' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_DK' => [ 'date' => 'dd/MM/y', 'time' => 'HH.mm', 'rtl' => false, + 'translation' => false, ], 'en_DM' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_ER' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_FI' => [ 'date' => 'dd/MM/y', 'time' => 'H.mm', 'rtl' => false, + 'translation' => false, ], 'en_FJ' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_FK' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_FM' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_GB' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_GD' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_GG' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_GH' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_GI' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_GM' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'en_GU' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => false, ], 'en_GY' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_HK' => [ 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, + ], + 'en_ID' => + [ + 'date' => 'M/d/yy', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, ], 'en_IE' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_IL' => [ 'date' => 'dd/MM/y', 'time' => 'H:mm', 'rtl' => false, + 'translation' => false, ], 'en_IM' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_IN' => [ 'date' => 'dd/MM/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_IO' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_JE' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_JM' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_KE' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_KI' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_KN' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_KY' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_LC' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_LR' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_LS' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_MG' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'en_MH' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => false, ], 'en_MO' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'en_MP' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => false, ], 'en_MS' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_MT' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_MU' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_MV' => [ 'date' => 'd-M-yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_MW' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_MY' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_NA' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_NF' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_NG' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_NL' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_NR' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_NU' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_NZ' => [ 'date' => 'd/MM/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_PG' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'en_PH' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => false, ], 'en_PK' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_PN' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'en_PR' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => false, ], 'en_PW' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_RW' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_SB' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_SC' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_SD' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_SE' => [ 'date' => 'y-MM-dd', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_SG' => [ 'date' => 'd/M/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_SH' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_SI' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_SL' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_SS' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_SX' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_SZ' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_TC' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_TK' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_TO' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_TT' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_TV' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_TZ' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_UG' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'en_UM' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, - ], - 'en_US' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => false, ], 'en_VC' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_VG' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'en_VI' => - [ - 'date' => 'M/d/yy', - 'time' => 'h:mm a', - 'rtl' => false, + 'translation' => false, ], 'en_VU' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_WS' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_ZA' => [ 'date' => 'y/MM/dd', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'en_ZM' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'en_ZW' => [ 'date' => 'd/M/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'eo' => [ 'date' => 'yy-MM-dd', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'es' => [ 'date' => 'd/M/yy', 'time' => 'H:mm', 'rtl' => false, + 'translation' => true, ], 'es_AR' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_BO' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_BR' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_BZ' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_CL' => [ 'date' => 'dd-MM-yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_CO' => [ 'date' => 'd/MM/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'es_CR' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_CU' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_DO' => [ 'date' => 'd/M/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'es_EC' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, - ], - 'es_ES' => - [ - 'date' => 'd/M/yy', - 'time' => 'H:mm', - 'rtl' => false, - ], - 'es_GQ' => - [ - 'date' => 'd/M/yy', - 'time' => 'H:mm', - 'rtl' => false, + 'translation' => false, ], 'es_GT' => [ 'date' => 'd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_HN' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_MX' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_NI' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_PA' => [ 'date' => 'MM/dd/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'es_PE' => [ 'date' => 'd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_PH' => [ 'date' => 'd/M/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'es_PR' => [ 'date' => 'MM/dd/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'es_PY' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_SV' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_US' => [ 'date' => 'd/M/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'es_UY' => [ 'date' => 'd/M/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'es_VE' => [ 'date' => 'd/M/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'eu' => [ 'date' => 'yy/M/d', 'time' => 'HH:mm', 'rtl' => false, - ], - 'eu_ES' => - [ - 'date' => 'yy/M/d', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'fa' => [ 'date' => 'y/M/d', 'time' => 'H:mm', 'rtl' => true, - ], - 'fa_AF' => - [ - 'date' => 'y/M/d', - 'time' => 'H:mm', - 'rtl' => true, - ], - 'fa_IR' => - [ - 'date' => 'y/M/d', - 'time' => 'H:mm', - 'rtl' => true, + 'translation' => true, ], 'fi' => [ 'date' => 'd.M.y', 'time' => 'H.mm', 'rtl' => false, - ], - 'fi_FI' => - [ - 'date' => 'd.M.y', - 'time' => 'H.mm', - 'rtl' => false, + 'translation' => true, ], 'fo' => [ 'date' => 'dd.MM.yy', 'time' => 'HH:mm', 'rtl' => false, - ], - 'fo_DK' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fo_FO' => - [ - 'date' => 'dd.MM.yy', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'fr' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'fr_BE' => [ 'date' => 'd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, - ], - 'fr_BF' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_BI' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_BJ' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_BL' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'fr_CA' => [ 'date' => 'y-MM-dd', 'time' => 'HH \'h\' mm', 'rtl' => false, - ], - 'fr_CD' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_CF' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_CG' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'fr_CH' => [ 'date' => 'dd.MM.yy', 'time' => 'HH:mm', 'rtl' => false, - ], - 'fr_CI' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_CM' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'fr_DJ' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'fr_DZ' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'fr_FR' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_GA' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_GF' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_GN' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_GP' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_GQ' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_HT' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_KM' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_LU' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_MA' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_MC' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_MF' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_MG' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_ML' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_MQ' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'fr_MR' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'fr_MU' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_NC' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_NE' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_PF' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_PM' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_RE' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_RW' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_SC' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_SN' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'fr_SY' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'fr_TD' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'fr_TG' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'fr_TN' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'fr_VU' => [ 'date' => 'dd/MM/y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'fr_WF' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'fr_YT' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'he' => [ 'date' => 'd.M.y', 'time' => 'H:mm', 'rtl' => true, - ], - 'he_IL' => - [ - 'date' => 'd.M.y', - 'time' => 'H:mm', - 'rtl' => true, + 'translation' => true, ], 'hr' => [ 'date' => 'dd. MM. y.', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'hr_BA' => [ 'date' => 'd. M. yy.', 'time' => 'HH:mm', 'rtl' => false, - ], - 'hr_HR' => - [ - 'date' => 'dd. MM. y.', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'hu' => [ 'date' => 'y. MM. dd.', 'time' => 'H:mm', 'rtl' => false, + 'translation' => true, ], - 'hu_HU' => + 'id' => [ - 'date' => 'y. MM. dd.', - 'time' => 'H:mm', + 'date' => 'dd/MM/yy', + 'time' => 'HH.mm', 'rtl' => false, + 'translation' => true, ], 'it' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'it_CH' => [ 'date' => 'dd.MM.yy', 'time' => 'HH:mm', 'rtl' => false, - ], - 'it_IT' => - [ - 'date' => 'dd/MM/yy', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'it_SM' => - [ - 'date' => 'dd/MM/yy', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'it_VA' => - [ - 'date' => 'dd/MM/yy', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'ja' => [ 'date' => 'y/MM/dd', 'time' => 'H:mm', 'rtl' => false, - ], - 'ja_JP' => - [ - 'date' => 'y/MM/dd', - 'time' => 'H:mm', - 'rtl' => false, + 'translation' => true, ], 'ko' => [ 'date' => 'yy. M. d.', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => true, ], - 'ko_KP' => + 'ko_CN' => [ 'date' => 'yy. M. d.', - 'time' => 'h:mm a', - 'rtl' => false, - ], - 'ko_KR' => - [ - 'date' => 'yy. M. d.', - 'time' => 'h:mm a', + 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'nb_NO' => [ 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'nl' => [ 'date' => 'dd-MM-y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'nl_AW' => - [ - 'date' => 'dd-MM-y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'nl_BE' => [ 'date' => 'd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], - 'nl_BQ' => + 'pa' => [ - 'date' => 'dd-MM-y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'nl_CW' => - [ - 'date' => 'dd-MM-y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'nl_NL' => - [ - 'date' => 'dd-MM-y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'nl_SR' => - [ - 'date' => 'dd-MM-y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'nl_SX' => - [ - 'date' => 'dd-MM-y', - 'time' => 'HH:mm', + 'date' => 'd/M/yy', + 'time' => 'h:mm a', 'rtl' => false, + 'translation' => true, ], 'pl' => [ 'date' => 'd.MM.y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'pl_PL' => - [ - 'date' => 'd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'pt' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'pt_AO' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_BR' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'pt_CH' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_CV' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_GQ' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_GW' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_LU' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_MO' => [ 'date' => 'dd/MM/yy', 'time' => 'h:mm a', 'rtl' => false, + 'translation' => false, ], 'pt_MZ' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_PT' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_ST' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'pt_TL' => [ 'date' => 'dd/MM/yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => false, ], 'ro' => [ 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'ro_MD' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'ro_RO' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'ru' => [ 'date' => 'dd.MM.y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'ru_BY' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'ru_KG' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'ru_KZ' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'ru_MD' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'ru_RU' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'ru_UA' => - [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'sk' => [ 'date' => 'd. M. y', 'time' => 'H:mm', 'rtl' => false, + 'translation' => true, ], - 'sk_SK' => + 'sl' => [ - 'date' => 'd. M. y', - 'time' => 'H:mm', + 'date' => 'd. MM. yy', + 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'sv' => [ 'date' => 'y-MM-dd', 'time' => 'HH:mm', 'rtl' => false, - ], - 'sv_AX' => - [ - 'date' => 'y-MM-dd', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'sv_FI' => - [ - 'date' => 'y-MM-dd', - 'time' => 'HH:mm', - 'rtl' => false, - ], - 'sv_SE' => - [ - 'date' => 'y-MM-dd', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'tr' => [ 'date' => 'd.MM.y', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'tr_CY' => [ 'date' => 'd.MM.y', 'time' => 'h:mm a', 'rtl' => false, - ], - 'tr_TR' => - [ - 'date' => 'd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => false, ], 'uk' => [ 'date' => 'dd.MM.yy', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'vi' => [ 'date' => 'dd/MM/y', 'time' => 'HH:mm', 'rtl' => false, - ], - 'vi_VN' => - [ - 'date' => 'dd/MM/y', - 'time' => 'HH:mm', - 'rtl' => false, + 'translation' => true, ], 'zh_CN' => [ 'date' => 'y/M/d', 'time' => 'HH:mm', 'rtl' => false, + 'translation' => true, ], 'zh_Hant' => [ 'date' => 'y/M/d', 'time' => 'Bh:mm', 'rtl' => false, + 'translation' => true, ], ]; \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 1bdb80b0..ea136436 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,8 +1,7 @@ parameters: locale: en - # the original list of all locales is the next line, it can be used to regenerate the locale list in case new locales will be added - #app_locales: ar|cs|da|de|el|en|eo|es|eu|fa|fi|fo|fr|he|hr|hu|it|ja|ko|nb_NO|nl|pl|pt|ro|ru|sk|sv|tr|uk|vi|zh_CN|zh_Hant - app_locales: ar|cs|da|de|el|en|eo|es|eu|fa|fi|fo|fr|he|hr|hu|it|ja|ko|nb_NO|nl|pl|pt|pt_BR|ro|ru|sk|sv|tr|uk|vi|zh_CN|zh_Hant|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_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_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_NL|en_NR|en_NU|en_NZ|en_PG|en_PH|en_PK|en_PN|en_PR|en_PW|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_EC|es_ES|es_GQ|es_GT|es_HN|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_KP|ko_KR|nl_AW|nl_BE|nl_BQ|nl_CW|nl_NL|nl_SR|nl_SX|pl_PL|pt_AO|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|vi_VN + # can be regenerated with "bin/console kimai:reset:locales" + app_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|tr|uk|vi|zh_CN|zh_Hant|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 services: # default configuration for services in *this* file diff --git a/phpstan.neon b/phpstan.neon index c90086d3..e357de1b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -492,11 +492,6 @@ parameters: count: 1 path: src/Command/RegenerateLocalesCommand.php - - - message: "#^Property App\\\\Command\\\\RegenerateLocalesCommand\\:\\:\\$rtlLocales type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Command/RegenerateLocalesCommand.php - - message: "#^Cannot call method find\\(\\) on Symfony\\\\Component\\\\Console\\\\Application\\|null\\.$#" count: 4 @@ -652,26 +647,6 @@ parameters: count: 1 path: src/Configuration/LdapConfiguration.php - - - message: "#^Method App\\\\Configuration\\\\LocaleService\\:\\:__construct\\(\\) has parameter \\$languageSettings with no value type specified in iterable type array\\.$#" - count: 1 - path: src/Configuration/LocaleService.php - - - - message: "#^Method App\\\\Configuration\\\\LocaleService\\:\\:getDateFormat\\(\\) should return string but returns bool\\|string\\.$#" - count: 1 - path: src/Configuration/LocaleService.php - - - - message: "#^Method App\\\\Configuration\\\\LocaleService\\:\\:getTimeFormat\\(\\) should return string but returns bool\\|string\\.$#" - count: 1 - path: src/Configuration/LocaleService.php - - - - message: "#^Method App\\\\Configuration\\\\LocaleService\\:\\:isRightToLeft\\(\\) should return bool but returns bool\\|string\\.$#" - count: 1 - path: src/Configuration/LocaleService.php - - message: "#^Method App\\\\Configuration\\\\SamlConfiguration\\:\\:getAttributeMapping\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 @@ -1607,11 +1582,6 @@ parameters: count: 1 path: src/Entity/User.php - - - message: "#^Method App\\\\Entity\\\\User\\:\\:getLocale\\(\\) should return string but returns bool\\|float\\|int\\|string\\|null\\.$#" - count: 1 - path: src/Entity/User.php - - message: "#^Method App\\\\Entity\\\\User\\:\\:getMemberships\\(\\) return type with generic interface Doctrine\\\\Common\\\\Collections\\\\Collection does not specify its types\\: TKey, T$#" count: 1 @@ -5407,11 +5377,6 @@ parameters: count: 1 path: src/Utils/FileHelper.php - - - message: "#^Property App\\\\Utils\\\\FormFormatConverter\\:\\:\\$formatConvertRules type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/Utils/FormFormatConverter.php - - message: "#^Property App\\\\Utils\\\\JavascriptFormatConverter\\:\\:\\$formatConvertRules type has no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/API/Serializer/ValidationFailedExceptionErrorHandler.php b/src/API/Serializer/ValidationFailedExceptionErrorHandler.php index 8a66c15a..b0530434 100644 --- a/src/API/Serializer/ValidationFailedExceptionErrorHandler.php +++ b/src/API/Serializer/ValidationFailedExceptionErrorHandler.php @@ -83,7 +83,7 @@ final class ValidationFailedExceptionErrorHandler implements SubscribingHandlerI $user = $this->security->getUser(); if ($user !== null) { - $locale = $user->getLocale(); + $locale = $user->getLanguage(); } if (null !== $error->getPlural()) { diff --git a/src/Command/RegenerateLocalesCommand.php b/src/Command/RegenerateLocalesCommand.php index 783b6677..57bc096e 100644 --- a/src/Command/RegenerateLocalesCommand.php +++ b/src/Command/RegenerateLocalesCommand.php @@ -29,15 +29,22 @@ use Symfony\Component\Intl\Locales; #[AsCommand(name: 'kimai:reset:locales')] final class RegenerateLocalesCommand extends Command { - private string $defaultDate = 'dd.MM.y'; - private string $defaultTime = 'HH:mm'; - private array $rtlLocales = [ - 'ar' => true, - 'fa' => true, - 'he' => true, - ]; + /** + * @var string[] + */ + private array $rtlLocales = ['ar', 'fa', 'he']; + /** + * new locales were added here, to shrink the list a little bit + * this can be removed in the future, if there will ever be the need for it + * + * @var string[] + */ + private array $noRegionCode = ['ar', 'id', 'pa', 'sl']; - public function __construct(private LocaleService $localeService, private string $projectDirectory, private string $kernelEnvironment) + public function __construct( + private readonly string $projectDirectory, + private readonly string $kernelEnvironment + ) { parent::__construct(); } @@ -55,32 +62,44 @@ final class RegenerateLocalesCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $locales = $this->localeService->getAllLocales(); - // detect all registered locales and allow to choose them as well, so people get to - // choose the language for translation with the correct format of their location - /* + // find all available locales from the translation filenames + $translationFilenames = glob($this->projectDirectory . DIRECTORY_SEPARATOR . 'translations/*.xlf'); + if ($translationFilenames === false) { + $io->error('Failed reading translation files'); + + return Command::FAILURE; + } + $firstLevelLocales = []; + foreach ($translationFilenames as $file) { + $firstLevelLocales[] = explode('.', basename($file))[1]; + } + $firstLevelLocales = array_unique($firstLevelLocales); + $io->title('Locales found from translation files'); + $io->writeln(implode('|', $firstLevelLocales)); + $secondLevel = []; - foreach (Locales::getLocales() as $locale) { - if (substr_count($locale, '_') === 1) { - $baseLocale = substr($locale, 0, strpos($locale, '_')); - if (in_array($baseLocale, $locales)) { - $subLocale = substr($locale, strpos($locale, '_') + 1); - if (!is_numeric($subLocale)) { - $secondLevel[] = $locale; + foreach (Locales::getLocales() as $localeCode) { + $locale = explode('_', $localeCode); + if (\count($locale) === 2 && !\in_array($locale[0], $this->noRegionCode, true)) { + $baseLocale = $locale[0]; + if (\in_array($baseLocale, $firstLevelLocales)) { + $regionCode = $locale[1]; + if (!is_numeric($regionCode)) { + $secondLevel[] = $localeCode; } } } } - $locales = array_merge($locales, $secondLevel); - */ + + sort($firstLevelLocales); + sort($secondLevel); + + // keep the locales that have translation filesat the begin + // the config is than easier to read and the locales will be sorted in the UI anyway + $locales = array_merge($firstLevelLocales, $secondLevel); $appLocales = []; - $defaults = [ - 'date' => $this->defaultDate, - 'time' => $this->defaultTime, - 'rtl' => false, - ]; // make sure all allowed locales are registered foreach ($locales as $locale) { @@ -88,11 +107,13 @@ final class RegenerateLocalesCommand extends Command continue; } - $appLocales[$locale] = $defaults; + $appLocales[$locale] = LocaleService::DEFAULT_SETTINGS; } // 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); @@ -115,14 +136,50 @@ final class RegenerateLocalesCommand extends Command $rtlLocale = substr($rtlLocale, 0, strpos($rtlLocale, '_')); } - if (\array_key_exists($rtlLocale, $this->rtlLocales)) { - $settings['rtl'] = $this->rtlLocales[$rtlLocale]; - } + $settings['rtl'] = \in_array($rtlLocale, $this->rtlLocales, true); // pre-fill all formats with the default locale settings $appLocales[$locale] = $settings; } + $removableDuplicates = []; + foreach ($appLocales as $locale => $setting) { + $localeParts = explode('_', $locale); + if (\count($localeParts) === 1) { + continue; + } + // e.g. norwegian just exists with region code + if (!\array_key_exists($localeParts[0], $appLocales)) { + continue; + } + $baseLocaleSettings = $appLocales[$localeParts[0]]; + if ($baseLocaleSettings['time'] !== $setting['time']) { + continue; + } + if ($baseLocaleSettings['date'] !== $setting['date']) { + continue; + } + if ($setting['translation'] === true) { + continue; + } + if ($baseLocaleSettings['rtl'] !== $setting['rtl']) { + continue; + } + $removableDuplicates[] = $locale; + } + + $io->title('Redundant locales that will be skipped'); + $io->writeln(implode('|', $removableDuplicates)); + + foreach ($removableDuplicates as $duplicate) { + unset($appLocales[$duplicate]); + } + + // in the future this list should be reduced to the list of available translations, but for a long time users + // could choose from the entire list of all locales, so we likely have to keep that forever ... + $io->title('List of app_locales for services.yaml'); + $io->writeln(implode('|', $locales)); + ksort($appLocales); $filename = 'config/locales.php'; diff --git a/src/Command/UserLoginLinkCommand.php b/src/Command/UserLoginLinkCommand.php index f33f17dc..4553e982 100644 --- a/src/Command/UserLoginLinkCommand.php +++ b/src/Command/UserLoginLinkCommand.php @@ -70,7 +70,7 @@ final class UserLoginLinkCommand extends Command } $request = new Request(); - $request->setLocale($user->getLocale()); + $request->setLocale($user->getLanguage()); $this->requestStack->push($request); $loginLinkDetails = $this->loginLink->createLoginLink($user, $request); diff --git a/src/Configuration/LocaleService.php b/src/Configuration/LocaleService.php index 6a79ef10..a3a2133f 100644 --- a/src/Configuration/LocaleService.php +++ b/src/Configuration/LocaleService.php @@ -9,9 +9,21 @@ namespace App\Configuration; +use App\Entity\User; + final class LocaleService { - public function __construct(private array $languageSettings) + public const DEFAULT_SETTINGS = [ + 'date' => 'dd.MM.y', + 'time' => 'HH:mm', + 'rtl' => false, + 'translation' => false, + ]; + + /** + * @param array $languageSettings + */ + public function __construct(private readonly array $languageSettings) { } @@ -25,6 +37,18 @@ final class LocaleService return array_keys($this->languageSettings); } + /** + * Returns an array with all language codes that have translations. + * + * @return string[] + */ + public function getTranslatedLocales(): array + { + return array_keys(array_filter($this->languageSettings, function (array $setting) { + return $setting['translation']; + })); + } + public function isKnownLocale(string $language): bool { return \in_array($language, $this->getAllLocales()); @@ -38,7 +62,7 @@ final class LocaleService */ public function getDateFormat(string $locale): string { - return $this->getConfig('date', $locale); + return (string) $this->getConfig('date', $locale); } /** @@ -49,7 +73,7 @@ final class LocaleService */ public function getTimeFormat(string $locale): string { - return $this->getConfig('time', $locale); + return (string) $this->getConfig('time', $locale); } /** @@ -76,7 +100,34 @@ final class LocaleService public function isRightToLeft(string $locale): bool { - return $this->getConfig('rtl', $locale); + return (bool) $this->getConfig('rtl', $locale); + } + + public function isTranslated(string $locale): bool + { + return (bool) $this->getConfig('translation', $locale); + } + + public function getNearestTranslationLocale(string $locale): string + { + if (!$this->isKnownLocale($locale)) { + $parts = explode('_', $locale); + if (\count($parts) !== 2 || \strlen($parts[0]) !== 2 || !$this->isKnownLocale($parts[0])) { + return User::DEFAULT_LANGUAGE; + } + $locale = $parts[0]; + } + + if (!$this->isTranslated($locale)) { + $base = explode('_', $locale)[0]; + if (!$this->isTranslated($base)) { + return User::DEFAULT_LANGUAGE; + } + + return $base; + } + + return $locale; } public function is24Hour(string $locale): bool diff --git a/src/Controller/SystemConfigurationController.php b/src/Controller/SystemConfigurationController.php index 56c02405..8e262b89 100644 --- a/src/Controller/SystemConfigurationController.php +++ b/src/Controller/SystemConfigurationController.php @@ -22,13 +22,13 @@ use App\Form\Type\CustomerTypePatternType; use App\Form\Type\DatePickerType; use App\Form\Type\DateTimeTextType; use App\Form\Type\DayTimeType; -use App\Form\Type\LanguageType; use App\Form\Type\MinuteIncrementType; use App\Form\Type\ProjectTypePatternType; use App\Form\Type\RoundingModeType; use App\Form\Type\SkinType; use App\Form\Type\TimezoneType; use App\Form\Type\TrackingModeType; +use App\Form\Type\UserLanguageType; use App\Form\Type\WeekDaysType; use App\Form\Type\YesNoType; use App\Timesheet\LockdownService; @@ -504,7 +504,7 @@ final class SystemConfigurationController extends AbstractController ->setOptions(['help' => 'default_value_new']), (new Configuration('defaults.user.language')) ->setLabel('language') - ->setType(LanguageType::class) + ->setType(UserLanguageType::class) ->setOptions(['help' => 'default_value_new']), (new Configuration('defaults.user.theme')) ->setLabel('skin') diff --git a/src/Controller/WizardController.php b/src/Controller/WizardController.php index 6cfe7203..277d22ff 100644 --- a/src/Controller/WizardController.php +++ b/src/Controller/WizardController.php @@ -11,9 +11,10 @@ namespace App\Controller; use App\Entity\User; use App\Entity\UserPreference; -use App\Form\Type\LanguageType; use App\Form\Type\SkinType; use App\Form\Type\TimezoneType; +use App\Form\Type\UserLanguageType; +use App\Form\Type\UserLocaleType; use App\Form\UserPasswordType; use App\User\UserService; use Symfony\Component\Form\Extension\Core\Type\HiddenType; @@ -44,14 +45,16 @@ final class WizardController extends AbstractController if ($wizard === 'profile') { $data = [ - UserPreference::LOCALE => $request->getLocale(), + UserPreference::LANGUAGE => $user->getPreferenceValue(UserPreference::LANGUAGE, $request->getLocale(), false), + UserPreference::LOCALE => $user->getPreferenceValue(UserPreference::LOCALE, $request->getLocale(), false), UserPreference::TIMEZONE => $user->getTimezone(), UserPreference::SKIN => $user->getSkin(), 'reload' => '0', ]; $form = $this->createFormBuilder($data) - ->add(UserPreference::LOCALE, LanguageType::class) + ->add(UserPreference::LANGUAGE, UserLanguageType::class) + ->add(UserPreference::LOCALE, UserLocaleType::class, ['help' => null]) ->add(UserPreference::TIMEZONE, TimezoneType::class) ->add(UserPreference::SKIN, SkinType::class) ->add('reload', HiddenType::class) @@ -69,7 +72,8 @@ final class WizardController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { /** @var array $data */ $data = $form->getData(); - $user->setLanguage($data[UserPreference::LOCALE]); + $user->setLanguage($data[UserPreference::LANGUAGE]); + $user->setLocale($data[UserPreference::LOCALE]); $user->setTimezone($data[UserPreference::TIMEZONE]); $user->setPreferenceValue(UserPreference::SKIN, $data[UserPreference::SKIN]); $user->setWizardAsSeen('profile'); diff --git a/src/DependencyInjection/AppExtension.php b/src/DependencyInjection/AppExtension.php index 53780030..ef6c617c 100644 --- a/src/DependencyInjection/AppExtension.php +++ b/src/DependencyInjection/AppExtension.php @@ -9,6 +9,7 @@ namespace App\DependencyInjection; +use App\Configuration\LocaleService; use App\Kernel; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -112,12 +113,6 @@ final class AppExtension extends Extension $settings = include $directory . DIRECTORY_SEPARATOR . 'config/locales.php'; $appLocales = []; - $defaults = [ - 'date' => 'dd.MM.y', - 'time' => 'HH:mm', - 'rtl' => false, - ]; - // make sure all allowed locales are registered foreach ($locales as $locale) { // unlikely that a locale disappears, but in case that a new symfony update comes with changed locales @@ -125,7 +120,7 @@ final class AppExtension extends Extension continue; } - $appLocales[$locale] = $defaults; + $appLocales[$locale] = LocaleService::DEFAULT_SETTINGS; if (\array_key_exists($locale, $settings)) { $appLocales[$locale] = array_merge($appLocales[$locale], $settings[$locale]); diff --git a/src/Entity/User.php b/src/Entity/User.php index c3f92ac3..d35b3f23 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -318,7 +318,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas } /** - * Read-only list of of all visible user preferences. + * Read-only list of all visible user preferences. * * @internal only for API usage * @return UserPreference[] @@ -334,6 +334,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas $skip = [ UserPreference::TIMEZONE, UserPreference::LOCALE, + UserPreference::LANGUAGE, UserPreference::SKIN, 'calendar_initial_view', 'login_initial_view', @@ -406,13 +407,22 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas return null; } + /** + * The locale used for formatting number, money, dates and times + */ #[Serializer\VirtualProperty] - #[Serializer\SerializedName('language')] + #[Serializer\SerializedName('locale')] #[Serializer\Groups(['User_Entity'])] #[OA\Property(type: 'string')] public function getLocale(): string { - return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE, false); + // uses language as fallback, because the language was here before + return (string) $this->getPreferenceValue(UserPreference::LOCALE, $this->getLanguage(), false); + } + + public function setLocale(?string $locale): void + { + $this->setPreferenceValue(UserPreference::LOCALE, $locale ?? User::DEFAULT_LANGUAGE); } #[Serializer\VirtualProperty] @@ -424,17 +434,21 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas return $this->getPreferenceValue(UserPreference::TIMEZONE, date_default_timezone_get(), false); } + /** + * The locale used for translations + */ + #[Serializer\VirtualProperty] + #[Serializer\SerializedName('language')] + #[Serializer\Groups(['User_Entity'])] + #[OA\Property(type: 'string')] public function getLanguage(): string { - return $this->getLocale(); + return (string) $this->getPreferenceValue(UserPreference::LANGUAGE, User::DEFAULT_LANGUAGE, false); } public function setLanguage(?string $language): void { - if ($language === null) { - $language = User::DEFAULT_LANGUAGE; - } - $this->setPreferenceValue(UserPreference::LOCALE, $language); + $this->setPreferenceValue(UserPreference::LANGUAGE, $language ?? User::DEFAULT_LANGUAGE); } public function isFirstDayOfWeekSunday(): bool diff --git a/src/Entity/UserPreference.php b/src/Entity/UserPreference.php index 42ae54d3..9cb55ad9 100644 --- a/src/Entity/UserPreference.php +++ b/src/Entity/UserPreference.php @@ -28,7 +28,8 @@ class UserPreference public const HOURLY_RATE = 'hourly_rate'; public const INTERNAL_RATE = 'internal_rate'; public const SKIN = 'skin'; - public const LOCALE = 'language'; + public const LANGUAGE = 'language'; + public const LOCALE = 'locale'; public const TIMEZONE = 'timezone'; public const FIRST_WEEKDAY = 'first_weekday'; public const WORK_HOURS_MONDAY = 'work_monday'; diff --git a/src/EventSubscriber/ThemeOptionsSubscriber.php b/src/EventSubscriber/ThemeOptionsSubscriber.php index 5c8f8433..18fe1bfc 100644 --- a/src/EventSubscriber/ThemeOptionsSubscriber.php +++ b/src/EventSubscriber/ThemeOptionsSubscriber.php @@ -24,7 +24,11 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInt */ final class ThemeOptionsSubscriber implements EventSubscriberInterface { - public function __construct(private TokenStorageInterface $storage, private ContextHelper $helper, private LocaleService $localeService) + public function __construct( + private readonly TokenStorageInterface $storage, + private readonly ContextHelper $helper, + private readonly LocaleService $localeService + ) { } @@ -44,7 +48,7 @@ final class ThemeOptionsSubscriber implements EventSubscriberInterface $this->helper->setAssetVersion((string) Constants::VERSION_ID); - if ($this->localeService->isRightToLeft(\Locale::getDefault())) { + if ($this->localeService->isRightToLeft($event->getRequest()->getLocale())) { $this->helper->setIsRightToLeft(true); } diff --git a/src/EventSubscriber/UserEnvironmentSubscriber.php b/src/EventSubscriber/UserEnvironmentSubscriber.php index d165d143..f989b4bb 100644 --- a/src/EventSubscriber/UserEnvironmentSubscriber.php +++ b/src/EventSubscriber/UserEnvironmentSubscriber.php @@ -10,6 +10,7 @@ namespace App\EventSubscriber; use App\Entity\User; +use App\Twig\LocaleFormatExtensions; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -18,7 +19,11 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; final class UserEnvironmentSubscriber implements EventSubscriberInterface { - public function __construct(private TokenStorageInterface $tokenStorage, private AuthorizationCheckerInterface $auth) + public function __construct( + private readonly TokenStorageInterface $tokenStorage, + private readonly AuthorizationCheckerInterface $auth, + private readonly LocaleFormatExtensions $localeFormatExtensions + ) { } @@ -36,19 +41,21 @@ final class UserEnvironmentSubscriber implements EventSubscriberInterface return; } - // the locale depends on the request, not on the user configuration - \Locale::setDefault($event->getRequest()->getLocale()); + $locale = $event->getRequest()->getLocale(); - // ignore events like the toolbar where we do not have a token - if (null === ($token = $this->tokenStorage->getToken())) { - return; + // events like the toolbar might not have a token + if (null !== ($token = $this->tokenStorage->getToken())) { + $user = $token->getUser(); + + if ($user instanceof User) { + $locale = $user->getLocale(); + date_default_timezone_set($user->getTimezone()); + $user->initCanSeeAllData($this->auth->isGranted('view_all_data')); + } } - $user = $token->getUser(); - - if ($user instanceof User) { - date_default_timezone_set($user->getTimezone()); - $user->initCanSeeAllData($this->auth->isGranted('view_all_data')); - } + // the locale is primarily used for formatting values, so we depend on the user locale if available + \Locale::setDefault($locale); + $this->localeFormatExtensions->setLocale($locale); } } diff --git a/src/EventSubscriber/UserPreferenceSubscriber.php b/src/EventSubscriber/UserPreferenceSubscriber.php index d0d83d99..934a66c5 100644 --- a/src/EventSubscriber/UserPreferenceSubscriber.php +++ b/src/EventSubscriber/UserPreferenceSubscriber.php @@ -22,6 +22,7 @@ use App\Form\Type\InitialViewType; use App\Form\Type\SkinType; use App\Form\Type\TimezoneType; use App\Form\Type\UserLanguageType; +use App\Form\Type\UserLocaleType; use App\Form\Type\YesNoType; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -84,11 +85,16 @@ final class UserPreferenceSubscriber implements EventSubscriberInterface ->setSection('locale') ->setType(TimezoneType::class), - (new UserPreference(UserPreference::LOCALE, $this->systemConfiguration->getUserDefaultLanguage())) + (new UserPreference(UserPreference::LANGUAGE, $this->systemConfiguration->getUserDefaultLanguage())) ->setOrder(250) ->setSection('locale') ->setType(UserLanguageType::class), + (new UserPreference(UserPreference::LOCALE, $this->systemConfiguration->getUserDefaultLanguage())) + ->setOrder(250) + ->setSection('locale') + ->setType(UserLocaleType::class), + (new UserPreference(UserPreference::FIRST_WEEKDAY, User::DEFAULT_FIRST_WEEKDAY)) ->setOrder(300) ->setSection('locale') diff --git a/src/Form/Helper/ProjectHelper.php b/src/Form/Helper/ProjectHelper.php index 7ea72b4e..233d4250 100644 --- a/src/Form/Helper/ProjectHelper.php +++ b/src/Form/Helper/ProjectHelper.php @@ -12,10 +12,9 @@ namespace App\Form\Helper; use App\Configuration\LocaleService; use App\Configuration\SystemConfiguration; use App\Entity\Project; -use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; -final class ProjectHelper implements LocaleAwareInterface +final class ProjectHelper { public const PATTERN_NAME = '{name}'; public const PATTERN_COMMENT = '{comment}'; @@ -33,7 +32,11 @@ final class ProjectHelper implements LocaleAwareInterface private bool $showEnd = false; private ?string $locale = null; - public function __construct(private SystemConfiguration $configuration, private LocaleService $localeService, private TranslatorInterface $translator) + public function __construct( + private readonly SystemConfiguration $configuration, + private readonly LocaleService $localeService, + private readonly TranslatorInterface $translator + ) { } @@ -42,7 +45,7 @@ final class ProjectHelper implements LocaleAwareInterface return $this->locale ?? \Locale::getDefault(); } - public function setLocale(string $locale): void + public function setLocale(?string $locale): void { $this->locale = $locale; } diff --git a/src/Form/Type/LanguageType.php b/src/Form/Type/LanguageType.php index 6751f0d0..e1c288a8 100644 --- a/src/Form/Type/LanguageType.php +++ b/src/Form/Type/LanguageType.php @@ -13,6 +13,7 @@ use App\Configuration\LocaleService; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Intl\Locales; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -20,21 +21,32 @@ use Symfony\Component\OptionsResolver\OptionsResolver; */ final class LanguageType extends AbstractType { - public function __construct(private LocaleService $localeService) + public function __construct(private readonly LocaleService $localeService) { } public function configureOptions(OptionsResolver $resolver): void { - $choices = []; - foreach ($this->localeService->getAllLocales() as $key) { - $name = ucfirst(Locales::getName($key, $key)); - $choices[$name] = $key; - } + $resolver->setDefault('choices', function (Options $options) { + $choices = []; + + if ($options['translated_only'] === true) { + $locales = $this->localeService->getTranslatedLocales(); + } else { + $locales = $this->localeService->getAllLocales(); + } + + foreach ($locales as $key) { + $name = ucfirst(Locales::getName($key, $key)); + $choices[$name] = $key; + } + + return $choices; + }); $resolver->setDefaults([ - 'choices' => $choices, 'label' => 'language', + 'translated_only' => false, 'choice_translation_domain' => false, ]); } diff --git a/src/Form/Type/TimePickerType.php b/src/Form/Type/TimePickerType.php index f2af56d8..34bcc936 100644 --- a/src/Form/Type/TimePickerType.php +++ b/src/Form/Type/TimePickerType.php @@ -16,6 +16,8 @@ use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; final class TimePickerType extends AbstractType @@ -40,6 +42,11 @@ final class TimePickerType extends AbstractType ]); } + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['format'] = $options['format']; + } + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->addModelTransformer( diff --git a/src/Form/Type/UserLanguageType.php b/src/Form/Type/UserLanguageType.php index b808c9f0..2432961c 100644 --- a/src/Form/Type/UserLanguageType.php +++ b/src/Form/Type/UserLanguageType.php @@ -9,30 +9,43 @@ namespace App\Form\Type; +use App\Configuration\LocaleService; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Contracts\Translation\TranslatorInterface; /** - * Custom form field type to select the user language. + * Custom form field type to select the user language, which is used to translate the UI. * @extends AbstractType */ final class UserLanguageType extends AbstractType { - public function __construct(private UrlGeneratorInterface $router, private TranslatorInterface $translator) + public function __construct(private readonly LocaleService $localeService) { } + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addModelTransformer(new CallbackTransformer( + function ($value) { + if ($value === null) { + return null; + } + + return $this->localeService->getNearestTranslationLocale($value); + }, + function ($value) { + return $value; + } + )); + } + public function configureOptions(OptionsResolver $resolver): void { - $route = $this->router->generate('help_locales'); - $message = $this->translator->trans('user.language.help'); - $moreLink = $this->translator->trans('help_locales'); - $resolver->setDefaults([ - 'help_html' => true, - 'help' => sprintf('%2$s %3$s', $route, $message, $moreLink) + 'label' => 'language', + 'translated_only' => true, ]); } diff --git a/src/Form/Type/UserLocaleType.php b/src/Form/Type/UserLocaleType.php new file mode 100644 index 00000000..9d484ddc --- /dev/null +++ b/src/Form/Type/UserLocaleType.php @@ -0,0 +1,47 @@ + + */ +final class UserLocaleType extends AbstractType +{ + public function __construct( + private readonly UrlGeneratorInterface $router, + private readonly TranslatorInterface $translator + ) + { + } + + public function configureOptions(OptionsResolver $resolver): void + { + $route = $this->router->generate('help_locales'); + $moreLink = $this->translator->trans('help_locales'); + + $resolver->setDefaults([ + 'label' => 'locale', + 'help_html' => true, + 'help' => sprintf('%2$s', $route, $moreLink) + ]); + } + + public function getParent(): string + { + return LanguageType::class; + } +} diff --git a/src/Form/UserEditType.php b/src/Form/UserEditType.php index 81771492..8057dfa0 100644 --- a/src/Form/UserEditType.php +++ b/src/Form/UserEditType.php @@ -15,6 +15,7 @@ use App\Form\Type\AvatarType; use App\Form\Type\MailType; use App\Form\Type\TimezoneType; use App\Form\Type\UserLanguageType; +use App\Form\Type\UserLocaleType; use App\Form\Type\UserType; use App\Form\Type\YesNoType; use Symfony\Component\Form\AbstractType; @@ -80,6 +81,10 @@ class UserEditType extends AbstractType 'required' => true, ]); + $builder->add('locale', UserLocaleType::class, [ + 'required' => true, + ]); + $builder->add('timezone', TimezoneType::class, [ 'required' => true, ]); diff --git a/src/Utils/FormFormatConverter.php b/src/Utils/FormFormatConverter.php index bff3c7f7..64f2fdc9 100644 --- a/src/Utils/FormFormatConverter.php +++ b/src/Utils/FormFormatConverter.php @@ -24,11 +24,11 @@ final class FormFormatConverter * This defines the mapping between ICU date format and PHP Date format. * * @see https://www.php.net/manual/en/datetime.format.php - * @var array + * @var array */ private static array $formatConvertRules = [ // Litepicker interprets a year like 22 as 1922 instead of 2022 - // so we have to make sure that it is always a4-digit year + // so we have to make sure that it is always a 4-digit year "'h'" => "\h", // special format for fr_CA which includes 'h' as character 'yy' => 'yyyy', 'y' => 'yyyy', @@ -47,10 +47,6 @@ final class FormFormatConverter /** * This works with ICU and DateTime format. - * - * @param string $format - * @param bool $html - * @return string */ public function convertToPattern(string $format, bool $html = true): string { @@ -83,8 +79,8 @@ final class FormFormatConverter $pattern = str_replace('g', self::PATTERN_HOUR_SINGLE, $pattern); $pattern = str_replace('i', self::PATTERN_MINUTES, $pattern); $pattern = str_replace('mm', self::PATTERN_MINUTES, $pattern); - $pattern = str_replace('A', '(AM|PM){1}', $pattern); - $pattern = str_replace('a', '(AM|PM){1}', $pattern); + $pattern = str_replace('A', '(AM|PM|am|pm){1}', $pattern); + $pattern = str_replace('a', '(AM|PM|am|pm){1}', $pattern); $pattern = str_replace('*****', 'h', $pattern); if (!$html) { diff --git a/templates/form/blocks.html.twig b/templates/form/blocks.html.twig index 4e3aed46..3cb501c3 100644 --- a/templates/form/blocks.html.twig +++ b/templates/form/blocks.html.twig @@ -139,6 +139,7 @@ {%- endblock date_widget %} {% block time_widget -%} + {%- set user_format = format -%} {%- set format = locale_format('time') -%} {%- set jsFormat = format|js_format -%} {%- set attr = attr|merge({'pattern': format|pattern, 'autocomplete': 'off', 'data-timepicker': 'on', 'data-format': jsFormat, 'placeholder': jsFormat}) -%} @@ -155,7 +156,7 @@ {% for index in [0, 1, 2, 3] %}