PluginProbe
Translate and Go multilingual – Automatic AI translation – wpLingua / 2.16.7
Translate and Go multilingual – Automatic AI translation – wpLingua v2.16.7
2.16.7 2.16.6 2.16.5 2.16.4 2.16.3 2.16.2 2.16.1 2.16.0 2.15.2 2.15.1 2.15.0 2.14.3 2.14.2 2.14.1 2.14.0 2.13.1 2.13.0 2.12.3 2.12.2 2.12.1 trunk 1.0.3 1.0.4 1.0.5 1.1.0 All 112 releases
← All changes | inc/translation.php +665 -651 2.12.12.16.7 View file →
@@ -1,651 +1,665 @@
1 -<?php
2 -
3 -// If this file is called directly, abort.
4 -if ( ! defined( 'WPINC' ) ) {
5 - die;
6 -}
7 -
8 -
9 -/**
10 - * Get the translated text from translations array
11 - *
12 - * @param string $text
13 - * @param array $translations
14 - * @return string
15 - */
16 -function wplng_get_translated_text_from_translations( $text, $translations ) {
17 -
18 - if ( empty( trim( $text ) ) ) {
19 - return $text;
20 - }
21 -
22 - // Manage non breaking space
23 - $text = str_replace(
24 - array( '&nbsp;', html_entity_decode( '&nbsp;' ) ),
25 - array( ' ', ' ' ),
26 - $text
27 - );
28 -
29 - /**
30 - * Get spaces before and after text
31 - */
32 - $temp = array();
33 - $spaces_before = '';
34 - $spaces_after = '';
35 -
36 - preg_match( '/^(\s*).*/', $text, $temp );
37 - if ( ! empty( $temp[1] ) ) {
38 - $spaces_before = $temp[1];
39 - }
40 -
41 - preg_match( '/.*(\s*)$/U', $text, $temp );
42 - if ( ! empty( $temp[1] ) ) {
43 - $spaces_after = $temp[1];
44 - }
45 -
46 - $text = wplng_text_esc( $text );
47 - $translated = $text;
48 -
49 - if ( wplng_text_is_translatable( $text ) ) {
50 - foreach ( $translations as $translation ) {
51 - if ( $text === $translation['source'] ) {
52 - $translated = $translation['translation'];
53 - break;
54 - }
55 - }
56 - }
57 -
58 - return $spaces_before . $translated . $spaces_after;
59 -}
60 -
61 -
62 -/**
63 - * Get the count of published 'wplng_translation' posts.
64 - *
65 - * This function retrieves the number of posts of type 'wplng_translation'
66 - * that are currently published on the site.
67 - *
68 - * @return int The number of published 'wplng_translation' posts.
69 - */
70 -function wplng_get_translation_count() {
71 -
72 - global $wplng_translation_count;
73 -
74 - if ( null !== $wplng_translation_count ) {
75 - return $wplng_translation_count;
76 - }
77 -
78 - $count_posts = wp_count_posts( 'wplng_translation' );
79 - $wplng_translation_count = (int) ( $count_posts->publish ?? 0 );
80 -
81 - return $wplng_translation_count;
82 -}
83 -
84 -
85 -/**
86 - * Get translation data from original text
87 - *
88 - * @param string $original
89 - * @return array Translation data
90 - */
91 -function wplng_get_translation_saved_from_original( $original ) {
92 -
93 - if ( empty( $original ) ) {
94 - return false;
95 - }
96 -
97 - $args = array(
98 - 'post_type' => 'wplng_translation',
99 - 'posts_per_page' => -1,
100 - 'meta_query' => array(
101 - array(
102 - 'key' => 'wplng_translation_md5',
103 - 'value' => md5( $original ),
104 - 'compare' => '=',
105 - ),
106 - ),
107 - 'fields' => 'ids',
108 - 'meta_query' => array(
109 - array(
110 - 'key' => 'wplng_translation_original_language_id',
111 - 'value' => wplng_get_language_website_id(),
112 - 'compare' => '=',
113 - ),
114 - ),
115 - );
116 -
117 - $posts = get_posts( $args );
118 -
119 - if ( empty( $posts ) ) {
120 - return false;
121 - }
122 -
123 - foreach ( $posts as $post_id ) {
124 -
125 - $meta = get_post_meta( $post_id );
126 -
127 - if ( isset( $meta['wplng_translation_original'][0] )
128 - && is_string( $meta['wplng_translation_original'][0] )
129 - && $meta['wplng_translation_original'][0] === $original
130 - ) {
131 - return get_post( $post_id );
132 - }
133 - }
134 -
135 - return false;
136 -}
137 -
138 -
139 -/**
140 - * Get all saved translations from a wp_query for all languages
141 - *
142 - * @return array
143 - */
144 -function wplng_get_translations_from_query() {
145 -
146 - $translations = array();
147 -
148 - $args = array(
149 - 'post_type' => 'wplng_translation',
150 - 'posts_per_page' => -1,
151 - 'no_found_rows' => true,
152 - 'update_post_term_cache' => false,
153 - 'update_post_meta_cache' => false,
154 - 'cache_results' => false,
155 - 'fields' => 'ids', // Retrieve only post IDs for better performance
156 - 'meta_query' => array(
157 - array(
158 - 'key' => 'wplng_translation_original_language_id',
159 - 'value' => wplng_get_language_website_id(),
160 - 'compare' => '=',
161 - ),
162 - ),
163 - );
164 -
165 - $post_ids = get_posts( $args );
166 -
167 - if ( empty( $post_ids ) ) {
168 - return array();
169 - }
170 -
171 - foreach ( $post_ids as $id ) {
172 -
173 - $meta = get_post_meta( $id );
174 -
175 - // Ensure 'wplng_translation_original' exists and is a valid string
176 - if ( empty( $meta['wplng_translation_original'][0] )
177 - || ! is_string( $meta['wplng_translation_original'][0] )
178 - || trim( $meta['wplng_translation_original'][0] ) === ''
179 - ) {
180 - continue;
181 - }
182 -
183 - $source = wplng_text_esc( trim( $meta['wplng_translation_original'][0] ) );
184 -
185 - $translation = array(
186 - 'source' => $source,
187 - 'post_id' => $id,
188 - 'review' => array(),
189 - 'translations' => array(),
190 - );
191 -
192 - // Ensure 'wplng_translation_translations' exists
193 - if ( empty( $meta['wplng_translation_translations'][0] ) ) {
194 - continue;
195 - }
196 -
197 - $translations_meta = json_decode( $meta['wplng_translation_translations'][0], true );
198 -
199 - // Ensure JSON decoding was successful
200 - if ( ! is_array( $translations_meta ) ) {
201 - continue;
202 - }
203 -
204 - foreach ( $translations_meta as $translation_meta ) {
205 - // Validate each translation entry
206 - if ( empty( $translation_meta['language_id'] )
207 - || ! wplng_is_valid_language_id( $translation_meta['language_id'] )
208 - || (
209 - isset( $translation_meta['translation'] )
210 - && $translation_meta['translation'] === '[WPLNG_EMPTY]'
211 - )
212 - ) {
213 - continue;
214 - }
215 -
216 - $language_id = sanitize_key( $translation_meta['language_id'] );
217 -
218 - if ( isset( $translation_meta['translation'] )
219 - && is_string( $translation_meta['translation'] )
220 - ) {
221 - $translated_text = wplng_text_esc( $translation_meta['translation'] );
222 -
223 - $translation['translations'][ $language_id ] = $translated_text;
224 - }
225 -
226 - if ( isset( $translation_meta['status'] )
227 - && is_int( $translation_meta['status'] )
228 - ) {
229 - $translation['review'][] = $language_id;
230 - }
231 - }
232 -
233 - // Skip entries with no valid translations
234 - if ( empty( $translation['translations'] ) ) {
235 - continue;
236 - }
237 -
238 - $array_index = (string) mb_substr( $source, 0, 1 ) . (string) strlen( $source );
239 -
240 - $translations[ $array_index ][] = $translation;
241 - }
242 -
243 - // Cache translations for better performance
244 - set_transient(
245 - 'wplng_cached_translations',
246 - $translations,
247 - MONTH_IN_SECONDS
248 - );
249 -
250 - return $translations;
251 -}
252 -
253 -
254 -/**
255 - * Save a translation
256 - *
257 - * @param string $language_id
258 - * @param string $original
259 - * @param array $translation
260 - * @return int|false Post ID or false on failure
261 - */
262 -function wplng_save_translation_new( $language_id, $original, $translation ) {
263 -
264 - $translation = str_replace( '\\', '', $translation );
265 -
266 - /**
267 - * Make the title
268 - */
269 -
270 - $tite_max_length = 100;
271 - $title = mb_substr( $original, 0, $tite_max_length );
272 - $title = wplng_text_esc_displayed( $title );
273 - $title = wp_encode_emoji( $title );
274 - if ( strlen( $original ) > $tite_max_length ) {
275 - $title .= __( '...', 'wplingua' );
276 - }
277 -
278 - /**
279 - * Create the post and get this ID
280 - */
281 -
282 - $new_post_id = wp_insert_post(
283 - array(
284 - 'post_title' => esc_html( $title ),
285 - 'post_type' => 'wplng_translation',
286 - 'post_status' => 'publish',
287 - 'post_name' => sanitize_title(
288 - 'wplingua-translation-' . md5( $title ) . '-' . $title
289 - ),
290 - )
291 - );
292 -
293 - if ( is_wp_error( $new_post_id ) || empty( $new_post_id ) ) {
294 - return false;
295 - }
296 -
297 - /**
298 - * Make $translation_meta
299 - */
300 -
301 - $languages_target = wplng_get_languages_target_ids();
302 - $translation_meta = array();
303 -
304 - foreach ( $languages_target as $target_language ) {
305 -
306 - if ( $target_language === $language_id ) {
307 - $translation_meta[] = array(
308 - 'language_id' => $target_language,
309 - 'translation' => esc_html( $translation ),
310 - 'status' => 'generated',
311 - );
312 - } else {
313 - $translation_meta[] = array(
314 - 'language_id' => $target_language,
315 - 'translation' => '[WPLNG_EMPTY]',
316 - 'status' => 'ungenerated',
317 - );
318 - }
319 - }
320 -
321 - /**
322 - * Get the URL where the translation was discovered
323 - */
324 -
325 - $discovery_url = wplng_get_url_original();
326 - $discovery_url = wp_make_link_relative( $discovery_url );
327 - $discovery_url = remove_query_arg(
328 - array( 'wplng-mode', 'wplng-load', 'nocache' ),
329 - $discovery_url
330 - );
331 -
332 - /**
333 - * Save meta: Discovered URL
334 - */
335 -
336 - $meta_return = add_post_meta(
337 - $new_post_id,
338 - 'wplng_translation_discovery_url',
339 - $discovery_url
340 - );
341 -
342 - if ( false === $meta_return ) {
343 - wp_delete_post( $new_post_id, true );
344 - return false;
345 - }
346 -
347 - /**
348 - * Save meta: original language ID
349 - */
350 -
351 - $meta_return = add_post_meta(
352 - $new_post_id,
353 - 'wplng_translation_original_language_id',
354 - wplng_get_language_website_id()
355 - );
356 -
357 - if ( false === $meta_return ) {
358 - wp_delete_post( $new_post_id, true );
359 - return false;
360 - }
361 -
362 - /**
363 - * Save meta: original text MD5
364 - */
365 -
366 - $meta_return = add_post_meta(
367 - $new_post_id,
368 - 'wplng_translation_md5',
369 - md5( $original )
370 - );
371 -
372 - if ( false === $meta_return ) {
373 - wp_delete_post( $new_post_id, true );
374 - return false;
375 - }
376 -
377 - /**
378 - * Save meta: original text
379 - */
380 -
381 - $meta_return = add_post_meta(
382 - $new_post_id,
383 - 'wplng_translation_original',
384 - $original
385 - );
386 -
387 - if ( false === $meta_return ) {
388 -
389 - // Try to save it with encoded emoji
390 - $original = wp_encode_emoji( $original );
391 -
392 - $meta_return = add_post_meta(
393 - $new_post_id,
394 - 'wplng_translation_original',
395 - $original
396 - );
397 -
398 - if ( false === $meta_return ) {
399 - wp_delete_post( $new_post_id, true );
400 - return false;
401 - }
402 - }
403 -
404 - /**
405 - * Save meta: Translation array as JSON
406 - */
407 -
408 - $meta_return = add_post_meta(
409 - $new_post_id,
410 - 'wplng_translation_translations',
411 - wp_json_encode(
412 - $translation_meta,
413 - JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
414 - )
415 - );
416 -
417 - if ( false === $meta_return ) {
418 -
419 - // Try to save it with encoded emoji
420 - foreach ( $translation_meta as $key => $translation ) {
421 -
422 - if ( empty( $translation['translation'] )
423 - || '[WPLNG_EMPTY]' === $translation['translation']
424 - ) {
425 - continue;
426 - }
427 -
428 - $translation_meta[ $key ]['translation'] = wp_encode_emoji( $translation['translation'] );
429 - }
430 -
431 - $meta_return = add_post_meta(
432 - $new_post_id,
433 - 'wplng_translation_translations',
434 - wp_json_encode(
435 - $translation_meta,
436 - JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
437 - )
438 - );
439 -
440 - if ( false === $meta_return ) {
441 - wp_delete_post( $new_post_id, true );
442 - return false;
443 - }
444 - }
445 -
446 - return $new_post_id;
447 -}
448 -
449 -
450 -/**
451 - * Update a translation
452 - *
453 - * @param object $post
454 - * @param string $language_id
455 - * @param array $translation
456 - * @return int|false Post ID, false on failure
457 - */
458 -function wplng_update_translation( $post, $language_id, $translation ) {
459 -
460 - $translation = str_replace( '\\', '', $translation );
461 - $meta = get_post_meta( $post->ID );
462 - $languages_target = wplng_get_languages_target_ids();
463 - $website_language = wplng_get_language_website_id();
464 -
465 - $translation_meta = ( empty( $meta['wplng_translation_translations'][0] ) )
466 - ? array() :
467 - json_decode( $meta['wplng_translation_translations'][0], true );
468 -
469 - $original_language_id_meta = ( empty( $meta['wplng_translation_original_language_id'][0] ) )
470 - ? wplng_get_language_website_id()
471 - : $meta['wplng_translation_original_language_id'][0];
472 -
473 - // If translation found is not valid
474 - if ( empty( $translation_meta )
475 - || $website_language !== $original_language_id_meta
476 - ) {
477 -
478 - /**
479 - * $original_language_id_meta must be the same as in option page
480 - */
481 -
482 - $meta_return = update_post_meta(
483 - $post->ID,
484 - 'wplng_translation_original_language_id',
485 - $website_language
486 - );
487 -
488 - if ( false === $meta_return ) {
489 - return false;
490 - }
491 -
492 - /**
493 - * Make $translation_meta
494 - */
495 -
496 - $translation_meta = array();
497 - foreach ( $languages_target as $key => $target_language ) {
498 -
499 - if ( $target_language === $language_id && $translation !== '[WPLNG_EMPTY]' ) {
500 - $translation_meta[] = array(
501 - 'language_id' => $target_language,
502 - 'translation' => '[WPLNG_EMPTY]',
503 - 'status' => 'ungenerated',
504 - );
505 - } else {
506 - $translation_meta[] = array(
507 - 'language_id' => $target_language,
508 - 'translation' => esc_html( $translation ),
509 - 'status' => 'generated',
510 - );
511 - }
512 - }
513 - } else { // Translation is valid
514 -
515 - // Set or update new translation
516 - $translation_already_in = false;
517 -
518 - foreach ( $translation_meta as $key => $translation_e ) {
519 - if ( $translation_e['language_id'] === $language_id ) {
520 - $translation_already_in = true;
521 - $translation_meta[ $key ] = array(
522 - 'language_id' => $language_id,
523 - 'translation' => esc_html( $translation ),
524 - 'status' => 'generated',
525 - );
526 - break;
527 - }
528 - }
529 -
530 - if ( ! $translation_already_in ) {
531 - $translation_meta[] = array(
532 - 'language_id' => $language_id,
533 - 'translation' => esc_html( $translation ),
534 - 'status' => 'generated',
535 - );
536 - }
537 - }
538 -
539 - /**
540 - * Update the translations post meta
541 - */
542 -
543 - $meta_return = update_post_meta(
544 - $post->ID,
545 - 'wplng_translation_translations',
546 - wp_json_encode(
547 - $translation_meta,
548 - JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
549 - )
550 - );
551 -
552 - if ( false === $meta_return ) {
553 -
554 - // Try to save it with encoded emoji
555 - foreach ( $translation_meta as $key => $translation ) {
556 -
557 - if ( empty( $translation['translation'] )
558 - || '[WPLNG_EMPTY]' === $translation['translation']
559 - ) {
560 - continue;
561 - }
562 -
563 - $translation_meta[ $key ]['translation'] = wp_encode_emoji( $translation['translation'] );
564 - }
565 -
566 - $meta_return = update_post_meta(
567 - $post->ID,
568 - 'wplng_translation_translations',
569 - wp_json_encode(
570 - $translation_meta,
571 - JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
572 - )
573 - );
574 -
575 - if ( false === $meta_return ) {
576 - return false;
577 - }
578 - }
579 -
580 - return $post->ID;
581 -}
582 -
583 -
584 -/**
585 - * Save a list of translations
586 - *
587 - * @param array $translations
588 - * @param string $language_target_id
589 - * @return array $translations with post IDs
590 - */
591 -function wplng_save_translations( $translations, $language_target_id ) {
592 -
593 - if ( empty( $translations ) || ! is_array( $translations ) ) {
594 - return array();
595 - }
596 -
597 - foreach ( $translations as $key => $translation ) {
598 -
599 - if (
600 - ! isset( $translation['source'] ) // Original text
601 - || ! isset( $translation['translation'] ) // Translater text
602 - ) {
603 - continue;
604 - }
605 -
606 - $translations[ $key ]['post_id'] = wplng_save_translation(
607 - $language_target_id,
608 - $translation['source'],
609 - $translation['translation'],
610 - false
611 - );
612 - }
613 -
614 - wplng_clear_translations_cache();
615 -
616 - return $translations;
617 -}
618 -
619 -
620 -/**
621 - * Save translation
622 - *
623 - * @param string $target_language_id
624 - * @param string $original
625 - * @param array $translation
626 - * @return array $translation with post ID
627 - */
628 -function wplng_save_translation( $target_language_id, $original, $translation, $clear_cache = true ) {
629 -
630 - $saved_translation = wplng_get_translation_saved_from_original( $original );
631 -
632 - if ( empty( $saved_translation ) ) {
633 - // Create new translation post
634 - return wplng_save_translation_new(
635 - $target_language_id,
636 - $original,
637 - $translation
638 - );
639 - } else {
640 - // Update the translation post
641 - return wplng_update_translation(
642 - $saved_translation,
643 - $target_language_id,
644 - $translation
645 - );
646 - }
647 -
648 - if ( $clear_cache ) {
649 - wplng_clear_translations_cache();
650 - }
651 -}
1 +<?php
2 +
3 +// If this file is called directly, abort.
4 +if ( ! defined( 'WPINC' ) ) {
5 + die;
6 +}
7 +
8 +
9 +/**
10 + * Get the translated text from translations array
11 + *
12 + * @param string $text
13 + * @param array $translations
14 + * @return string
15 + */
16 +function wplng_get_translated_text_from_translations( $text, $translations ) {
17 +
18 + if ( empty( trim( $text ) ) ) {
19 + return $text;
20 + }
21 +
22 + // Manage non breaking space
23 + $text = str_replace(
24 + array( '&nbsp;', html_entity_decode( '&nbsp;' ) ),
25 + array( ' ', ' ' ),
26 + $text
27 + );
28 +
29 + /**
30 + * Get spaces before and after text
31 + */
32 + $temp = array();
33 + $spaces_before = '';
34 + $spaces_after = '';
35 +
36 + preg_match( '/^(\s*).*/', $text, $temp );
37 + if ( ! empty( $temp[1] ) ) {
38 + $spaces_before = $temp[1];
39 + }
40 +
41 + preg_match( '/(\s+)$/', $text, $temp );
42 + if ( ! empty( $temp[1] ) ) {
43 + $spaces_after = $temp[1];
44 + }
45 +
46 + $text = wplng_text_esc( $text );
47 + $translated = $text;
48 +
49 + foreach ( $translations as $translation ) {
50 + if ( $text === $translation['source'] ) {
51 + $translated = $translation['translation'];
52 + break;
53 + }
54 + }
55 +
56 + return $spaces_before . $translated . $spaces_after;
57 +}
58 +
59 +
60 +/**
61 + * Get the count of published 'wplng_translation' posts.
62 + *
63 + * This function retrieves the number of posts of type 'wplng_translation'
64 + * that are currently published on the site.
65 + *
66 + * @return int The number of published 'wplng_translation' posts.
67 + */
68 +function wplng_get_translation_count() {
69 +
70 + global $wplng_translation_count;
71 +
72 + if ( null !== $wplng_translation_count ) {
73 + return $wplng_translation_count;
74 + }
75 +
76 + $count_posts = wp_count_posts( 'wplng_translation' );
77 + $wplng_translation_count = (int) ( $count_posts->publish ?? 0 );
78 +
79 + return $wplng_translation_count;
80 +}
81 +
82 +
83 +/**
84 + * Get translation data from original text
85 + *
86 + * @param string $original
87 + * @return array Translation data
88 + */
89 +function wplng_get_translation_saved_from_original( $original ) {
90 +
91 + if ( empty( $original ) ) {
92 + return false;
93 + }
94 +
95 + $args = array(
96 + 'post_type' => 'wplng_translation',
97 + 'posts_per_page' => -1,
98 + 'fields' => 'ids',
99 + 'meta_query' => array(
100 + 'relation' => 'AND',
101 + array(
102 + 'key' => 'wplng_translation_md5',
103 + 'value' => md5( $original ),
104 + 'compare' => '=',
105 + ),
106 + array(
107 + 'key' => 'wplng_translation_original_language_id',
108 + 'value' => wplng_get_language_website_id(),
109 + 'compare' => '=',
110 + ),
111 + ),
112 + );
113 +
114 + $posts = get_posts( $args );
115 +
116 + if ( empty( $posts ) ) {
117 + return false;
118 + }
119 +
120 + foreach ( $posts as $post_id ) {
121 +
122 + $meta = get_post_meta( $post_id );
123 +
124 + if ( isset( $meta['wplng_translation_original'][0] )
125 + && is_string( $meta['wplng_translation_original'][0] )
126 + && $meta['wplng_translation_original'][0] === $original
127 + ) {
128 + return get_post( $post_id );
129 + }
130 + }
131 +
132 + return false;
133 +}
134 +
135 +
136 +/**
137 + * Get all saved translations for all languages from cache or wp_query
138 + *
139 + * @return array
140 + */
141 +function wplng_get_translations() {
142 +
143 + $translations = get_option( 'wplng_cached_translations' );
144 +
145 + if ( empty( $translations )
146 + || ! is_array( $translations )
147 + ) {
148 + $translations = wplng_get_translations_from_query();
149 + }
150 +
151 + return $translations;
152 +}
153 +
154 +
155 +/**
156 + * Get all saved translations from a wp_query for all languages
157 + *
158 + * @return array
159 + */
160 +function wplng_get_translations_from_query() {
161 +
162 + $translations = array();
163 +
164 + $args = array(
165 + 'post_type' => 'wplng_translation',
166 + 'posts_per_page' => -1,
167 + 'no_found_rows' => true,
168 + 'update_post_term_cache' => false,
169 + 'update_post_meta_cache' => false,
170 + 'cache_results' => false,
171 + 'fields' => 'ids', // Retrieve only post IDs for better performance
172 + 'meta_query' => array(
173 + array(
174 + 'key' => 'wplng_translation_original_language_id',
175 + 'value' => wplng_get_language_website_id(),
176 + 'compare' => '=',
177 + ),
178 + ),
179 + );
180 +
181 + $post_ids = get_posts( $args );
182 +
183 + if ( empty( $post_ids ) ) {
184 + return array();
185 + }
186 +
187 + foreach ( $post_ids as $id ) {
188 +
189 + $meta = get_post_meta( $id );
190 +
191 + // Ensure 'wplng_translation_original' exists and is a valid string
192 + if ( empty( $meta['wplng_translation_original'][0] )
193 + || ! is_string( $meta['wplng_translation_original'][0] )
194 + || trim( $meta['wplng_translation_original'][0] ) === ''
195 + ) {
196 + continue;
197 + }
198 +
199 + $source = wplng_text_esc( trim( $meta['wplng_translation_original'][0] ) );
200 +
201 + $translation = array(
202 + 'source' => $source,
203 + 'post_id' => $id,
204 + 'review' => array(),
205 + 'translations' => array(),
206 + );
207 +
208 + // Ensure 'wplng_translation_translations' exists
209 + if ( empty( $meta['wplng_translation_translations'][0] ) ) {
210 + continue;
211 + }
212 +
213 + $translations_meta = json_decode( $meta['wplng_translation_translations'][0], true );
214 +
215 + // Ensure JSON decoding was successful
216 + if ( ! is_array( $translations_meta ) ) {
217 + continue;
218 + }
219 +
220 + foreach ( $translations_meta as $translation_meta ) {
221 + // Validate each translation entry
222 + if ( empty( $translation_meta['language_id'] )
223 + || ! wplng_is_valid_language_id( $translation_meta['language_id'] )
224 + || (
225 + isset( $translation_meta['translation'] )
226 + && $translation_meta['translation'] === '[WPLNG_EMPTY]'
227 + )
228 + ) {
229 + continue;
230 + }
231 +
232 + $language_id = sanitize_key( $translation_meta['language_id'] );
233 +
234 + if ( isset( $translation_meta['translation'] )
235 + && is_string( $translation_meta['translation'] )
236 + ) {
237 + $translated_text = wplng_text_esc( $translation_meta['translation'] );
238 +
239 + $translation['translations'][ $language_id ] = $translated_text;
240 + }
241 +
242 + if ( isset( $translation_meta['status'] )
243 + && is_int( $translation_meta['status'] )
244 + ) {
245 + $translation['review'][] = $language_id;
246 + }
247 + }
248 +
249 + // Skip entries with no valid translations
250 + if ( empty( $translation['translations'] ) ) {
251 + continue;
252 + }
253 +
254 + $array_index = (string) mb_substr( $source, 0, 1 ) . (string) strlen( $source );
255 +
256 + $translations[ $array_index ][] = $translation;
257 + }
258 +
259 + // Cache translations as a persistent option
260 + update_option( 'wplng_cached_translations', $translations, false );
261 +
262 + return $translations;
263 +}
264 +
265 +
266 +/**
267 + * Save a translation
268 + *
269 + * @param string $language_id
270 + * @param string $original
271 + * @param array $translation
272 + * @return int|false Post ID or false on failure
273 + */
274 +function wplng_save_translation_new( $language_id, $original, $translation ) {
275 +
276 + $translation = str_replace( '\\', '', $translation );
277 +
278 + /**
279 + * Make the title
280 + */
281 +
282 + $tite_max_length = 100;
283 + $title = mb_substr( $original, 0, $tite_max_length );
284 + $title = wplng_text_esc_displayed( $title );
285 + $title = wp_encode_emoji( $title );
286 + if ( strlen( $original ) > $tite_max_length ) {
287 + $title .= __( '...', 'wplingua' );
288 + }
289 +
290 + /**
291 + * Create the post and get this ID
292 + */
293 +
294 + $new_post_id = wp_insert_post(
295 + array(
296 + 'post_title' => esc_html( $title ),
297 + 'post_type' => 'wplng_translation',
298 + 'post_status' => 'publish',
299 + 'post_name' => sanitize_title(
300 + 'wplingua-translation-' . md5( $title ) . '-' . $title
301 + ),
302 + )
303 + );
304 +
305 + if ( is_wp_error( $new_post_id ) || empty( $new_post_id ) ) {
306 + return false;
307 + }
308 +
309 + /**
310 + * Make $translation_meta
311 + */
312 +
313 + $languages_target = wplng_get_languages_target_ids();
314 + $translation_meta = array();
315 +
316 + foreach ( $languages_target as $target_language ) {
317 +
318 + if ( $target_language === $language_id ) {
319 + $translation_meta[] = array(
320 + 'language_id' => $target_language,
321 + 'translation' => esc_html( $translation ),
322 + 'status' => 'generated',
323 + );
324 + } else {
325 + $translation_meta[] = array(
326 + 'language_id' => $target_language,
327 + 'translation' => '[WPLNG_EMPTY]',
328 + 'status' => 'ungenerated',
329 + );
330 + }
331 + }
332 +
333 + /**
334 + * Get the URL where the translation was discovered
335 + */
336 +
337 + $discovery_url = wplng_get_url_original();
338 + $discovery_url = wp_make_link_relative( $discovery_url );
339 + $discovery_url = remove_query_arg(
340 + array( 'wplng-mode', 'wplng-load', 'nocache' ),
341 + $discovery_url
342 + );
343 +
344 + /**
345 + * Save meta: Discovered URL
346 + */
347 +
348 + $meta_return = add_post_meta(
349 + $new_post_id,
350 + 'wplng_translation_discovery_url',
351 + $discovery_url
352 + );
353 +
354 + if ( false === $meta_return ) {
355 + wp_delete_post( $new_post_id, true );
356 + return false;
357 + }
358 +
359 + /**
360 + * Save meta: original language ID
361 + */
362 +
363 + $meta_return = add_post_meta(
364 + $new_post_id,
365 + 'wplng_translation_original_language_id',
366 + wplng_get_language_website_id()
367 + );
368 +
369 + if ( false === $meta_return ) {
370 + wp_delete_post( $new_post_id, true );
371 + return false;
372 + }
373 +
374 + /**
375 + * Save meta: original text MD5
376 + */
377 +
378 + $meta_return = add_post_meta(
379 + $new_post_id,
380 + 'wplng_translation_md5',
381 + md5( $original )
382 + );
383 +
384 + if ( false === $meta_return ) {
385 + wp_delete_post( $new_post_id, true );
386 + return false;
387 + }
388 +
389 + /**
390 + * Save meta: original text
391 + */
392 +
393 + $meta_return = add_post_meta(
394 + $new_post_id,
395 + 'wplng_translation_original',
396 + $original
397 + );
398 +
399 + if ( false === $meta_return ) {
400 +
401 + // Try to save it with encoded emoji
402 + $original = wp_encode_emoji( $original );
403 +
404 + $meta_return = add_post_meta(
405 + $new_post_id,
406 + 'wplng_translation_original',
407 + $original
408 + );
409 +
410 + if ( false === $meta_return ) {
411 + wp_delete_post( $new_post_id, true );
412 + return false;
413 + }
414 + }
415 +
416 + /**
417 + * Save meta: Translation array as JSON
418 + */
419 +
420 + $meta_return = add_post_meta(
421 + $new_post_id,
422 + 'wplng_translation_translations',
423 + wp_json_encode(
424 + $translation_meta,
425 + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
426 + )
427 + );
428 +
429 + if ( false === $meta_return ) {
430 +
431 + // Try to save it with encoded emoji
432 + foreach ( $translation_meta as $key => $translation ) {
433 +
434 + if ( empty( $translation['translation'] )
435 + || '[WPLNG_EMPTY]' === $translation['translation']
436 + ) {
437 + continue;
438 + }
439 +
440 + $translation_meta[ $key ]['translation'] = wp_encode_emoji( $translation['translation'] );
441 + }
442 +
443 + $meta_return = add_post_meta(
444 + $new_post_id,
445 + 'wplng_translation_translations',
446 + wp_json_encode(
447 + $translation_meta,
448 + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
449 + )
450 + );
451 +
452 + if ( false === $meta_return ) {
453 + wp_delete_post( $new_post_id, true );
454 + return false;
455 + }
456 + }
457 +
458 + return $new_post_id;
459 +}
460 +
461 +
462 +/**
463 + * Update a translation
464 + *
465 + * @param object $post
466 + * @param string $language_id
467 + * @param array $translation
468 + * @return int|false Post ID, false on failure
469 + */
470 +function wplng_update_translation( $post, $language_id, $translation ) {
471 +
472 + $translation = str_replace( '\\', '', $translation );
473 + $meta = get_post_meta( $post->ID );
474 + $languages_target = wplng_get_languages_target_ids();
475 + $website_language = wplng_get_language_website_id();
476 +
477 + $translation_meta = ( empty( $meta['wplng_translation_translations'][0] ) )
478 + ? array() :
479 + json_decode( $meta['wplng_translation_translations'][0], true );
480 +
481 + $original_language_id_meta = ( empty( $meta['wplng_translation_original_language_id'][0] ) )
482 + ? wplng_get_language_website_id()
483 + : $meta['wplng_translation_original_language_id'][0];
484 +
485 + // If translation found is not valid
486 + if ( empty( $translation_meta )
487 + || $website_language !== $original_language_id_meta
488 + ) {
489 +
490 + /**
491 + * $original_language_id_meta must be the same as in option page
492 + */
493 +
494 + $meta_return = update_post_meta(
495 + $post->ID,
496 + 'wplng_translation_original_language_id',
497 + $website_language
498 + );
499 +
500 + if ( false === $meta_return ) {
501 + return false;
502 + }
503 +
504 + /**
505 + * Make $translation_meta
506 + */
507 +
508 + $translation_meta = array();
509 + foreach ( $languages_target as $key => $target_language ) {
510 +
511 + if ( $target_language === $language_id && $translation !== '[WPLNG_EMPTY]' ) {
512 + $translation_meta[] = array(
513 + 'language_id' => $target_language,
514 + 'translation' => esc_html( $translation ),
515 + 'status' => 'generated',
516 + );
517 + } else {
518 + $translation_meta[] = array(
519 + 'language_id' => $target_language,
520 + 'translation' => '[WPLNG_EMPTY]',
521 + 'status' => 'ungenerated',
522 + );
523 + }
524 + }
525 + } else { // Translation is valid
526 +
527 + // Set or update new translation
528 + $translation_already_in = false;
529 +
530 + foreach ( $translation_meta as $key => $translation_e ) {
531 + if ( $translation_e['language_id'] === $language_id ) {
532 + $translation_already_in = true;
533 + $translation_meta[ $key ] = array(
534 + 'language_id' => $language_id,
535 + 'translation' => esc_html( $translation ),
536 + 'status' => 'generated',
537 + );
538 + break;
539 + }
540 + }
541 +
542 + if ( ! $translation_already_in ) {
543 + $translation_meta[] = array(
544 + 'language_id' => $language_id,
545 + 'translation' => esc_html( $translation ),
546 + 'status' => 'generated',
547 + );
548 + }
549 + }
550 +
551 + /**
552 + * Update the translations post meta
553 + */
554 +
555 + $meta_return = update_post_meta(
556 + $post->ID,
557 + 'wplng_translation_translations',
558 + wp_json_encode(
559 + $translation_meta,
560 + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
561 + )
562 + );
563 +
564 + if ( false === $meta_return ) {
565 +
566 + // Try to save it with encoded emoji
567 + foreach ( $translation_meta as $key => $translation ) {
568 +
569 + if ( empty( $translation['translation'] )
570 + || '[WPLNG_EMPTY]' === $translation['translation']
571 + ) {
572 + continue;
573 + }
574 +
575 + $translation_meta[ $key ]['translation'] = wp_encode_emoji( $translation['translation'] );
576 + }
577 +
578 + $meta_return = update_post_meta(
579 + $post->ID,
580 + 'wplng_translation_translations',
581 + wp_json_encode(
582 + $translation_meta,
583 + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
584 + )
585 + );
586 +
587 + if ( false === $meta_return ) {
588 + return false;
589 + }
590 + }
591 +
592 + return $post->ID;
593 +}
594 +
595 +
596 +/**
597 + * Save a list of translations
598 + *
599 + * @param array $translations
600 + * @param string $language_target_id
601 + * @return array $translations with post IDs
602 + */
603 +function wplng_save_translations( $translations, $language_target_id ) {
604 +
605 + if ( empty( $translations ) || ! is_array( $translations ) ) {
606 + return array();
607 + }
608 +
609 + foreach ( $translations as $key => $translation ) {
610 +
611 + if (
612 + ! isset( $translation['source'] ) // Original text
613 + || ! isset( $translation['translation'] ) // Translater text
614 + ) {
615 + continue;
616 + }
617 +
618 + $translations[ $key ]['post_id'] = wplng_save_translation(
619 + $language_target_id,
620 + $translation['source'],
621 + $translation['translation'],
622 + false
623 + );
624 + }
625 +
626 + wplng_clear_translations_cache();
627 +
628 + return $translations;
629 +}
630 +
631 +
632 +/**
633 + * Save translation
634 + *
635 + * @param string $target_language_id
636 + * @param string $original
637 + * @param array $translation
638 + * @return array $translation with post ID
639 + */
640 +function wplng_save_translation( $target_language_id, $original, $translation, $clear_cache = true ) {
641 +
642 + $saved_translation = wplng_get_translation_saved_from_original( $original );
643 +
644 + if ( empty( $saved_translation ) ) {
645 + // Create new translation post
646 + $result = wplng_save_translation_new(
647 + $target_language_id,
648 + $original,
649 + $translation
650 + );
651 + } else {
652 + // Update the translation post
653 + $result = wplng_update_translation(
654 + $saved_translation,
655 + $target_language_id,
656 + $translation
657 + );
658 + }
659 +
660 + if ( $clear_cache ) {
661 + wplng_clear_translations_cache();
662 + }
663 +
664 + return $result;
665 +}