Shopify 页面翻译实现
实现在自己网站后台控制shopify页面翻译
1 <?php 2 namespace Cli\Controller; 3 4 use Curl; 5 use Exception; 6 use Think\Controller; 7 use Think\Log; 8 use Think\Think; 9 use translate\Baidu; 10 use translate\Translate; 11 12 class ShopifyTranslateController extends Controller 13 { 14 private $_api = [ 15 //api info ... 20 ]; 21 private $_logque; 22 private $_curlLastErr; 23 private $_storefrontToken = \'token....\'; 24 private $_rePrefix = \'ShopifyTranslate\'; 25 private $_redis; 26 //locale shopLocales 27 const CHINESE = \'zh-CN\'; 28 const JAPANESE = \'ja\'; 29 30 31 public function __construct() 32 { 33 $this->_api[\'authorization\'] = base64_encode( "{$this->_api[\'key\']}:{$this->_api[\'pwd\']}" ); 34 $this->_redis = getRedis(); 35 36 } 37 38 public function test() 39 { 40 $list = M(\'shopifyTranslate\')->where( [\'resourceId\' => \'gid://shopify/OnlineStoreTheme/80042688647\'] )->select(); 41 42 $translations = []; 43 foreach( $list as $_item ) { 44 $source = json_decode( $_item[\'source\'], true ); 45 46 $cn = str_replace("\n", "\\n", addslashes( $_item[\'zh-cn\'] ) ); 47 $ja = str_replace("\n", "\\n", addslashes( $_item[\'ja\'] ) ); 48 49 $translations[] = [ 50 \'key\' => $source[\'key\'], 51 \'value\' => $cn, 52 \'locale\' => "zh-CN", 53 \'translatableContentDigest\' => $source[\'digest\'] 54 ]; 55 56 $translations[] = [ 57 \'key\' => $source[\'key\'], 58 \'value\' => $ja, 59 \'locale\' => "ja", 60 \'translatableContentDigest\' => $source[\'digest\'] 61 ]; 62 } 63 64 $chunked = array_chunk( $translations, 100 ); 65 foreach( $chunked as $_chunk ) { 66 $_translations = json_encode( $_chunk ); 67 $data = <<<EOF 68 { 69 "query": "mutation CreateTranslation(\$id: ID!, \$translations: [TranslationInput!]!) { translationsRegister(resourceId: \$id, translations: \$translations) { translations { locale key outdated value } userErrors { field message } } }", 70 "variables": { 71 "id": "gid://shopify/OnlineStoreTheme/80042688647", 72 "translations": $_translations 73 } 74 } 75 EOF; 76 $ret = $this->sendRequest( $data, \'post\', \'graphql.json\', [], false, \'json\' ); 77 var_dump( $ret ); 78 } 79 80 81 } 82 83 public function pull() 84 { 85 86 } 87 88 //第三方翻译。 https://github.com/John-Theo/google-translate-server 89 public function translate() 90 { 91 $list = M(\'shopifyTranslate\')->field(\'id, en\')->where( \'ja is null\' )->select(); 92 import( \'Lib.Extend.Translate.sdk.Baidu\', dirname( THINK_PATH ) ); 93 94 $baidu = new Baidu(); 95 foreach( $list as $_item ) { 96 97 $up = []; 98 99 $en = urlencode( $_item[\'en\'] ); 100 101 $ret = (new Curl())->get( "http://127.0.0.1:30031?to=zh-CN&text={$en}" ); 102 $ret = json_decode( $ret, true ); 103 if( !empty( $ret[\'text\'] ) ) { 104 $up[\'zh-CN\'] = $ret[\'text\']; 105 }else { 106 $str = addslashes( $_item[\'en\'] ); 107 $res = $baidu->run( $str, \'zh\', \'en\' ); 108 $up[\'zh-CN\'] = addslashes( $res ); 109 } 110 sleep( 3 ); 111 112 $ret = (new Curl())->get( "http://127.0.0.1:30031?to=ja&text={$en}" ); 113 $ret = json_decode( $ret, true ); 114 if( !empty( $ret[\'text\'] ) ) { 115 $up[\'ja\'] = $ret[\'text\']; 116 }else { 117 $res = $baidu->run( $str, \'jp\', \'en\' ); 118 $up[\'ja\'] = addslashes( $res ); 119 sleep(1); 120 } 121 sleep( 3 ); 122 123 124 M(\'shopifyTranslate\')->where( [\'id\' => $_item[\'id\']] )->save( $up ); 125 } 126 } 127 128 public function setTranslate( $id = 0 ) 129 { 130 $this->_setLog( \'setTranslate start\' ); 131 132 // $list = M(\'shopifyTranslate\')->where( [\'synch\' => 0, \'ja\' => [\'exp\', \'is not null\']] )->select(); 133 134 // $keys = [ 135 // \'!\', \'"\', \'#\', \'$\', \'%\', \''\', \'(\', \')\', \'*\', \'+\', \',\', \'-\', \'.\', \'/\', \':\', \';\', \'<\', \'=\', \'>\', \'?\', \'@\', \'[\', \'\\', \']\', \'^\', \'_\', \'`\', \'{\', \'|\', \'}\', \'~\', 136 // \'⦅\', \'⦆\', \'¢\', \'£\', \'¬\', \' ̄\', \'¦\', \'¥\', \'₩\', \'│\', \'←\', \'↑\', \'→\', \'↓\', \'■\', \'○\',\'“\',\'”\',\'/ \',\' _ \',\' // \' 137 // ]; 138 // $values = [ 139 // \'!\', \'"\', \'#\', \'$\', \'%\', \'\\'\', \'(\', \')\', \'*\', \'+\', \',\', \'-\', \'.\', \'/\', \':\', \';\', \'<\', \'=\', \'>\', \'?\', \'@\', \'[\', \'\\\', \']\', \'^\', \'_\', \'`\', \'{\', \'|\', \'}\', \'~\', 140 // \'⦅\', \'⦆\', \'¢\', \'£\', \'¬\', \'¯\', \'¦\', \'¥\', \'₩\', \'│\', \'←\', \'↑\', \'→\', \'↓\', \'■\', \'○\',\'"\',\'"\',\'/\',\'_\',\'//\' 141 // ]; 142 // foreach( $list as $_item ) { 143 // $up = []; 144 145 // $up[\'zh-CN\'] = strtr( $_item[\'zh-cn\'], array_combine( $keys, $values ) ); 146 // $up[\'ja\'] = strtr( $_item[\'ja\'], array_combine( $keys, $values ) ); 147 148 // M(\'shopifyTranslate\')->where( [\'id\' => $_item[\'id\']] )->save($up); 149 // } 150 // die; 151 152 $list = M(\'shopifyTranslate\')->where( [\'synch\' => 0, \'ja\' => [\'exp\', \'is not null\'], \'type\' => [\'neq\', \'EMAIL_TEMPLATE\']] )->select(); 153 // $list = M(\'shopifyTranslate\')->where( [\'id\' => $id] )->select(); 154 155 foreach( $list as $_item ) { 156 157 $source = json_decode( $_item[\'source\'], true ); 158 $cn = str_replace("\n", "\\n", addslashes( $_item[\'zh-cn\'] ) ); 159 $ja = str_replace("\n", "\\n", addslashes( $_item[\'ja\'] ) ); 160 161 $data = <<<EOF 162 { 163 "query": "mutation CreateTranslation(\$id: ID!, \$translations: [TranslationInput!]!) { translationsRegister(resourceId: \$id, translations: \$translations) { translations { locale key outdated value } userErrors { field message } } }", 164 "variables": { 165 "id": "{$_item[\'resourceid\']}", 166 "translations": [ 167 { 168 "key": "{$source[\'key\']}", 169 "value": "{$cn}", 170 "locale": "zh-CN", 171 "translatableContentDigest": "{$source[\'digest\']}" 172 }, 173 { 174 "key": "{$source[\'key\']}", 175 "value": "{$ja}", 176 "locale": "ja", 177 "translatableContentDigest": "{$source[\'digest\']}" 178 } 179 ] 180 } 181 } 182 EOF; 183 $ret = $this->sendRequest( $data, \'post\', \'graphql.json\', [], false, \'json\' ); 184 if( $ret ) { 185 M(\'shopifyTranslate\')->where( [\'id\' => $_item[\'id\']] )->save([\'synch\' => 1]); 186 var_dump( $_item[\'id\'] . \' ok ..\' ); 187 // return true; 188 usleep(500); 189 }else { 190 var_dump( $_item[\'id\'] . \' fail ..\' . $ret ); 191 // return false; 192 } 193 } 194 195 } 196 197 public function setToDb() 198 { 199 $types = $this->getTranslateType(); 200 foreach( $types as $type ) { 201 $this->getTranslatableResources( $type, self::CHINESE ); 202 $this->parseDataToDb( $type, self::CHINESE ); 203 } 204 } 205 206 //解析数据入库 207 public function parseDataToDb( $type, $locale ) 208 { 209 $this->_setLog( \'parseDataToDb start\' . json_encode( func_get_args() ) ); 210 $cacheFile = DATA_PATH . "ShopifyTranslate:to{$locale}:{$type}"; 211 if( !file_exists( $cacheFile ) ) { 212 $this->_setLog( \'err :: 获取原始数据失败\' . json_encode( func_get_args() ), Log::WARN ); 213 return false; 214 } 215 216 $fhandle = fopen( $cacheFile, \'r+\' ); 217 if( !$fhandle ) { 218 $this->_setLog( \'err :: 打开文件失败\' . $cacheFile, Log::WARN ); 219 return false; 220 } 221 222 $preData = []; 223 $totals = 0; 224 while( ($line = fgets( $fhandle )) !== false ) { 225 $ret = json_decode( $line, true ); 226 if( !$ret ) { 227 $this->_setLog( \'err :: 读取文件异常\' . $cacheFile . \' >>> \' . $line, Log::WARN ); 228 continue; 229 } 230 231 232 if( isset( $ret[\'data\'][\'translatableResources\'][\'edges\'] ) ) { 233 // $this->_setLog( \'parseDataToDb edges count == \' . count( $ret[\'data\'][\'translatableResources\'][\'edges\'] ) ); 234 foreach( $ret[\'data\'][\'translatableResources\'][\'edges\'] as $edges ) { 235 $resourceId = $edges[\'node\'][\'resourceId\']; 236 // $this->_setLog( \'parseDataToDb edges > translatableContent > count == \' . count( $edges[\'node\'][\'translatableContent\'] ) ); 237 foreach( $edges[\'node\'][\'translatableContent\'] as $node ) { 238 if( empty( $node[\'value\'] ) ) 239 continue; 240 241 $preData_key = md5( $node[\'key\'] . $resourceId ); 242 $preData[$preData_key] = [ 243 \'en\' => $node[\'value\'], 244 \'resourceId\' => $resourceId, 245 \'en_md5\' => md5( $node[\'key\'] . $node[\'value\'] . $resourceId . $type ), 246 \'source\' => json_encode( $node ), 247 \'type\' => $type, 248 ]; 249 $totals++; 250 } 251 252 // foreach( $edges[\'node\'][\'translations\'] as $node ) { 253 // $preData_key = md5( $node[\'key\'] . $resourceId ); 254 // if( isset( $preData[$preData_key] ) ) 255 // $preData[$preData_key][$node[\'locale\']] = $node[\'value\']; 256 257 // } 258 } 259 }else { 260 $this->_setLog( \'err : 解析结果失败...\' . $cacheFile, Log::WARN ); 261 } 262 263 } 264 $this->_setLog( "parseDataToDb totals == {$totals} , preData count == " . count( $preData ), Log::WARN ); 265 266 fclose( $fhandle ); 267 268 $i = 0; 269 foreach( $preData as $_item ) { 270 if( !M(\'shopifyTranslate\')->where( [\'en_md5\' => $_item[\'en_md5\']] )->find() ) { 271 echo $i++; 272 M(\'shopifyTranslate\')->add( $_item ); 273 } 274 } 275 } 276 277 //获取原始信息 278 private function getTranslatableResources( $type, $locale, $force = false ) 279 { 280 $this->_setLog( \'getTranslatableResources start\' . json_encode( func_get_args() ) ); 281 282 $cacheFile = DATA_PATH . "ShopifyTranslate:to{$locale}:{$type}"; 283 if( $force && file_exists( $cacheFile ) ) 284 unlink( $cacheFile ); 285 286 if( !file_exists( $cacheFile ) ) { 287 $hasNextPage = false; 288 $after = \'\'; 289 do{ 290 $data = <<<EOF 291 { 292 translatableResources(first: 50, resourceType: {$type} {$after}) { 293 edges { 294 cursor 295 node { 296 resourceId 297 translatableContent { 298 key 299 value 300 digest 301 locale 302 } 303 translations(locale: "{$locale}") 304 { 305 locale 306 key 307 value 308 } 309 } 310 } 311 pageInfo { 312 hasNextPage 313 } 314 } 315 } 316 EOF; 317 318 $ret = $this->sendRequest( $data ); 319 $hasNextPage = !empty( $ret[\'data\'][\'translatableResources\'][\'pageInfo\'][\'hasNextPage\'] ) ? true : false; 320 file_put_contents( $cacheFile, json_encode( $ret ) . PHP_EOL, FILE_APPEND ); 321 foreach( $ret[\'data\'][\'translatableResources\'][\'edges\'] as $edges ) { 322 $after = ", after: \"{$edges[\'cursor\']}\""; 323 } 324 sleep(1); 325 }while( $hasNextPage ); 326 } 327 328 $this->_setLog( \'getTranslatableResources over ~\' . json_encode( func_get_args() ) ); 329 } 330 331 //get type 332 private function getTranslateType() 333 { 334 $rekey = $this->_rePrefix . \'::getTranslateType\'; 335 $data = $this->_redis->get( $rekey ); 336 if( $data ) 337 return json_decode( $data, true ); 338 339 $data = <<<EOF 340 { 341 __type(name: "TranslatableResourceType") { 342 enumValues { 343 name 344 } 345 } 346 } 347 EOF; 348 349 $ret = $this->sendRequest( $data ); 350 if( isset( $ret[\'data\'][\'__type\'][\'enumValues\'] ) ) { 351 $data = []; 352 foreach( $ret[\'data\'][\'__type\'][\'enumValues\'] as $_name ) { 353 $data[] = $_name[\'name\']; 354 } 355 $this->_redis->set( $rekey, json_encode( $data ), 86400 ); 356 return $data; 357 }else { 358 $this->_setLog( \'err :: 解析结果失败...\' . $ret, Log::WARN ); 359 } 360 } 361 362 363 364 private function sendRequest( $data = \'\', $method = \'post\', $urlExtend = \'graphql.json\', $header = [], $needResHeader = false, $contentType = \'graphql\' ) 365 { 366 static $tryTimes = 1; 367 $tmpHeader = [ 368 \'Cache-Controller: max-age=0\', 369 \'Authorization: Basic \' . $this->_api[\'authorization\'], 370 \'X-Shopify-Storefront-Access-Token: \' . $this->_storefrontToken, 371 \'Accept: application/json\', 372 ]; 373 if( is_array( $data ) && !empty( $data ) ) { 374 $data = json_encode( $data ); 375 $tmpHeader[] = \'Content-Length: \' . strlen( $data ); 376 }else { 377 $data = (string)$data; 378 } 379 380 if( !empty( $header ) ) { 381 $tmpHeader = array_merge( $tmpHeader, $header ); 382 } 383 384 if( $contentType == \'graphql\' ) { 385 $tmpHeader[] = \'Content-Type:application/graphql\'; 386 }else { 387 $tmpHeader[] = \'Content-Type:application/\' . $contentType; 388 } 389 390 $url = "https://{$this->_api[\'key\']}:{$this->_api[\'pwd\']}@{$this->_api[\'url\']}{$this->_api[\'version\']}/{$urlExtend}"; 391 $this->_setLog( \'开始curl \'. $method .\' ,url == \' . $url . \',, param == \' . $data . \',, header == \' . var_export( $tmpHeader, true ) ); 392 $ret = (new Curl())->execute( $method, $url, $data, \'\', $tmpHeader, \'\', \'\', $needResHeader ); 393 if( is_array( $ret ) || $ret === false ) { 394 $tryTimes++; 395 if( $tryTimes >= 1 ) { 396 $this->_curlLastErr = "send curl error .."; 397 if( isset( $ret[1] ) ) 398 $this->_curlLastErr .= "{$ret[1]}"; 399 $this->_setLog( \'curl 请求shopify失败,请检查! \' . json_encode( $ret ), Log::WARN ); 400 return false; 401 }else { 402 return $this->sendRequest( $method, $urlExtend, $data, $header, $needResHeader ); 403 } 404 } 405 406 $deRet = json_decode( $ret, true ); 407 408 if( $needResHeader ) { 409 if( isset( $deRet[\'ret\'][\'errors\'] ) ) { 410 $this->_curlLastErr = $ret; 411 $this->_setLog( \'请求成功,但是返回了失败,.\' . $ret, Log::WARN ); 412 return false; 413 } 414 }else { 415 if( isset( $deRet[\'errors\'] ) || isset( $deRet[\'error\'] ) ) { 416 $this->_curlLastErr = $ret; 417 $this->_setLog( \'请求成功,但是返回了失败,.\' . $ret, Log::WARN ); 418 return false; 419 } 420 } 421 422 // $this->_setLog( \'请求成功了,\' . $ret ); 423 424 return $deRet; 425 } 426 427 public function htmlMof() 428 { 429 430 header(\'Access-Control-Allow-Origin: *\'); 431 header(\'Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept\'); 432 433 if( substr($_POST[\'html\'], 0, 3) == \'<p>\' ) { 434 $_POST[\'html\'] = substr( $_POST[\'html\'], 3 ); 435 $_POST[\'html\'] = substr( $_POST[\'html\'], 0, -4 ); 436 } 437 438 $ret = M(\'shopifyTranslate\')->where([\'id\' => $_POST[\'id\']])->save( [\'ja\' => $_POST[\'html\'],\'mof_time\' => time()] ); 439 if( $ret !== false ) { 440 $this->setTranslate( $_POST[\'id\'] ); 441 echo json_encode( [\'code\' => 1] ); 442 }else 443 echo json_encode( [\'code\' => 0] ); 444 die; 445 } 446 447 private function _setLog( $msg, $level = Log::NOTICE ) 448 { 449 Log::write( $msg . PHP_EOL, Log::NOTICE, \'\', durableLog( \'api-shopify-translate\' ) ); 450 return; 451 452 if( !$this->_logque instanceof \SplQueue ) 453 $this->_logque = new \SplQueue(); 454 455 $this->_logque->enqueue( $msg . PHP_EOL ); 456 457 if( $level == Log::ERR ) { 458 //send mail 459 exit; 460 } 461 } 462 463 public function __destruct() 464 { 465 if( $this->_logque instanceof \SplQueue ) { 466 $msg = \'\'; 467 while( !$this->_logque->isEmpty() ) { 468 $msg .= $this->_logque->dequeue(); 469 } 470 Log::write( $msg, Log::NOTICE, \'\', durableLog( \'api-shopify-translate\' ) ); 471 } 472 } 473 474 }
版权声明:本文为lxdd原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。