Випадок не супер складний але я вирішила про нього написати. При роботі з мультимовними сайтами виникає проблема, ми не можем прописати однакові слаги на обох мовних версіях статті. Безкоштовна версія плагіну Polylang не дає такої можливості. Обійти цей недолік можна використанням іншого плагіна – Permalink Manager Lite. Після активації плагіна в адмінці зявляється поле, воно по замовчуванню дублює слаг з рідного поля для слагу в WordPress. Проте його можна змінювати і поле виглядає так:

Прикладом буде ця стаття, я її зробила з однаковими слагами в українській і англійській версіях:
https://victoriaweb.me/how-to-make-identical-slugs-in-wordpress/
https://victoriaweb.me/en/how-to-make-identical-slugs-in-wordpress/

Автор: Maciej Bis

By WP SYNTEX
Альтернативний метод роботи зі слагами
На сайті GitHub є плагін розробника Ulrich Pogson – Polylang Slug. Я його протестувала на блоковій темі Blockera і він працює добре. Нижче код плагіна без перевірки версій, він також працює якщо додати в файл functions.php:
function polylang_slug_unique_slug_in_language( $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug ){
// Return slug if it was not changed.
if ( $original_slug === $slug ) {
return $slug;
}
global $wpdb;
// Get language of a post
$lang = pll_get_post_language( $post_ID );
$options = get_option( 'polylang' );
// return the slug if Polylang does not return post language or has incompatable redirect setting or is not translated post type.
if ( empty( $lang ) || 0 === $options['force_lang'] || ! pll_is_translated_post_type( $post_type ) ) {
return $slug;
}
// " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = ID".
$join_clause = polylang_slug_model_post_join_clause();
// " AND pll_tr.term_taxonomy_id IN (" . implode(',', $languages) . ")".
$where_clause = polylang_slug_model_post_where_clause( $lang );
// Polylang does not translate attachements - skip if it is one.
// @TODO Recheck this with the Polylang settings
if ( 'attachment' == $post_type ) {
// Attachment slugs must be unique across all types.
$check_sql = "SELECT post_name FROM $wpdb->posts $join_clause WHERE post_name = %s AND ID != %d $where_clause LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $original_slug, $post_ID ) );
} elseif ( is_post_type_hierarchical( $post_type ) ) {
// Page slugs must be unique within their own trees. Pages are in a separate
// namespace than posts so page slugs are allowed to overlap post slugs.
$check_sql = "SELECT ID FROM $wpdb->posts $join_clause WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d $where_clause LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $original_slug, $post_type, $post_ID, $post_parent ) );
} else {
// Post slugs must be unique across all posts.
$check_sql = "SELECT post_name FROM $wpdb->posts $join_clause WHERE post_name = %s AND post_type = %s AND ID != %d $where_clause LIMIT 1";
$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $original_slug, $post_type, $post_ID ) );
}
if ( ! $post_name_check ) {
return $original_slug;
}
return $slug;
}
add_filter( 'wp_unique_post_slug', 'polylang_slug_unique_slug_in_language', 10, 6 );
/**
* Modify the sql query to include checks for the current language.
*
* @since 0.1.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $query Database query.
*
* @return string The modified query.
*/
function polylang_slug_filter_queries( $query ) {
global $wpdb;
// Query for posts page, pages, attachments and hierarchical CPT. This is the only possible place to make the change. The SQL query is set in get_page_by_path()
$is_pages_sql = preg_match(
"#SELECT ID, post_name, post_parent, post_type FROM {$wpdb->posts} .*#",
polylang_slug_standardize_query( $query ),
$matches
);
if ( ! $is_pages_sql ) {
return $query;
}
// Check if should contine. Don't add $query polylang_slug_should_run() as $query is a SQL query.
if ( ! polylang_slug_should_run() ) {
return $query;
}
$lang = pll_current_language();
// " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = ID".
$join_clause = polylang_slug_model_post_join_clause();
// " AND pll_tr.term_taxonomy_id IN (" . implode(',', $languages) . ")".
$where_clause = polylang_slug_model_post_where_clause( $lang );
$query = preg_match(
"#(SELECT .* (?=FROM))(FROM .* (?=WHERE))(?:(WHERE .*(?=ORDER))|(WHERE .*$))(.*)#",
polylang_slug_standardize_query( $query ),
$matches
);
// Reindex array numerically $matches[3] and $matches[4] are not added together thus leaving a gap. With this $matches[5] moves up to $matches[4]
$matches = array_values( $matches );
// SELECT, FROM, INNER JOIN, WHERE, WHERE CLAUSE (additional), ORBER BY (if included)
$sql_query = $matches[1] . $matches[2] . $join_clause . $matches[3] . $where_clause . $matches[4];
/**
* Disable front end query modification.
*
* Allows disabling front end query modification if not needed.
*
* @since 0.2.0
*
* @param string $sql_query Database query.
* @param array $matches {
* @type string $matches[1] SELECT SQL Query.
* @type string $matches[2] FROM SQL Query.
* @type string $matches[3] WHERE SQL Query.
* @type string $matches[4] End of SQL Query (Possibly ORDER BY).
* }
* @param string $join_clause INNER JOIN Polylang clause.
* @param string $where_clause Additional Polylang WHERE clause.
*/
return apply_filters( 'polylang_slug_sql_query', $sql_query, $matches, $join_clause, $where_clause );
}
add_filter( 'query', 'polylang_slug_filter_queries' );
/**
* Extend the WHERE clause of the query.
*
* This allows the query to return only the posts of the current language
*
* @since 0.1.0
*
* @param string $where The WHERE clause of the query.
* @param WP_Query $query The WP_Query instance (passed by reference).
*
* @return string The WHERE clause of the query.
*/
function polylang_slug_posts_where_filter( $where, $query ) {
// Check if should contine.
if ( ! polylang_slug_should_run( $query ) ) {
return $where;
}
$lang = empty( $query->query['lang'] ) ? pll_current_language() : $query->query['lang'];
// " AND pll_tr.term_taxonomy_id IN (" . implode(',', $languages) . ")"
$where .= polylang_slug_model_post_where_clause( $lang );
return $where;
}
add_filter( 'posts_where', 'polylang_slug_posts_where_filter', 10, 2 );
/**
* Extend the JOIN clause of the query.
*
* This allows the query to return only the posts of the current language
*
* @since 0.1.0
*
* @param string $join The JOIN clause of the query.
* @param WP_Query $query The WP_Query instance (passed by reference).
*
* @return string The JOIN clause of the query.
*/
function polylang_slug_posts_join_filter( $join, $query ) {
// Check if should contine.
if ( ! polylang_slug_should_run( $query ) ) {
return $join;
}
// " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = ID".
$join .= polylang_slug_model_post_join_clause();
return $join;
}
add_filter( 'posts_join', 'polylang_slug_posts_join_filter', 10, 2 );
/**
* Check if the query needs to be adapted.
*
* @since 0.2.0
*
* @param WP_Query $query The WP_Query instance (passed by reference).
*
* @return bool
*/
function polylang_slug_should_run( $query = '' ) {
/**
* Disable front end query modification.
*
* Allows disabling front end query modification if not needed.
*
* @since 0.2.0
*
* @param bool false Not disabling run.
* @param WP_Query $query The WP_Query instance (passed by reference).
*/
// Do not run in admin or if Polylang is disabled
$disable = apply_filters( 'polylang_slug_disable', false, $query );
if ( is_admin() || is_feed() || ! function_exists( 'pll_current_language' ) || $disable ) {
return false;
}
// The lang query should be defined if the URL contains the language
$lang = empty( $query->query['lang'] ) ? pll_current_language() : $query->query['lang'];
// Checks if the post type is translated when doing a custom query with the post type defined
$is_translated = ! empty( $query->query['post_type'] ) && ! pll_is_translated_post_type( $query->query['post_type'] );
return ! ( empty( $lang ) || $is_translated );
}
/**
* Standardize the query.
*
* This makes the standardized and simpler to run regex on
*
* @since 0.2.0
*
* @param string $query Database query.
*
* @return string The standardized query.
*/
function polylang_slug_standardize_query( $query ) {
// Strip tabs, newlines and multiple spaces.
$query = str_replace(
array( "\t", " \n", "\n", " \r", "\r", " ", " " ),
array( '', ' ', ' ', ' ', ' ', ' ', ' ' ),
$query
);
return trim( $query );
}
/**
* Fetch the polylang join clause.
*
* @since 0.2.0
*
* @return string
*/
function polylang_slug_model_post_join_clause() {
if ( function_exists( 'PLL' ) ) {
return PLL()->model->post->join_clause();
} elseif ( array_key_exists( 'polylang', $GLOBALS ) ) {
global $polylang;
return $polylang->model->join_clause( 'post' );
}
return '';
}
/**
* Fetch the polylang where clause.
*
* @since 0.2.0
*
* @param string $lang The current language slug.
*
* @return string
*/
function polylang_slug_model_post_where_clause( $lang = '' ) {
if ( function_exists( 'PLL' ) ) {
return PLL()->model->post->where_clause( $lang );
} elseif ( array_key_exists( 'polylang', $GLOBALS ) ) {
global $polylang;
return $polylang->model->where_clause( $lang, 'post' );
}
return '';
}