$languages Codes of all active languages. * @param string $own Language code of the page; empty when the page has none. * @param array $translations Language code => id of the translated page, 0 or missing for none. * @return array Missing language codes. */ public static function missing( array $languages, string $own, array $translations ): array { if ( '' === $own ) { return array(); } $missing = array(); foreach ( $languages as $code ) { if ( $code === $own ) { continue; } if ( empty( $translations[ $code ] ) ) { $missing[] = $code; } } return $missing; } /** * What a translated page left as in the original (F-COMMERCE-02). * * @param array{title: string, description: string} $translation Title and search description of the translated page. * @param array{title: string, description: string} $original The same of the page in the default language. * @return array Names: same_title, no_description, same_description. */ public static function untranslated( array $translation, array $original ): array { $found = array(); $title = self::plain( $translation['title'] ); $first = self::plain( $original['title'] ); // One-word titles such as "Blog" or brand names are often the same in every language. if ( '' !== $title && str_contains( $title, ' ' ) && $title === $first ) { $found[] = 'same_title'; } $description = self::plain( $translation['description'] ); $source = self::plain( $original['description'] ); if ( '' !== $source && '' === $description ) { $found[] = 'no_description'; } elseif ( '' !== $source && $description === $source ) { $found[] = 'same_description'; } return $found; } /** * Text for comparing: lower case, single spaces. * * @param string $text Text. */ private static function plain( string $text ): string { return mb_strtolower( trim( (string) preg_replace( '/\s+/u', ' ', $text ) ) ); } }