This is a prototype for now I am just exploring idea the most efficient and future proof way to create translation system for wordpress.
function snn_get_languages() { $languages = get_option( 'snn_languages', array() ); return $languages; } function snn_debug_mode() { return get_option( 'snn_debug_mode', false ); } function snn_register_language_post_types() { $languages = snn_get_languages(); if ( empty( $languages ) ) { return; } foreach ( $languages as $lang ) { $lang = sanitize_key( $lang ); if ( empty( $lang ) || post_type_exists( $lang ) ) { continue; } $labels = array( 'name' => sprintf( '%s', strtoupper( $lang ) ), 'singular_name' => sprintf( 'Translation (%s)', strtoupper( $lang ) ), 'add_new' => 'Add New', 'add_new_item' => sprintf( 'Add New Translation (%s)', strtoupper( $lang ) ), 'edit_item' => sprintf( 'Edit Translation (%s)', strtoupper( $lang ) ), 'new_item' => sprintf( 'New Translation (%s)', strtoupper( $lang ) ), 'view_item' => sprintf( 'View Translation (%s)', strtoupper( $lang ) ), 'search_items' => sprintf( 'Search Translations (%s)', strtoupper( $lang ) ), 'not_found' => 'No translations found', 'not_found_in_trash' => 'No translations found in Trash', ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'has_archive' => false, 'rewrite' => array( 'slug' => $lang, 'with_front' => false, ), 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions' ), 'taxonomies' => array( 'category', 'post_tag' ), ); register_post_type( $lang, $args ); } } add_action( 'init', 'snn_register_language_post_types', 15 ); function snn_hide_language_post_type_menu_items() { if ( snn_debug_mode() ) { return; } $languages = snn_get_languages(); if ( empty( $languages ) ) { return; } echo '<style>'; foreach ( $languages as $lang ) { $lang = sanitize_key( $lang ); echo '#menu-posts-' . esc_attr( $lang ) . ' { display: none !important; }'; } echo '</style>'; } add_action( 'admin_head', 'snn_hide_language_post_type_menu_items' ); function snn_add_admin_menu() { add_menu_page( 'SNN Translation', 'SNN Translation', 'manage_options', 'snn-translation', 'snn_translation_admin_page', 'dashicons-translation', 80 ); } add_action( 'admin_menu', 'snn_add_admin_menu' ); function snn_translation_admin_page() { if ( isset( $_POST['snn_save'] ) && check_admin_referer( 'snn_save_options', 'snn_nonce' ) ) { $langs = isset( $_POST['snn_languages'] ) ? sanitize_text_field( $_POST['snn_languages'] ) : ''; $lang_array = array_filter( array_map( 'trim', explode( ',', $langs ) ) ); update_option( 'snn_languages', $lang_array ); $debug = isset( $_POST['snn_debug_mode'] ) ? true : false; update_option( 'snn_debug_mode', $debug ); echo '<div class="updated"><p>Settings saved.</p></div>'; } $languages = snn_get_languages(); $languages_str = implode( ', ', $languages ); $debug = snn_debug_mode(); ?> <div class="wrap"> <h1>SNN Translation Settings</h1> <form method="post"> <?php wp_nonce_field( 'snn_save_options', 'snn_nonce' ); ?> <table class="form-table"> <tr> <th scope="row"><label for="snn_languages">Languages</label></th> <td> <input type="text" name="snn_languages" id="snn_languages" value="<?php echo esc_attr( $languages_str ); ?>" class="regular-text" /> <p class="description">Enter language codes separated by commas. E.g., en, fr, de</p> </td> </tr> <tr> <th scope="row">Debug Mode</th> <td> <label for="snn_debug_mode"> <input type="checkbox" name="snn_debug_mode" id="snn_debug_mode" <?php checked( $debug ); ?> /> Enable debug mode (translations remain visible in admin menus) </label> </td> </tr> </table> <?php submit_button( 'Save Settings', 'primary', 'snn_save' ); ?> </form> </div> <?php } function snn_add_translation_flags_column( $columns ) { $columns['snn_translation'] = 'Translations'; return $columns; } add_filter( 'manage_posts_columns', 'snn_add_translation_flags_column' ); add_filter( 'manage_pages_columns', 'snn_add_translation_flags_column' ); function snn_render_translation_flags_column( $column, $post_id ) { if ( 'snn_translation' !== $column ) { return; } if ( get_post_meta( $post_id, '_snn_original_post', true ) ) { return; } $languages = snn_get_languages(); if ( empty( $languages ) ) { return; } $output = ''; foreach ( $languages as $lang ) { $translation = snn_get_translation_post( $post_id, $lang ); if ( $translation ) { $edit_link = get_edit_post_link( $translation->ID ); $output .= '<a style="margin-right:6px;" href="' . esc_url( $edit_link ) . '" title="Edit ' . esc_attr( $lang ) . ' translation">' . strtoupper( esc_html( $lang ) ) . '</a>'; } else { $url = add_query_arg( array( 'snn_translate' => 1, 'lang' => $lang, 'post_id' => $post_id, ), admin_url( 'admin-post.php' ) ); $output .= '<a style="margin-right:6px;" href="' . esc_url( $url ) . '" title="Translate to ' . esc_attr( $lang ) . '">' . strtoupper( esc_html( $lang ) ) . '</a>'; } } echo $output; } add_action( 'manage_posts_custom_column', 'snn_render_translation_flags_column', 10, 2 ); add_action( 'manage_pages_custom_column', 'snn_render_translation_flags_column', 10, 2 ); function snn_get_translation_post( $post_id, $lang ) { $args = array( 'post_type' => $lang, 'meta_query' => array( array( 'key' => '_snn_original_post', 'value' => $post_id, 'compare' => '=' ), array( 'key' => '_snn_translation_lang', 'value' => $lang, 'compare' => '=' ) ), 'post_status' => 'any', 'numberposts' => 1, ); $posts = get_posts( $args ); return ! empty( $posts ) ? $posts[0] : null; } function snn_clone_post( $post_id, $lang ) { $original_post = get_post( $post_id ); if ( ! $original_post ) { return new WP_Error( 'no_post', 'Original post not found.' ); } $new_post = array( 'post_title' => $original_post->post_title, 'post_content' => $original_post->post_content, 'post_excerpt' => $original_post->post_excerpt, 'post_status' => 'draft', 'post_type' => $lang, 'post_author' => get_current_user_id(), 'menu_order' => $original_post->menu_order, 'post_parent' => $original_post->post_parent, 'comment_status' => $original_post->comment_status, 'ping_status' => $original_post->ping_status, ); $new_post_id = wp_insert_post( $new_post ); if ( is_wp_error( $new_post_id ) ) { return $new_post_id; } update_post_meta( $new_post_id, '_snn_original_post', $post_id ); update_post_meta( $new_post_id, '_snn_translation_lang', $lang ); update_post_meta( $new_post_id, '_snn_original_post_type', $original_post->post_type ); $meta = get_post_meta( $post_id ); foreach ( $meta as $meta_key => $meta_values ) { if ( in_array( $meta_key, array( '_edit_lock', '_edit_last', '_snn_original_post', '_snn_translation_lang', '_snn_original_post_type' ), true ) ) { continue; } foreach ( $meta_values as $meta_value ) { add_post_meta( $new_post_id, $meta_key, maybe_unserialize( $meta_value ) ); } } $taxonomies = get_object_taxonomies( $original_post->post_type ); foreach ( $taxonomies as $taxonomy ) { $terms = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'slugs' ) ); if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) { wp_set_object_terms( $new_post_id, $terms, $taxonomy, false ); } } $thumbnail_id = get_post_thumbnail_id( $post_id ); if ( $thumbnail_id ) { set_post_thumbnail( $new_post_id, $thumbnail_id ); } return $new_post_id; } function snn_handle_translation_action() { if ( isset( $_GET['snn_translate'] ) && $_GET['snn_translate'] == 1 && isset( $_GET['lang'] ) && isset( $_GET['post_id'] ) ) { $lang = sanitize_text_field( $_GET['lang'] ); $post_id = absint( $_GET['post_id'] ); if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_die( 'Permission denied.' ); } $translation = snn_get_translation_post( $post_id, $lang ); if ( ! $translation ) { $result = snn_clone_post( $post_id, $lang ); if ( is_wp_error( $result ) ) { wp_die( 'Error cloning post: ' . $result->get_error_message() ); } $translation = get_post( $result ); } wp_redirect( get_edit_post_link( $translation->ID, '' ) ); exit; } } add_action( 'admin_init', 'snn_handle_translation_action' ); function snn_disable_canonical_for_translations( $redirect_url, $requested_url ) { if ( is_singular() ) { $post = get_queried_object(); $languages = array_map( 'sanitize_key', snn_get_languages() ); if ( $post && in_array( $post->post_type, $languages, true ) ) { return false; } } return $redirect_url; } add_filter( 'redirect_canonical', 'snn_disable_canonical_for_translations', 10, 2 );