[ Index ]

PHP Cross Reference of GlotPress

title

Body

[close]

/gp-includes/things/ -> translation-set.php (source)

   1  <?php
   2  /**
   3   * Things: GP_Translation_Set class
   4   *
   5   * @package GlotPress
   6   * @subpackage Things
   7   * @since 1.0.0
   8   */
   9  
  10  /**
  11   * Core class used to implement the translation sets.
  12   *
  13   * @since 1.0.0
  14   */
  15  class GP_Translation_Set extends GP_Thing {
  16  
  17      var $table_basename           = 'gp_translation_sets';
  18      var $field_names              = array( 'id', 'name', 'slug', 'project_id', 'locale' );
  19      var $non_db_field_names       = array( 'current_count', 'untranslated_count', 'waiting_count', 'fuzzy_count', 'all_count', 'warnings_count', 'percent_translated', 'wp_locale', 'last_modified' );
  20      var $int_fields               = array( 'id', 'project_id' );
  21      var $non_updatable_attributes = array( 'id' );
  22  
  23      /**
  24       * ID of the translation set.
  25       *
  26       * @var int
  27       */
  28      public $id;
  29  
  30      /**
  31       * Name of the translation set.
  32       *
  33       * @var string
  34       */
  35      public $name;
  36  
  37      /**
  38       * Slug of the translation set.
  39       *
  40       * @var string
  41       */
  42      public $slug;
  43  
  44      /**
  45       * Project ID of the translation set.
  46       *
  47       * @var int
  48       */
  49      public $project_id;
  50  
  51      /**
  52       * Locale of the translation set.
  53       *
  54       * @var string
  55       */
  56      public $locale;
  57  
  58      /**
  59       * GP project of the translation set.
  60       *
  61       * @var GP_Project
  62       */
  63      public $project;
  64  
  65      /**
  66       * Number of waiting translations.
  67       *
  68       * @var int
  69       */
  70      public $waiting_count;
  71  
  72      /**
  73       * Number of fuzzy translations.
  74       *
  75       * @var int
  76       */
  77      public $fuzzy_count;
  78  
  79      /**
  80       * Number of untranslated originals.
  81       *
  82       * @var int
  83       */
  84      public $untranslated_count;
  85  
  86      /**
  87       * Number of current translations.
  88       *
  89       * @var int
  90       */
  91      public $current_count;
  92  
  93      /**
  94       * Number of translations with warnings.
  95       *
  96       * @var int
  97       */
  98      public $warnings_count;
  99  
 100      /**
 101       * Number of all originals.
 102       *
 103       * @var int
 104       */
 105      public $all_count;
 106  
 107      /**
 108       * Sets restriction rules for fields.
 109       *
 110       * @since 1.0.0
 111       *
 112       * @param GP_Validation_Rules $rules The validation rules instance.
 113       */
 114  	public function restrict_fields( $rules ) {
 115          $rules->name_should_not_be( 'empty' );
 116          $rules->slug_should_not_be( 'empty' );
 117          $rules->locale_should_not_be( 'empty' );
 118          $rules->project_id_should_not_be( 'empty' );
 119      }
 120  
 121      /**
 122       * Normalizes an array with key-value pairs representing
 123       * a GP_Translation_Set object.
 124       *
 125       * @since 1.0.0
 126       *
 127       * @param array $args Arguments for a GP_Translation_Set object.
 128       * @return array Normalized arguments for a GP_Translation_Set object.
 129       */
 130  	public function normalize_fields( $args ) {
 131          $args = parent::normalize_fields( $args );
 132  
 133          if ( isset( $args['name'] ) && empty( $args['name'] ) ) {
 134              if ( isset( $args['locale'] ) && ! empty( $args['locale'] ) ) {
 135                  $locale       = GP_locales::by_slug( $args['locale'] );
 136                  $args['name'] = $locale->english_name;
 137              }
 138          }
 139  
 140          if ( isset( $args['slug'] ) && ! $args['slug'] ) {
 141              $args['slug'] = 'default';
 142          }
 143  
 144          if ( ! empty( $args['slug'] ) ) {
 145              $args['slug'] = gp_sanitize_slug( $args['slug'] );
 146          }
 147  
 148          return $args;
 149      }
 150  
 151      /**
 152       * Returns the English name of a locale.
 153       *
 154       * If the slug of the locale is not 'default' then the name of the
 155       * current translation sets gets added as a suffix.
 156       *
 157       * @since 1.0.0
 158       *
 159       * @param  string $separator Separator, in case the slug is not 'default'. Default: '&rarr;'.
 160       * @return string The English name of a locale.
 161       */
 162  	public function name_with_locale( $separator = '&rarr;' ) {
 163          $locale = GP_Locales::by_slug( $this->locale );
 164          $parts  = array( $locale->english_name );
 165  
 166          if ( 'default' !== $this->slug ) {
 167              $parts[] = $this->name;
 168          }
 169  
 170          return implode( '&nbsp;' . $separator . '&nbsp;', $parts );
 171      }
 172  
 173  	public function by_project_id_slug_and_locale( $project_id, $slug, $locale_slug ) {
 174          $result = $this->one(
 175              "SELECT * FROM $this->table
 176              WHERE slug = %s AND project_id= %d AND locale = %s",
 177              $slug,
 178              $project_id,
 179              $locale_slug
 180          );
 181  
 182          if ( ! $result && 0 === $project_id ) {
 183              $result = $this->create(
 184                  array(
 185                      'project_id' => $project_id,
 186                      'name'       => GP_Locales::by_slug( $locale_slug )->english_name,
 187                      'slug'       => $slug,
 188                      'locale'     => $locale_slug,
 189                  )
 190              );
 191          }
 192  
 193          return $result;
 194      }
 195  
 196  	public function by_locale( $locale_slug ) {
 197          return $this->many(
 198              "SELECT * FROM $this->table
 199              WHERE locale = %s",
 200              $locale_slug
 201          );
 202      }
 203  
 204  	public function existing_locales() {
 205          global $wpdb;
 206  
 207          // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
 208          return $wpdb->get_col( "SELECT DISTINCT(locale) FROM $this->table" );
 209      }
 210  
 211  	public function existing_slugs() {
 212          global $wpdb;
 213  
 214          // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
 215          return $wpdb->get_col( "SELECT DISTINCT(slug) FROM $this->table" );
 216      }
 217  
 218  	public function by_project_id( $project_id ) {
 219          return $this->many(
 220              "SELECT * FROM $this->table
 221              WHERE project_id = %d ORDER BY name ASC",
 222              $project_id
 223          );
 224      }
 225  
 226      /**
 227       * Import translations from a Translations object.
 228       *
 229       * @param  Translations $translations   the translations to be imported to this translation-set.
 230       * @param  string       $desired_status 'current', 'waiting' or 'fuzzy'.
 231       * @return boolean or void
 232       */
 233  	public function import( $translations, $desired_status = 'current' ) {
 234          wp_raise_memory_limit( 'gp_translations_import' );
 235  
 236          if ( ! isset( $this->project ) || ! $this->project ) {
 237              $this->project = GP::$project->get( $this->project_id );
 238          }
 239  
 240          // Fuzzy is also checked in the flags, but if all strings in an import are fuzzy, fuzzy status can be forced.
 241          if ( ! in_array( $desired_status, array( 'current', 'waiting', 'fuzzy' ), true ) ) {
 242              return false;
 243          }
 244  
 245          $locale = GP_Locales::by_slug( $this->locale );
 246          $user   = wp_get_current_user();
 247  
 248          $existing_translations = array();
 249  
 250          $current_translations_list        = GP::$translation->for_translation(
 251              $this->project,
 252              $this,
 253              'no-limit',
 254              array(
 255                  'status' => 'current',
 256              )
 257          );
 258          $existing_translations['current'] = new Translations();
 259          foreach ( $current_translations_list as $entry ) {
 260              $existing_translations['current']->add_entry( $entry );
 261          }
 262          unset( $current_translations_list );
 263  
 264          $translations_added = 0;
 265          foreach ( $translations->entries as $entry ) {
 266              if ( empty( $entry->translations ) ) {
 267                  continue;
 268              }
 269  
 270              $is_fuzzy = in_array( 'fuzzy', $entry->flags, true );
 271  
 272              /**
 273               * Filter whether to import fuzzy translations.
 274               *
 275               * @since 1.0.0
 276               *
 277               * @param bool              $import_over  Import fuzzy translation. Default true.
 278               * @param Translation_Entry $entry        Translation entry object to import.
 279               * @param Translations      $translations Translations collection.
 280               */
 281              if ( $is_fuzzy && ! apply_filters( 'gp_translation_set_import_fuzzy_translations', true, $entry, $translations ) ) {
 282                  continue;
 283              }
 284  
 285              /**
 286               * Filters the the status of imported translations of a translation set.
 287               *
 288               * @since 1.0.0
 289               * @since 2.3.0 Added `$new_translation` and `$old_translation` parameters.
 290               *
 291               * @param string              $status          The status of imported translations.
 292               * @param Translation_Entry   $new_translation Translation entry object to import.
 293               * @param GP_Translation|null $old_translation The previous translation.
 294               */
 295              $entry->status = apply_filters( 'gp_translation_set_import_status', $is_fuzzy ? 'fuzzy' : $desired_status, $entry, null );
 296  
 297              $entry->warnings = maybe_unserialize( GP::$translation_warnings->check( $entry->singular, $entry->plural, $entry->translations, $locale ) );
 298              if ( ! empty( $entry->warnings ) && 'current' === $entry->status ) {
 299                  $entry->status = 'waiting';
 300              }
 301  
 302              // Lazy load other entries.
 303              if ( ! isset( $existing_translations[ $entry->status ] ) ) {
 304                  $existing_translations_list              = GP::$translation->for_translation(
 305                      $this->project,
 306                      $this,
 307                      'no-limit',
 308                      array(
 309                          'status' => $entry->status,
 310                      )
 311                  );
 312                  $existing_translations[ $entry->status ] = new Translations();
 313                  foreach ( $existing_translations_list as $_entry ) {
 314                      $existing_translations[ $entry->status ]->add_entry( $_entry );
 315                  }
 316                  unset( $existing_translations_list );
 317              }
 318  
 319              $create     = false;
 320              $translated = $existing_translations[ $entry->status ]->translate_entry( $entry );
 321              if ( 'current' !== $entry->status && ! $translated ) {
 322                  // Don't create an entry if it already exists as current.
 323                  $translated = $existing_translations['current']->translate_entry( $entry );
 324              }
 325  
 326              if ( $translated ) {
 327                  // We have the same string translated, so create a new one if they don't match.
 328                  $entry->original_id      = $translated->original_id;
 329                  $translated_is_different = array_pad( $entry->translations, $locale->nplurals, null ) !== $translated->translations;
 330  
 331                  /**
 332                   * Filter whether to import over an existing translation on a translation set.
 333                   *
 334                   * @since 1.0.0
 335                   *
 336                   * @param bool $import_over Import over an existing translation.
 337                   */
 338                  $create = apply_filters( 'gp_translation_set_import_over_existing', $translated_is_different );
 339              } else {
 340                  // we don't have the string translated, let's see if the original is there
 341                  $original = GP::$original->by_project_id_and_entry( $this->project->id, $entry, '+active' );
 342                  if ( $original ) {
 343                      $entry->original_id = $original->id;
 344                      $create             = true;
 345                  }
 346              }
 347              if ( $create ) {
 348                  if ( $user ) {
 349                      $entry->user_id = $user->ID;
 350                  }
 351  
 352                  $entry->translation_set_id = $this->id;
 353  
 354                  $entry->status = apply_filters( 'gp_translation_set_import_status', $entry->status, $entry, $translated );
 355                  // Check for errors.
 356                  $translation = GP::$translation->create( $entry );
 357                  if ( is_object( $translation ) ) {
 358                      $translation->set_status( $entry->status );
 359                      $translations_added += 1;
 360                  }
 361              }
 362          }
 363  
 364          gp_clean_translation_set_cache( $this->id );
 365  
 366          /**
 367           * Fires after translations have been imported to a translation set.
 368           *
 369           * @since 1.0.0
 370           *
 371           * @param int $translation_set The ID of the translation set the import was made into.
 372           */
 373          do_action( 'gp_translations_imported', $this->id );
 374  
 375          return $translations_added;
 376      }
 377  
 378      /**
 379       * Retrieves the number of waiting translations.
 380       *
 381       * @return int Number of waiting translations.
 382       */
 383  	public function waiting_count() {
 384          if ( ! isset( $this->waiting_count ) ) {
 385              $this->update_status_breakdown();
 386          }
 387  
 388          return $this->waiting_count;
 389      }
 390  
 391      /**
 392       * Retrieves the number of untranslated originals.
 393       *
 394       * @return int Number of untranslated originals.
 395       */
 396  	public function untranslated_count() {
 397          if ( ! isset( $this->untranslated_count ) ) {
 398              $this->update_status_breakdown();
 399          }
 400  
 401          return $this->untranslated_count;
 402      }
 403  
 404      /**
 405       * Retrieves the number of fuzzy translations.
 406       *
 407       * @return int Number of fuzzy translations.
 408       */
 409  	public function fuzzy_count() {
 410          if ( ! isset( $this->fuzzy_count ) ) {
 411              $this->update_status_breakdown();
 412          }
 413  
 414          return $this->fuzzy_count;
 415      }
 416  
 417      /**
 418       * Retrieves the number of current translations.
 419       *
 420       * @return int Number of current translations.
 421       */
 422  	public function current_count() {
 423          if ( ! isset( $this->current_count ) ) {
 424              $this->update_status_breakdown();
 425          }
 426  
 427          return $this->current_count;
 428      }
 429  
 430      /**
 431       * Retrieves the number of translations with warnings.
 432       *
 433       * @return int Number of translations with warnings.
 434       */
 435  	public function warnings_count() {
 436          if ( ! isset( $this->warnings_count ) ) {
 437              $this->update_status_breakdown();
 438          }
 439  
 440          return $this->warnings_count;
 441      }
 442  
 443      /**
 444       * Retrieves the number of all originals.
 445       *
 446       * @return int Number of all originals.
 447       */
 448  	public function all_count() {
 449          if ( ! isset( $this->all_count ) ) {
 450              $this->update_status_breakdown();
 451          }
 452  
 453          return $this->all_count;
 454      }
 455  
 456      /**
 457       * Populates the count properties.
 458       */
 459  	public function update_status_breakdown() {
 460          $counts = wp_cache_get( $this->id, 'translation_set_status_breakdown' );
 461  
 462          /*
 463           * The format was changed in 2.1 and 3.0.
 464           *
 465           * In 2.1 the format was changed to an array of objects in the following sequence,
 466           * however not all may exist in the array, so for example, the array may only be 3
 467           * entries long and skip any of the values:
 468           *
 469           *     [0] = Current translations
 470           *     [1] = Fuzzy translations
 471           *     [2] = Rejected translations
 472           *     [3] = Old translations
 473           *     [4] = Translations with warnings
 474           *     [5] = All translations
 475           *
 476           * In 3.0 the untranslated object was added:
 477           *
 478           *     [0] = Current translations
 479           *     [1] = Fuzzy translations
 480           *     [2] = Rejected translations
 481           *     [3] = Old translations
 482           *     [4] = Translations with warnings
 483           *     [5] = Untranslated originals
 484           *     [6] = All translations
 485           *
 486           * Version 3.0 also introduced the cache 'version' array entry to allow for easy
 487           * detection of when the cache should be expired due to changes in the cache.
 488           *
 489           * Note: The _version key is unset after the cache is loaded so that it does not
 490           * get used in the for loops when setting the object properties.
 491           *
 492           */
 493          if ( ! is_array( $counts ) || ! array_key_exists( '_version', $counts ) || GP_CACHE_VERSION !== $counts['_version'] ) {
 494              global $wpdb;
 495              $counts = array();
 496  
 497              $status_counts = $wpdb->get_results(
 498                  $wpdb->prepare(
 499                      "SELECT
 500                          t.status AS translation_status,
 501                          COUNT(*) AS total,
 502                          COUNT( CASE WHEN o.priority = '-2' THEN o.priority END ) AS `hidden`,
 503                          COUNT( CASE WHEN o.priority <> '-2' THEN o.priority END ) AS `public`
 504                      FROM {$wpdb->gp_translations} AS t
 505                      INNER JOIN {$wpdb->gp_originals} AS o ON t.original_id = o.id
 506                      WHERE
 507                          t.translation_set_id = %d
 508                          AND o.status = '+active'
 509                      GROUP BY t.status",
 510                      $this->id
 511                  )
 512              );
 513  
 514              if ( $status_counts ) {
 515                  $counts = $status_counts;
 516              }
 517  
 518              $warnings_counts = $wpdb->get_row(
 519                  $wpdb->prepare(
 520                      "SELECT
 521                          COUNT(*) AS total,
 522                          COUNT( CASE WHEN o.priority = '-2' THEN o.priority END ) AS `hidden`,
 523                          COUNT( CASE WHEN o.priority <> '-2' THEN o.priority END ) AS `public`
 524                      FROM {$wpdb->gp_translations} AS t
 525                      INNER JOIN {$wpdb->gp_originals} AS o ON t.original_id = o.id
 526                      WHERE
 527                          t.translation_set_id = %d AND
 528                          o.status = '+active' AND
 529                          ( t.status = 'current' OR t.status = 'waiting' )
 530                          AND warnings IS NOT NULL
 531                          AND warnings != ''",
 532                      $this->id
 533                  )
 534              );
 535  
 536              if ( $warnings_counts ) {
 537                  $counts[] = (object) array(
 538                      'translation_status' => 'warnings',
 539                      'total'              => (int) $warnings_counts->total,
 540                      'hidden'             => (int) $warnings_counts->hidden,
 541                      'public'             => (int) $warnings_counts->public,
 542                  );
 543              }
 544  
 545              $untranslated_counts = $wpdb->get_row(
 546                  $wpdb->prepare(
 547                      "SELECT
 548                          COUNT(*) AS total,
 549                          COUNT( CASE WHEN o.priority = '-2' THEN o.priority END ) AS `hidden`,
 550                          COUNT( CASE WHEN o.priority <> '-2' THEN o.priority END ) AS `public`
 551                      FROM {$wpdb->gp_originals} AS o
 552                      LEFT JOIN {$wpdb->gp_translations} AS t ON o.id = t.original_id AND t.translation_set_id = %d AND t.status != 'rejected' AND t.status != 'old'
 553                      WHERE
 554                          o.project_id = %d AND
 555                          o.status = '+active' AND
 556                          t.translation_0 IS NULL",
 557                      $this->id,
 558                      $this->project_id
 559                  )
 560              );
 561  
 562              if ( $untranslated_counts ) {
 563                  $counts[] = (object) array(
 564                      'translation_status' => 'untranslated',
 565                      'total'              => (int) $untranslated_counts->total,
 566                      'public'             => (int) $untranslated_counts->public,
 567                      'hidden'             => (int) $untranslated_counts->hidden,
 568                  );
 569              }
 570  
 571              $counts['_version'] = GP_CACHE_VERSION;
 572  
 573              wp_cache_set( $this->id, $counts, 'translation_set_status_breakdown' );
 574          }
 575  
 576          if ( array_key_exists( '_version', $counts ) ) {
 577              unset( $counts['_version'] );
 578          }
 579  
 580          $all_count = GP::$original->count_by_project_id( $this->project_id, 'all' );
 581          $counts[]  = (object) array(
 582              'translation_status' => 'all',
 583              'total'              => $all_count->total,
 584              'hidden'             => $all_count->hidden,
 585              'public'             => $all_count->public,
 586          );
 587  
 588          $statuses   = GP::$translation->get_static( 'statuses' );
 589          $statuses[] = 'warnings';
 590          $statuses[] = 'untranslated';
 591          $statuses[] = 'all';
 592          foreach ( $statuses as $status ) {
 593              $this->{$status . '_count'} = 0;
 594          }
 595  
 596          $user_can_view_hidden = GP::$permission->current_user_can( 'write', 'project', $this->project_id );
 597          foreach ( $counts as $count ) {
 598              if ( in_array( $count->translation_status, $statuses, true ) ) {
 599                  $this->{$count->translation_status . '_count'} = $user_can_view_hidden ? (int) $count->total : (int) $count->public;
 600              }
 601          }
 602      }
 603  
 604      /**
 605       * Copies translations from a translation set to the current one
 606       *
 607       * This function doesn't merge then, just copies unconditionally. If a translation already exists, it will be duplicated.
 608       * When copying translations from another project, it will search to find the original first.
 609       */
 610  	public function copy_translations_from( $source_translation_set_id ) {
 611          global $wpdb;
 612          $current_date = $this->now_in_mysql_format();
 613  
 614          $source_set = GP::$translation_set->get( $source_translation_set_id );
 615          if ( $source_set->project_id != $this->project_id ) {
 616              $translations = GP::$translation->find_many_no_map( "translation_set_id = '{$source_set->id}'" );
 617              foreach ( $translations as $entry ) {
 618                  $source_original = GP::$original->get( $entry->original_id );
 619                  $original        = GP::$original->by_project_id_and_entry( $this->project_id, $source_original );
 620                  if ( $original ) {
 621                      $entry->original_id        = $original->id;
 622                      $entry->translation_set_id = $this->id;
 623                      GP::$translation->create( $entry );
 624                  }
 625              }
 626          } else {
 627              return $this->query(
 628                  "INSERT INTO $wpdb->gp_translations (
 629                      original_id,       translation_set_id, translation_0, translation_1, translation_2, user_id, status, date_added,       date_modified, warnings
 630                  )
 631                  SELECT
 632                      original_id, %s AS translation_set_id, translation_0, translation_1, translation_2, user_id, status, date_added, %s AS date_modified, warnings
 633                  FROM $wpdb->gp_translations WHERE translation_set_id = %s",
 634                  $this->id,
 635                  $current_date,
 636                  $source_translation_set_id
 637              );
 638          }
 639      }
 640  
 641  
 642  	public function percent_translated() {
 643          $original_counts = GP::$original->count_by_project_id( $this->project_id, 'all' );
 644  
 645          if ( GP::$permission->current_user_can( 'write', 'project', $this->project_id ) ) {
 646              $original_count = $original_counts->total;
 647          } else {
 648              $original_count = $original_counts->public;
 649          }
 650  
 651          return $original_count ? floor( $this->current_count() / $original_count * 100 ) : 0;
 652      }
 653  
 654      /**
 655       * Retrieves the last modified date of a translation in this translation set.
 656       *
 657       * @since 1.0.0
 658       *
 659       * @return string|false The last modified date on success, false on failure.
 660       */
 661  	public function last_modified() {
 662          return GP::$translation->last_modified( $this );
 663      }
 664  
 665      /**
 666       * Deletes a translation set and all of its translations and glossaries.
 667       *
 668       * @since 2.0.0
 669       *
 670       * @return bool
 671       */
 672  	public function delete() {
 673          GP::$translation->delete_many( array( 'translation_set_id' => $this->id ) );
 674  
 675          GP::$glossary->delete_many( array( 'translation_set_id', $this->id ) );
 676  
 677          return parent::delete();
 678      }
 679  
 680      /**
 681       * Executes after creating a translation set.
 682       *
 683       * @since 3.0.0
 684       *
 685       * @return bool
 686       */
 687  	public function after_create() {
 688          /**
 689           * Fires after creating a translation set.
 690           *
 691           * @since 3.0.0
 692           *
 693           * @param GP_Translation_Set $translation_set The translation set that was created.
 694           */
 695          do_action( 'gp_translation_set_created', $this );
 696  
 697          return true;
 698      }
 699  
 700      /**
 701       * Executes after saving a translation set.
 702       *
 703       * @since 3.0.0
 704       *
 705       * @param GP_Translation_Set $translation_set_before Translation set before the update.
 706       * @return bool
 707       */
 708  	public function after_save( $translation_set_before ) {
 709          /**
 710           * Fires after saving a translation set.
 711           *
 712           * @since 3.0.0
 713           *
 714           * @param GP_Translation_Set $translation_set        Translation set following the update.
 715           * @param GP_Translation_Set $translation_set_before Translation set before the update.
 716           */
 717          do_action( 'gp_translation_set_saved', $this, $translation_set_before );
 718  
 719          return true;
 720      }
 721  
 722      /**
 723       * Executes after deleting a translation set.
 724       *
 725       * @since 3.0.0
 726       *
 727       * @return bool
 728       */
 729  	public function after_delete() {
 730          /**
 731           * Fires after deleting a translation set.
 732           *
 733           * @since 3.0.0
 734           *
 735           * @param GP_Translation_Set $translation_set The translation set that was deleted.
 736           */
 737          do_action( 'gp_translation_set_deleted', $this );
 738  
 739          return true;
 740      }
 741  }
 742  GP::$translation_set = new GP_Translation_Set();


Generated: Sat Nov 23 01:01:06 2024 Cross-referenced by PHPXref 0.7.1