Skip to content

Early-bird lifetime licence: $199 once, for the first 200 buyers only. See the offer

Greeklish addresses for Greek titles

New posts, pages, products, categories and tags with Greek titles get addresses in Latin letters: “Καλοκαιρινά φορέματα” becomes kalokairina-foremata. Existing addresses don’t change.

  • Type PHP
  • Runs Everywhere (runs as WordPress loads)
  • Category Greek e-shop
  • Tested with WordPress 7.1
PHP
<?php

// Greek letters in the addresses (slugs) WordPress makes for new posts,
// pages, products and terms become Latin letters. Other text, other slugs and
// addresses that already exist are left as they are.
$greeklish = static function ( string $text ): string {
	return (string) preg_replace_callback(
		'/\p{Greek}+/u',
		static function ( array $m ): string {
			$word = strtr( mb_strtolower( $m[0], 'UTF-8' ), array( 'ου' => 'ou', 'ού' => 'ou' ) );
			return strtr(
				$word,
				array(
					'α' => 'a', 'ά' => 'a', 'β' => 'v', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'έ' => 'e',
					'ζ' => 'z', 'η' => 'i', 'ή' => 'i', 'θ' => 'th', 'ι' => 'i', 'ί' => 'i', 'ϊ' => 'i',
					'ΐ' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => 'x', 'ο' => 'o',
					'ό' => 'o', 'π' => 'p', 'ρ' => 'r', 'σ' => 's', 'ς' => 's', 'τ' => 't', 'υ' => 'y',
					'ύ' => 'y', 'ϋ' => 'y', 'ΰ' => 'y', 'φ' => 'f', 'χ' => 'ch', 'ψ' => 'ps', 'ω' => 'o',
					'ώ' => 'o',
				)
			);
		},
		$text
	);
};

// WordPress cuts %-encoded slugs at 200 characters (a few dozen Greek letters),
// so the title or name the slug was made from is kept to transliterate in full.
$source = '';
add_filter(
	'wp_insert_post_empty_content',
	static function ( $maybe_empty, $postarr = array() ) use ( &$source ) {
		$source = (string) ( $postarr['post_title'] ?? '' );
		return $maybe_empty;
	},
	10,
	2
);
add_filter(
	'pre_insert_term',
	static function ( $term ) use ( &$source ) {
		$source = is_string( $term ) ? $term : '';
		return $term;
	}
);

// A slug with Greek letters (WordPress stores them %-encoded) in Latin letters,
// or null when there's nothing to change.
$latin_slug = static function ( string $slug ) use ( $greeklish, &$source ): ?string {
	$decoded = urldecode( $slug );
	if ( ! preg_match( '/\p{Greek}/u', $decoded ) ) {
		return null;
	}
	if ( '' !== $source && '' !== $slug && str_starts_with( sanitize_title_with_dashes( $source, '', 'save' ), $slug ) ) {
		$decoded = $source; // The slug was made from this text: use all of it.
	}
	$latin = sanitize_title_with_dashes( $greeklish( $decoded ), '', 'save' );
	return '' === $latin ? null : $latin;
};

// Posts, pages, products…: when WordPress makes a post's address unique.
$post_slug = null;
$post_slug = static function ( $slug, $post_id = 0, $status = '', $type = '', $parent = 0 ) use ( $latin_slug, &$post_slug ) {
	if ( $post_id && '' !== (string) get_post_field( 'post_name', $post_id ) && get_post_field( 'post_name', $post_id ) === $slug ) {
		return $slug; // An address the post already has stays.
	}
	$latin = $latin_slug( (string) $slug );
	if ( null === $latin ) {
		return $slug;
	}
	remove_filter( 'wp_unique_post_slug', $post_slug, 10 );
	$slug = wp_unique_post_slug( $latin, $post_id, $status, $type, $parent );
	add_filter( 'wp_unique_post_slug', $post_slug, 10, 5 );
	return $slug;
};
add_filter( 'wp_unique_post_slug', $post_slug, 10, 5 );

// Categories, tags and other terms: when WordPress makes a new term's slug unique.
$term_slug = null;
$term_slug = static function ( $slug, $term = null ) use ( $latin_slug, &$term_slug ) {
	$latin = $latin_slug( (string) $slug );
	if ( null === $latin || ! is_object( $term ) ) {
		return $slug;
	}
	remove_filter( 'wp_unique_term_slug', $term_slug, 10 );
	$slug = wp_unique_term_slug( $latin, $term );
	add_filter( 'wp_unique_term_slug', $term_slug, 10, 2 );
	return $slug;
};
add_filter( 'wp_unique_term_slug', $term_slug, 10, 2 );

In these packs

  • Greek e-shop: For shops in Greece: receipt or invoice with a checked VAT number (ΑΦΜ), Greek phone and postcode checks, a cash on delivery fee, and greeklish addresses.

Move your snippets over this afternoon.

Importing changes nothing until you switch over, and your old shortcodes keep working.