| 
          <?php | 
        
        
           | 
          
 | 
        
        
           | 
          /** | 
        
        
           | 
           * Translate post-type-archive-slug when different from post-type-slug. | 
        
        
           | 
           * | 
        
        
           | 
           * You can have your archive slug set to, for example /books and the singles on /book/title by setting | 
        
        
           | 
           * $args['rewrite'] => [ 'slug' => 'book', ... ]; | 
        
        
           | 
           * $args['has_archive'] => 'books'; | 
        
        
           | 
           * when registering your post_type | 
        
        
           | 
           * | 
        
        
           | 
           * WPML supports translating the single-slug, but not the archive-slug. | 
        
        
           | 
           * | 
        
        
           | 
           * Following code allows that. It's a 3-part solution, all 3 parts are needed. | 
        
        
           | 
           * | 
        
        
           | 
           * After implementation: | 
        
        
           | 
           * | 
        
        
           | 
           * 1. save permalinks (will register the strings with WPML) | 
        
        
           | 
           * 2. use WPML string translation to translate the strings, found in text-domain "post-type-archive-slug" | 
        
        
           | 
           * You may need to set the correct source-language for the text-domain using the WPML String Translation tools. | 
        
        
           | 
           * 3. save permalinks (will update rewrite rules for tanslations) | 
        
        
           | 
           * | 
        
        
           | 
           * If you don't want to use WPML String Translation, change the text-domain post-type-archive-slug to that of your theme | 
        
        
           | 
           * or plugin and register the strings by putting a line like this: | 
        
        
           | 
           * $not_used = __('archive-slug', 'text-domain'); | 
        
        
           | 
           * in your theme or plugin and scan the files with your favorite POMO-editor. | 
        
        
           | 
           * | 
        
        
           | 
           * Make sure that all this is done in WP backend when on the default (primary) language, see [this comment](https://gist.github.com/senlin/1adf65ae297c32142e765628fce194ad?permalink_comment_id=5351512#gistcomment-5351512)  | 
        
        
           | 
           * | 
        
        
           | 
           * For more information: | 
        
        
           | 
           * @see https://developer.wordpress.org/apis/handbook/internationalization/localization/#translating-themes-and-plugins | 
        
        
           | 
           * | 
        
        
           | 
           *  | 
        
        
           | 
           * Part 1: filter the link generation. This works on the page. | 
        
        
           | 
           */ | 
        
        
           | 
          
 | 
        
        
           | 
          add_filter( 'post_type_archive_link', function ( $permalink, $post_type ) { | 
        
        
           | 
          	$post_type_object = get_post_type_object( $post_type ); | 
        
        
           | 
          
 | 
        
        
           | 
          	// dont do anything if | 
        
        
           | 
          	// - not a valid post_type | 
        
        
           | 
          	// - rewrite not enabled | 
        
        
           | 
          	// - slug not defined | 
        
        
           | 
          	// - archive-slug not defined | 
        
        
           | 
          	// - archive-slug not different from post-slug | 
        
        
           | 
          	if ( ! $post_type_object || ! is_array( $post_type_object->rewrite ) || ! isset( $post_type_object->rewrite['slug'] ) || ! is_a( $post_type_object, WP_Post_Type::class ) || ! is_string( $post_type_object->has_archive ) || $post_type_object->rewrite['slug'] == $post_type_object->has_archive ) { | 
        
        
           | 
          		return $permalink; | 
        
        
           | 
          	} | 
        
        
           | 
          
 | 
        
        
           | 
          	// use a cached version written after permalink resave, so we don't have to do grunt work every time. | 
        
        
           | 
          	$match = get_option( '__wpml_post_type_archive_slug_match', [] ); | 
        
        
           | 
          	$match = $match && isset( $match[ $post_type ] ) ? $match[ $post_type ] : false; | 
        
        
           | 
          	if ( ! $match ) { | 
        
        
           | 
          		// sorry, need to save permalinks first. | 
        
        
           | 
          		// silently fail. | 
        
        
           | 
          		return $permalink; | 
        
        
           | 
          	} | 
        
        
           | 
          
 | 
        
        
           | 
          	/** | 
        
        
           | 
          	 * @action wpml_register_single_string | 
        
        
           | 
          	 * @var string $context the text-domain | 
        
        
           | 
          	 *                      This should be $text_domain, not $context | 
        
        
           | 
          	 * @var string $name a description of the string. | 
        
        
           | 
          	 *                   because this is the actual Context for the string | 
        
        
           | 
          	 * @var string $value the text to translate | 
        
        
           | 
          	 * @var bool $allow_empty_value , default = false | 
        
        
           | 
          	 * @var string $source_lang_code , default to system-default-language. | 
        
        
           | 
          	 */ | 
        
        
           | 
          	do_action( 'wpml_register_single_string', 'post-type-archive-slug', '', $post_type_object->has_archive ); | 
        
        
           | 
          	$permalink = preg_replace( '@/' . implode('|', $match) . '/@', '/' . __( $post_type_object->has_archive, 'post-type-archive-slug' ) . '/', $permalink ); | 
        
        
           | 
          	/** | 
        
        
           | 
          	 * Why? sometimes url's come with dual slashes after the website domain. | 
        
        
           | 
          	 * replace all // with / except when it is :// | 
        
        
           | 
          	 */ | 
        
        
           | 
          	$permalink = preg_replace('@([^:]/)/@', '\1', $permalink); | 
        
        
           | 
          
 | 
        
        
           | 
          	return $permalink; | 
        
        
           | 
          }, PHP_INT_MAX, 2 ); | 
        
        
           | 
          
 | 
        
        
           | 
          // above works for "current language" but fails for the language switcher. hence the following hook | 
        
        
           | 
          
 | 
        
        
           | 
          /** | 
        
        
           | 
           * Part 2: filter the link generation. This works in the language switch. | 
        
        
           | 
           * Warning: this is done multiple times during a page generation, but only once for the actual language-switcher | 
        
        
           | 
           * This is detected by the code by checking the post_Type being defined. ... ugly, but it works. | 
        
        
           | 
           */ | 
        
        
           | 
          
 | 
        
        
           | 
          add_filter( 'icl_ls_languages', function ( $languages ) { | 
        
        
           | 
          	$patch            = get_option( '__wpml_post_type_archive_slug_match', [] ); | 
        
        
           | 
          	if ( is_admin() || ! $patch || ! is_array( $patch ) ) { | 
        
        
           | 
          		return $languages; | 
        
        
           | 
          	} | 
        
        
           | 
          
 | 
        
        
           | 
          	foreach ( $patch as $post_type => $_patches ) { | 
        
        
           | 
          		$post_type_object = get_post_type_object( $post_type ); | 
        
        
           | 
          		if ($post_type_object && is_a($post_type_object, 'WP_Post_Type')) { | 
        
        
           | 
          			foreach ( $languages as $language_code => &$language ) { | 
        
        
           | 
          				$language['url'] = preg_replace( '@/(' . implode('|', $_patches) . ')/@', '/' . ($_patches[$language_code] ?: $post_type_object->has_archive) . '/', $language['url'] ); | 
        
        
           | 
          				/** | 
        
        
           | 
          				 * Why? sometimes url's come with dual slashes after the website domain. | 
        
        
           | 
          				 * replace all // with / except when it is :// | 
        
        
           | 
          				 */ | 
        
        
           | 
          				$language['url'] = preg_replace('@([^:]/)/@', '\1', $language['url']); | 
        
        
           | 
          			} | 
        
        
           | 
          		} | 
        
        
           | 
          	} | 
        
        
           | 
          
 | 
        
        
           | 
          	return $languages; | 
        
        
           | 
          } ); | 
        
        
           | 
          
 | 
        
        
           | 
          /** | 
        
        
           | 
           * Part 3: make the url's work. | 
        
        
           | 
           * This step also caches the list of post-types and their possible slugs | 
        
        
           | 
           * Effort is mate to only do work when there is actually an archive-slug that is different from the post-type-slug and rewrite is enabled. | 
        
        
           | 
           */ | 
        
        
           | 
          
 | 
        
        
           | 
          add_filter( 'rewrite_rules_array', function ( $rules ) { | 
        
        
           | 
          
 | 
        
        
           | 
          	global $sitepress; | 
        
        
           | 
          
 | 
        
        
           | 
          	$current_language = $sitepress->get_current_language(); | 
        
        
           | 
          
 | 
        
        
           | 
          	$post_types = get_post_types(); | 
        
        
           | 
          	$post_types = array_filter( $post_types, function ( $post_type ) { | 
        
        
           | 
          		$post_type_object = get_post_type_object( $post_type ); | 
        
        
           | 
          
 | 
        
        
           | 
          		return ! ( ! $post_type_object || ! is_array( $post_type_object->rewrite ) || ! isset( $post_type_object->rewrite['slug'] ) || ! is_a( $post_type_object, WP_Post_Type::class ) || ! is_string( $post_type_object->has_archive ) || $post_type_object->rewrite['slug'] == $post_type_object->has_archive ); | 
        
        
           | 
          	} ); | 
        
        
           | 
          	if ( ! $post_types ) { | 
        
        
           | 
          		return $rules; | 
        
        
           | 
          	} | 
        
        
           | 
          
 | 
        
        
           | 
          	$languages = apply_filters( 'wpml_active_languages', array(), 'skip_missing=0' ); | 
        
        
           | 
          
 | 
        
        
           | 
          	$match = array_map( function ( $post_type ) use ( $languages, $sitepress ) { | 
        
        
           | 
          		$post_type_object = get_post_type_object( $post_type ); | 
        
        
           | 
          
 | 
        
        
           | 
          		return $post_type_object->has_archive; | 
        
        
           | 
          	}, $post_types ); | 
        
        
           | 
          
 | 
        
        
           | 
          	$patch = array_map( function ( $post_type ) use ( $languages, $sitepress ) { | 
        
        
           | 
          		$post_type_object = get_post_type_object( $post_type ); | 
        
        
           | 
          		$strings          = []; | 
        
        
           | 
          		foreach ( $languages as $language_code => $language ) { | 
        
        
           | 
          			$sitepress->switch_lang( $language_code ); | 
        
        
           | 
          			$strings[ $language_code ] = __( $post_type_object->has_archive, 'post-type-archive-slug' ); | 
        
        
           | 
          		} | 
        
        
           | 
          
 | 
        
        
           | 
          		return $strings; | 
        
        
           | 
          	}, $post_types ); | 
        
        
           | 
          
 | 
        
        
           | 
          	$sitepress->switch_lang( $current_language ); | 
        
        
           | 
          
 | 
        
        
           | 
          	update_option( '__wpml_post_type_archive_slug_match', $patch ); | 
        
        
           | 
          
 | 
        
        
           | 
          	$patch = array_map(function($strings){ | 
        
        
           | 
          		return implode('|', $strings); | 
        
        
           | 
          	}, $patch); | 
        
        
           | 
          	$match = '@^(' . implode( '|', $match ) . ')/@'; | 
        
        
           | 
          	$patch = '(?:' . implode( '|', $patch ) . ')'; | 
        
        
           | 
          
 | 
        
        
           | 
          
 | 
        
        
           | 
          	$keys   = array_keys( $rules ); | 
        
        
           | 
          	$values = array_values( $rules ); | 
        
        
           | 
          
 | 
        
        
           | 
          	foreach ( $keys as &$key ) { | 
        
        
           | 
          		$key = preg_replace( $match, $patch, $key ); | 
        
        
           | 
          	} | 
        
        
           | 
          
 | 
        
        
           | 
          	$rules = array_combine( $keys, $values ); | 
        
        
           | 
          
 | 
        
        
           | 
          	return $rules; | 
        
        
           | 
          } ); | 
        
  
I noticed that the second language archive URL is working on the primary language too.
Now in primary language I have 2 URLs with the same archive. that's weird...