L10N.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  6. * SPDX-License-Identifier: AGPL-3.0-only
  7. */
  8. namespace OC\L10N;
  9. use OCP\IL10N;
  10. use OCP\L10N\IFactory;
  11. use Psr\Log\LoggerInterface;
  12. use Punic\Calendar;
  13. use Symfony\Component\Translation\IdentityTranslator;
  14. class L10N implements IL10N {
  15. /** @var IFactory */
  16. protected $factory;
  17. /** @var string App of this object */
  18. protected $app;
  19. /** @var string Language of this object */
  20. protected $lang;
  21. /** @var string Locale of this object */
  22. protected $locale;
  23. /** @var IdentityTranslator */
  24. private $identityTranslator;
  25. /** @var string[] */
  26. private $translations = [];
  27. /**
  28. * @param IFactory $factory
  29. * @param string $app
  30. * @param string $lang
  31. * @param string $locale
  32. * @param array $files
  33. */
  34. public function __construct(IFactory $factory, $app, $lang, $locale, array $files) {
  35. $this->factory = $factory;
  36. $this->app = $app;
  37. $this->lang = $lang;
  38. $this->locale = $locale;
  39. foreach ($files as $languageFile) {
  40. $this->load($languageFile);
  41. }
  42. }
  43. /**
  44. * The code (en, de, ...) of the language that is used for this instance
  45. *
  46. * @return string language
  47. */
  48. public function getLanguageCode(): string {
  49. return $this->lang;
  50. }
  51. /**
  52. * The code (en_US, fr_CA, ...) of the locale that is used for this instance
  53. *
  54. * @return string locale
  55. */
  56. public function getLocaleCode(): string {
  57. return $this->locale;
  58. }
  59. /**
  60. * Translating
  61. * @param string $text The text we need a translation for
  62. * @param array|string $parameters default:array() Parameters for sprintf
  63. * @return string Translation or the same text
  64. *
  65. * Returns the translation. If no translation is found, $text will be
  66. * returned.
  67. */
  68. public function t(string $text, $parameters = []): string {
  69. if (!\is_array($parameters)) {
  70. $parameters = [$parameters];
  71. }
  72. return (string)new L10NString($this, $text, $parameters);
  73. }
  74. /**
  75. * Translating
  76. * @param string $text_singular the string to translate for exactly one object
  77. * @param string $text_plural the string to translate for n objects
  78. * @param integer $count Number of objects
  79. * @param array $parameters default:array() Parameters for sprintf
  80. * @return string Translation or the same text
  81. *
  82. * Returns the translation. If no translation is found, $text will be
  83. * returned. %n will be replaced with the number of objects.
  84. *
  85. * The correct plural is determined by the plural_forms-function
  86. * provided by the po file.
  87. *
  88. */
  89. public function n(string $text_singular, string $text_plural, int $count, array $parameters = []): string {
  90. $identifier = "_{$text_singular}_::_{$text_plural}_";
  91. if (isset($this->translations[$identifier])) {
  92. return (string)new L10NString($this, $identifier, $parameters, $count);
  93. }
  94. if ($count === 1) {
  95. return (string)new L10NString($this, $text_singular, $parameters, $count);
  96. }
  97. return (string)new L10NString($this, $text_plural, $parameters, $count);
  98. }
  99. /**
  100. * Localization
  101. * @param string $type Type of localization
  102. * @param \DateTime|int|string $data parameters for this localization
  103. * @param array $options
  104. * @return string|int|false
  105. *
  106. * Returns the localized data.
  107. *
  108. * Implemented types:
  109. * - date
  110. * - Creates a date
  111. * - params: timestamp (int/string)
  112. * - datetime
  113. * - Creates date and time
  114. * - params: timestamp (int/string)
  115. * - time
  116. * - Creates a time
  117. * - params: timestamp (int/string)
  118. * - firstday: Returns the first day of the week (0 sunday - 6 saturday)
  119. * - jsdate: Returns the short JS date format
  120. */
  121. public function l(string $type, $data = null, array $options = []) {
  122. if ($this->locale === null) {
  123. // Use the language of the instance
  124. $this->locale = $this->getLanguageCode();
  125. }
  126. if ($this->locale === 'sr@latin') {
  127. $this->locale = 'sr_latn';
  128. }
  129. if ($type === 'firstday') {
  130. return (int)Calendar::getFirstWeekday($this->locale);
  131. }
  132. if ($type === 'jsdate') {
  133. return (string)Calendar::getDateFormat('short', $this->locale);
  134. }
  135. $value = new \DateTime();
  136. if ($data instanceof \DateTime) {
  137. $value = $data;
  138. } elseif (\is_string($data) && !is_numeric($data)) {
  139. $data = strtotime($data);
  140. $value->setTimestamp($data);
  141. } elseif ($data !== null) {
  142. $data = (int)$data;
  143. $value->setTimestamp($data);
  144. }
  145. $options = array_merge(['width' => 'long'], $options);
  146. $width = $options['width'];
  147. switch ($type) {
  148. case 'date':
  149. return (string)Calendar::formatDate($value, $width, $this->locale);
  150. case 'datetime':
  151. return (string)Calendar::formatDatetime($value, $width, $this->locale);
  152. case 'time':
  153. return (string)Calendar::formatTime($value, $width, $this->locale);
  154. case 'weekdayName':
  155. return (string)Calendar::getWeekdayName($value, $width, $this->locale);
  156. default:
  157. return false;
  158. }
  159. }
  160. /**
  161. * Returns an associative array with all translations
  162. *
  163. * Called by \OC_L10N_String
  164. * @return array
  165. */
  166. public function getTranslations(): array {
  167. return $this->translations;
  168. }
  169. /**
  170. * @internal
  171. * @return IdentityTranslator
  172. */
  173. public function getIdentityTranslator(): IdentityTranslator {
  174. if (\is_null($this->identityTranslator)) {
  175. $this->identityTranslator = new IdentityTranslator();
  176. // We need to use the language code here instead of the locale,
  177. // because Symfony does not distinguish between the two and would
  178. // otherwise e.g. with locale "Czech" and language "German" try to
  179. // pick a non-existing plural rule, because Czech has 4 plural forms
  180. // and German only 2.
  181. $this->identityTranslator->setLocale($this->getLanguageCode());
  182. }
  183. return $this->identityTranslator;
  184. }
  185. /**
  186. * @param string $translationFile
  187. * @return bool
  188. */
  189. protected function load(string $translationFile): bool {
  190. $json = json_decode(file_get_contents($translationFile), true);
  191. if (!\is_array($json)) {
  192. $jsonError = json_last_error();
  193. \OCP\Server::get(LoggerInterface::class)->warning("Failed to load $translationFile - json error code: $jsonError", ['app' => 'l10n']);
  194. return false;
  195. }
  196. $this->translations = array_merge($this->translations, $json['translations']);
  197. return true;
  198. }
  199. }