PageRenderTime 69ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 1ms

/application/modules/translator/classes/LangReplacer.php

http://github.com/imagecms/ImageCMS
PHP | 446 lines | 237 code | 83 blank | 126 comment | 29 complexity | 39d9b2e72961d582e6d0d97124df974a MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-1.0
  1. <?php
  2. namespace translator\classes;
  3. use Gettext\Translation;
  4. use Gettext\Translations;
  5. class LangReplacer
  6. {
  7. public static $domainTranslations = [];
  8. const LANG_FUNC = '(t?langf?)';
  9. const WORD = '([\s\S]+?)';
  10. const QUOTE = '[\'\"]{1}';
  11. const QUOTED_WORD = self::QUOTE . self::WORD . self::QUOTE;
  12. const QUOTED_DOMAIN = '(?:\s*,\s*' . self::QUOTE . self::WORD . self::QUOTE . ')?';
  13. const QUOTED_PARAMS = '(?:\s*,\s*' . self::WORD . ')?';
  14. const EXPRESSION = '/' . self::LANG_FUNC . '\(' . self::QUOTED_WORD . self::QUOTED_DOMAIN . '\)/';
  15. /*
  16. *******************************************************************************************************************
  17. * INTERFACE
  18. *
  19. * USAGE
  20. * $module = 'aggregator';
  21. * $allModules
  22. * LangReplacer::replaceModuleLangStrings($module);
  23. * LangReplacer::replaceMainLangStrings();
  24. * LangReplacer::findCyrillicKeys($module)
  25. *******************************************************************************************************************
  26. */
  27. public static function findCyrillicKeys($module) {
  28. if ($module == 'main') {
  29. $langStrings = self::parseMain();
  30. } else {
  31. $langStrings = self::parseModule($module);
  32. }
  33. $found = [];
  34. foreach ($langStrings as $file => $found) {
  35. foreach (array_keys($found) as $key) {
  36. if (self::isCyrillic($key)) {
  37. dump($file, $key);
  38. }
  39. }
  40. }
  41. }
  42. public static function replaceMainLangStrings() {
  43. $mainLangStrings = self::parseMain();
  44. $module = 'main';
  45. foreach ($mainLangStrings as $file => $data) {
  46. $translated = self::changeKeysInFile($file, $data);
  47. self::updateKeysInMoPoFiles($translated, $module);
  48. }
  49. self::savePoMo($module);
  50. }
  51. /**
  52. * Replace key to english translation
  53. * Add translation to other languages for this key
  54. * @param $module
  55. */
  56. public static function replaceModuleLangStrings($module) {
  57. $moduleLangStrings = self::parseModule($module);
  58. foreach ($moduleLangStrings as $file => $data) {
  59. $translated = self::changeKeysInFile($file, $data);
  60. self::updateKeysInMoPoFiles($translated, $module);
  61. }
  62. }
  63. private static function savePoMo($saveDomain) {
  64. foreach (self::$domainTranslations as $domain => $langs) {
  65. if ($domain == $saveDomain) {
  66. foreach ($langs as $lang => $translation) {
  67. $dir = self::getDomainPoFilePath($domain, $lang);
  68. dump(sprintf('Po file saving.. : %s', $dir));
  69. $translation->toPoFile($dir);
  70. $dir = self::createDomainMoFilePath($domain, $lang);
  71. $translation->toMoFile($dir);
  72. dump(sprintf('Mo file saving.. : %s', $dir));
  73. }
  74. }
  75. }
  76. }
  77. /**
  78. *
  79. * @param array $translated
  80. * @param string $module
  81. */
  82. private static function updateKeysInMoPoFiles($translated, $module) {
  83. $languages = self::getModuleLanguages($module);
  84. foreach ($languages as $language) {
  85. $translationsAllLocales[$language] = self::getDomainTranslations($module, $language);
  86. }
  87. foreach ($translated as $changed) {
  88. foreach ($changed as $one) {
  89. foreach ($translationsAllLocales as $lang => $translationsOneLocale) {
  90. /** @var Translation $oldTranslation */
  91. $oldTranslation = $translationsOneLocale->find('', $one['from']);
  92. if ($oldTranslation) {
  93. $newTranslation = new Translation('', $one['to']);
  94. $newTranslation->mergeWith($oldTranslation);
  95. $translationsOneLocale[] = $newTranslation;
  96. if (!$newTranslation->getTranslation()) {
  97. $newTranslation->setTranslation($one['from']);
  98. }
  99. }
  100. }
  101. }
  102. }
  103. }
  104. /**
  105. * @param string $file
  106. * @param array $data
  107. * @return array
  108. */
  109. private static function changeKeysInFile($file, $data) {
  110. $translated = [];
  111. if (is_writable($file)) {
  112. $content = $newContent = file_get_contents($file);
  113. foreach ($data as $signature => $data) {
  114. $function = $data['function'];
  115. $word = $data['string'];
  116. $domain = $data['domain'];
  117. $params = $data['params'];
  118. $translation = self::translate($domain, $word, 'en_US');
  119. if ($translation && $translation !== $word) {
  120. $newSignature = self::createReplacement($function, $translation, $domain, $params);
  121. $translated[$domain][] = [
  122. 'from' => $word,
  123. 'to' => $translation,
  124. 'domain' => $domain,
  125. ];
  126. dump(sprintf('%s replaced to %s', $signature, $newSignature));
  127. $newContent = str_replace($signature, $newSignature, $newContent);
  128. // dump(sprintf('Changed %s to %s', $word, $translation));
  129. } else {
  130. dump(sprintf("No default translation for: '%s' in domain %s", $word, $domain));
  131. }
  132. }
  133. if ($newContent && $newContent !== $content) {
  134. dump(sprintf('File %s saved: %s', $file, file_put_contents($file, $newContent) ? 'TRUE' : 'FALSE'));
  135. } else {
  136. // dump("Warning: {$file} no content or no changes");
  137. }
  138. } else {
  139. dump("File: {$file} is not writable");
  140. }
  141. return $translated;
  142. }
  143. public static function getMainPaths() {
  144. return [
  145. APPPATH . 'core',
  146. APPPATH . 'errors',
  147. APPPATH . 'helpers',
  148. APPPATH . 'libraries',
  149. APPPATH . 'modules/shop/classes',
  150. APPPATH . 'modules/shop/helpers',
  151. PUBPATH . 'system/language/form_validation',
  152. PUBPATH . 'system/language/email_lang',
  153. PUBPATH . 'system/language/upload',
  154. PUBPATH . 'system/libraries',
  155. PUBPATH . 'templates/administrator/js/jquery-validate',
  156. PUBPATH . 'application/modules/shop/widgets',
  157. PUBPATH . 'application/modules/shop/models',
  158. ];
  159. }
  160. /*
  161. *******************************************************************************************************************
  162. * PARSE
  163. *******************************************************************************************************************
  164. */
  165. /**
  166. * Find lang calls in module files
  167. *
  168. * @param $module
  169. * @return array
  170. */
  171. public static function parseModule($module) {
  172. return self::parseDir(self::getModulePath($module));
  173. }
  174. /**
  175. * Find all main lang calls in main directories
  176. * @return array
  177. */
  178. public static function parseMain() {
  179. $results = [];
  180. foreach (self::getMainPaths() as $mainPath) {
  181. $add = self::parseDir($mainPath);
  182. if (is_array($add)) {
  183. $results = array_merge($results, $add);
  184. }
  185. }
  186. return $results;
  187. }
  188. /**
  189. * Search lang function call in all php|tpl files recursively
  190. * @param $dir
  191. * @return array
  192. */
  193. public static function parseDir($dir) {
  194. $dirIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
  195. $results = [];
  196. /** @var \SplFileInfo $item */
  197. foreach ($dirIterator as $item) {
  198. if ($item->isFile() && in_array($item->getExtension(), ['php', 'tpl']) && !strstr($item->getBasename(), 'jsLangs')) {
  199. $res = self::find(file_get_contents($item->getRealPath()));
  200. if (count($res[0])) {
  201. foreach ($res[0] as $key => $signature) {
  202. $results[$item->getRealPath()][$signature] = [
  203. 'function' => $res[1][$key],
  204. 'string' => $res[2][$key],
  205. 'domain' => $res[3][$key],
  206. 'params' => $res[4][$key],
  207. ];
  208. }
  209. }
  210. }
  211. }
  212. return $results;
  213. }
  214. /*
  215. *******************************************************************************************************************
  216. * Translate
  217. *******************************************************************************************************************
  218. */
  219. /**
  220. * @param $domain
  221. * @param $word
  222. * @param string $lang
  223. * @return null|string
  224. */
  225. public static function translate($domain, $word, $lang = 'en_US') {
  226. $domain = $domain ?: 'main';
  227. $translations = self::getDomainTranslations($domain, $lang);
  228. $translation = $translations->find(null, $word);
  229. return $translation ? $translation->getTranslation() : null;
  230. }
  231. /**
  232. * @param $domain
  233. * @param string $locale
  234. * @return Translations
  235. */
  236. public static function getDomainTranslations($domain, $locale = 'en_US') {
  237. if (!isset(self::$domainTranslations[$domain][$locale])) {
  238. self::$domainTranslations[$domain][$locale] = Translations::fromPoFile(self::getDomainPoFilePath($domain, $locale));
  239. }
  240. return self::$domainTranslations[$domain][$locale];
  241. }
  242. /*
  243. *******************************************************************************************************************
  244. * REGEXP
  245. *******************************************************************************************************************
  246. */
  247. /**
  248. * Find all language calls in string
  249. * @param $fileContent
  250. * @param string $word
  251. * @return array
  252. */
  253. public static function find($fileContent, $word = '') {
  254. $expression = self::createExpression($word);
  255. preg_match_all($expression, $fileContent, $matches);
  256. return $matches;
  257. }
  258. public static function isCyrillic($word) {
  259. return preg_match('/[\p{Cyrillic}]/u', $word) || preg_match('/[А-Яа-яЁё]/u', $word);
  260. }
  261. /**
  262. * Create lang function call
  263. * @param $function
  264. * @param $word
  265. * @param $domain
  266. * @param $params
  267. * @return string
  268. */
  269. public static function createReplacement($function, $word, $domain, $params) {
  270. return sprintf("%s('%s'%s%s)", $function, $word, $domain ? sprintf(", '%s'", $domain) : '', $params ? sprintf(', %s', $params) : '');
  271. }
  272. /**
  273. * Create regular expression for lang call
  274. * @param $word
  275. * @return string
  276. */
  277. private static function createExpression($word = null) {
  278. $word = $word ? "($word)" : self::WORD;
  279. $quotedWord = self::QUOTE . $word . self::QUOTE;
  280. $expression = '/' . self::LANG_FUNC . '\(' . $quotedWord . self::QUOTED_DOMAIN . self::QUOTED_PARAMS . '\)/';
  281. return $expression;
  282. }
  283. /*
  284. *******************************************************************************************************************
  285. * PATHS
  286. *******************************************************************************************************************
  287. */
  288. /**
  289. * Get lang directories for module
  290. *
  291. * @param $module
  292. * @return array ['en_US', ...]
  293. */
  294. public static function getModuleLanguages($module) {
  295. $languages = [];
  296. if (in_array($module, ['shop', '', 'main'])) {
  297. $path = PUBPATH . APPPATH . 'language/main/*';
  298. } else {
  299. $path = self::getModulePath($module) . '/language/*';
  300. }
  301. foreach (glob($path) as $item) {
  302. $languages[] = substr($item, strrpos($item, '/') + 1);
  303. }
  304. return $languages;
  305. }
  306. /**
  307. * Get po file path
  308. * @param string $domain
  309. * @param string $locale
  310. * @return string
  311. */
  312. public static function getDomainPoFilePath($domain, $locale = 'en_US') {
  313. if (in_array($domain, ['shop', '', 'main'])) {
  314. $domain = 'main';
  315. }
  316. $dir = self::getTranslationDir($domain, $locale);
  317. return sprintf('%s%s.po', $dir, $domain);
  318. }
  319. /**
  320. * Create new mo file path
  321. * @param $domain
  322. * @param string $locale
  323. * @return string
  324. */
  325. public static function createDomainMoFilePath($domain, $locale = 'en_US') {
  326. if (in_array($domain, ['shop', '', 'main'])) {
  327. $domain = 'main';
  328. }
  329. $dir = self::getTranslationDir($domain, $locale);
  330. return sprintf('%s%s_%s.mo', $dir, $domain, time());
  331. }
  332. /**
  333. * Languages directory
  334. *
  335. * @param $domain
  336. * @param $locale
  337. * @return string
  338. */
  339. public static function getTranslationDir($domain, $locale) {
  340. if (in_array($domain, ['shop', '', 'main'])) {
  341. $transPath = PUBPATH . APPPATH . "language/main/{$locale}/LC_MESSAGES/";
  342. } else {
  343. $transPath = self::getModulePath($domain) . "/language/{$locale}/LC_MESSAGES/";
  344. }
  345. return $transPath;
  346. }
  347. /**
  348. * Full path to module
  349. * @param $moduleName
  350. * @return string
  351. */
  352. public static function getModulePath($moduleName) {
  353. return PUBPATH . APPPATH . 'modules/' . $moduleName;
  354. }
  355. public static function getAllModules() {
  356. $modules = [];
  357. $paths = PUBPATH . APPPATH . 'modules/*';
  358. foreach (glob($paths) as $path) {
  359. $modules[] = substr($path, strrpos($path, '/') + 1);
  360. }
  361. return $modules;
  362. }
  363. }