Dictionary.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\App\Language;
  7. use Magento\Framework\Component\ComponentRegistrar;
  8. use Magento\Framework\Filesystem\Directory\ReadFactory;
  9. /**
  10. * A service for reading language package dictionaries
  11. *
  12. * @api
  13. * @since 100.0.2
  14. */
  15. class Dictionary
  16. {
  17. /**
  18. * Paths of all language packages
  19. *
  20. * @var string[]
  21. */
  22. private $paths;
  23. /**
  24. * Creates directory read objects
  25. *
  26. * @var ReadFactory
  27. */
  28. private $directoryReadFactory;
  29. /**
  30. * Component Registrar
  31. *
  32. * @var ReadFactory
  33. */
  34. private $componentRegistrar;
  35. /**
  36. * @var ConfigFactory
  37. */
  38. private $configFactory;
  39. /**
  40. * @var array
  41. */
  42. private $packList = [];
  43. /**
  44. * @param ReadFactory $directoryReadFactory
  45. * @param ComponentRegistrar $componentRegistrar
  46. * @param ConfigFactory $configFactory
  47. */
  48. public function __construct(
  49. ReadFactory $directoryReadFactory,
  50. ComponentRegistrar $componentRegistrar,
  51. ConfigFactory $configFactory
  52. ) {
  53. $this->directoryReadFactory = $directoryReadFactory;
  54. $this->componentRegistrar = $componentRegistrar;
  55. $this->configFactory = $configFactory;
  56. }
  57. /**
  58. * Load and merge all phrases from language packs by specified code
  59. *
  60. * Takes into account inheritance between language packs
  61. * Returns associative array where key is phrase in the source code and value is its translation
  62. *
  63. * @param string $languageCode
  64. * @return array
  65. * @throws \Magento\Framework\Exception\LocalizedException
  66. */
  67. public function getDictionary($languageCode)
  68. {
  69. $languages = [];
  70. $this->paths = $this->componentRegistrar->getPaths(ComponentRegistrar::LANGUAGE);
  71. foreach ($this->paths as $path) {
  72. $directoryRead = $this->directoryReadFactory->create($path);
  73. if ($directoryRead->isExist('language.xml')) {
  74. $xmlSource = $directoryRead->readFile('language.xml');
  75. try {
  76. $languageConfig = $this->configFactory->create(['source' => $xmlSource]);
  77. } catch (\Magento\Framework\Config\Dom\ValidationException $e) {
  78. throw new \Magento\Framework\Exception\LocalizedException(
  79. new \Magento\Framework\Phrase(
  80. 'The XML in file "%1" is invalid:' . "\n%2\nVerify the XML and try again.",
  81. [$path . '/language.xml', $e->getMessage()]
  82. ),
  83. $e
  84. );
  85. }
  86. $this->packList[$languageConfig->getVendor()][$languageConfig->getPackage()] = $languageConfig;
  87. if ($languageConfig->getCode() === $languageCode) {
  88. $languages[] = $languageConfig;
  89. }
  90. }
  91. }
  92. // Collect the inherited packages with meta-information of sorting
  93. $packs = [];
  94. foreach ($languages as $languageConfig) {
  95. $this->collectInheritedPacks($languageConfig, $packs);
  96. }
  97. uasort($packs, [$this, 'sortInherited']);
  98. // Merge all packages of translation to one dictionary
  99. $result = [];
  100. foreach ($packs as $packInfo) {
  101. /** @var Config $languageConfig */
  102. $languageConfig = $packInfo['language'];
  103. $dictionary = $this->readPackCsv($languageConfig->getVendor(), $languageConfig->getPackage());
  104. foreach ($dictionary as $key => $value) {
  105. $result[$key] = $value;
  106. }
  107. }
  108. return $result;
  109. }
  110. /**
  111. * Line up (flatten) a tree of inheritance of language packs
  112. *
  113. * Record level of recursion (level of inheritance) for further use in sorting
  114. *
  115. * @param Config $languageConfig
  116. * @param array $result
  117. * @param int $level
  118. * @param array $visitedPacks
  119. * @return void
  120. */
  121. private function collectInheritedPacks($languageConfig, &$result, $level = 0, array &$visitedPacks = [])
  122. {
  123. $packKey = implode('|', [$languageConfig->getVendor(), $languageConfig->getPackage()]);
  124. if (!isset($visitedPacks[$packKey]) &&
  125. (!isset($result[$packKey]) || $result[$packKey]['inheritance_level'] < $level)
  126. ) {
  127. $visitedPacks[$packKey] = true;
  128. $result[$packKey] = [
  129. 'inheritance_level' => $level,
  130. 'sort_order' => $languageConfig->getSortOrder(),
  131. 'language' => $languageConfig,
  132. 'key' => $packKey,
  133. ];
  134. foreach ($languageConfig->getUses() as $reuse) {
  135. if (isset($this->packList[$reuse['vendor']][$reuse['package']])) {
  136. $parentLanguageConfig = $this->packList[$reuse['vendor']][$reuse['package']];
  137. $this->collectInheritedPacks($parentLanguageConfig, $result, $level + 1, $visitedPacks);
  138. }
  139. }
  140. }
  141. }
  142. /**
  143. * Sub-routine for custom sorting packs using inheritance level and sort order
  144. *
  145. * First sort by inheritance level descending, then by sort order ascending
  146. *
  147. * @param array $current
  148. * @param array $next
  149. * @return int
  150. * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
  151. */
  152. private function sortInherited($current, $next)
  153. {
  154. if ($current['inheritance_level'] > $next['inheritance_level']) {
  155. return -1;
  156. } elseif ($current['inheritance_level'] < $next['inheritance_level']) {
  157. return 1;
  158. }
  159. if ($current['sort_order'] > $next['sort_order']) {
  160. return 1;
  161. } elseif ($current['sort_order'] < $next['sort_order']) {
  162. return -1;
  163. }
  164. return strcmp($current['key'], $next['key']);
  165. }
  166. /**
  167. * Read the CSV-files in a language package
  168. *
  169. * The files are sorted alphabetically, then each of them is read, and results are recorded into key => value array
  170. *
  171. * @param string $vendor
  172. * @param string $package
  173. * @return array
  174. */
  175. private function readPackCsv($vendor, $package)
  176. {
  177. $path = $this->componentRegistrar->getPath(ComponentRegistrar::LANGUAGE, strtolower($vendor . '_' . $package));
  178. $result = [];
  179. if (isset($path)) {
  180. $directoryRead = $this->directoryReadFactory->create($path);
  181. $foundCsvFiles = $directoryRead->search("*.csv");
  182. foreach ($foundCsvFiles as $foundCsvFile) {
  183. $file = $directoryRead->openFile($foundCsvFile);
  184. while (($row = $file->readCsv()) !== false) {
  185. if (is_array($row) && count($row) > 1) {
  186. $result[$row[0]] = $row[1];
  187. }
  188. }
  189. }
  190. }
  191. return $result;
  192. }
  193. }