diff --git a/.changelogs/fix_i18n-dashboard-language-and-slugs-1.yml b/.changelogs/fix_i18n-dashboard-language-and-slugs-1.yml new file mode 100644 index 0000000000..b1bbee63f7 --- /dev/null +++ b/.changelogs/fix_i18n-dashboard-language-and-slugs-1.yml @@ -0,0 +1,7 @@ +significance: minor +type: added +links: + - "#2429" + - "#2525" +entry: Loads translation files later for compatibility with plugins like Loco + Translate. diff --git a/.changelogs/fix_i18n-dashboard-language-and-slugs-2.yml b/.changelogs/fix_i18n-dashboard-language-and-slugs-2.yml new file mode 100644 index 0000000000..a783b76c60 --- /dev/null +++ b/.changelogs/fix_i18n-dashboard-language-and-slugs-2.yml @@ -0,0 +1,5 @@ +significance: patch +type: fixed +links: + - "#2525" +entry: LifterLMS block editor strings now appear in the user's language. diff --git a/.changelogs/fix_i18n-dashboard-language-and-slugs-3.yml b/.changelogs/fix_i18n-dashboard-language-and-slugs-3.yml new file mode 100644 index 0000000000..fa2f86673c --- /dev/null +++ b/.changelogs/fix_i18n-dashboard-language-and-slugs-3.yml @@ -0,0 +1,4 @@ +significance: minor +type: added +entry: Adds settings in the Permalinks page to edit the custom post type and + taxonomy slugs. Slugs are saved in the site language on install on update. diff --git a/.changelogs/fix_i18n-dashboard-language-and-slugs-4.yml b/.changelogs/fix_i18n-dashboard-language-and-slugs-4.yml new file mode 100644 index 0000000000..8ba53bd7d1 --- /dev/null +++ b/.changelogs/fix_i18n-dashboard-language-and-slugs-4.yml @@ -0,0 +1,5 @@ +significance: patch +type: added +entry: Adds `llms_switch_to_site_locale` and `llms_restore_locale` to help + LifterLMS add-ons switch to the site language when getting translation + strings. diff --git a/.changelogs/fix_i18n-dashboard-language-and-slugs.yml b/.changelogs/fix_i18n-dashboard-language-and-slugs.yml new file mode 100644 index 0000000000..b0a7147b3a --- /dev/null +++ b/.changelogs/fix_i18n-dashboard-language-and-slugs.yml @@ -0,0 +1,5 @@ +significance: patch +type: fixed +links: + - "#2324" +entry: Fixed user's language setting not honored on backend. diff --git a/class-lifterlms.php b/class-lifterlms.php index 4cc9bf0b38..87b6229889 100644 --- a/class-lifterlms.php +++ b/class-lifterlms.php @@ -68,20 +68,12 @@ final class LifterLMS { * @since 5.3.0 Move the loading of the LifterLMS autoloader to the main `lifterlms.php` file. * @since 6.1.0 Automatically load payment gateways. * @since 6.4.0 Moved registration of `LLMS_Shortcodes::init()` with the 'init' hook to `LLMS_Shortcodes::__construct()`. + * @since [version] Lood locale textdomain on `init` instead of immediately * * @return void */ private function __construct() { - /** - * Localize as early as possible. - * - * Since 4.6 the "just_in_time" l10n will load the default (not custom) file first - * so we must localize before any l10n functions (like `__()`) are used - * so that our custom "safe" location will always load first. - */ - $this->localize(); - $this->define_constants(); $this->init_assets(); @@ -89,9 +81,9 @@ private function __construct() { $this->query = new LLMS_Query(); // Hooks. - register_activation_hook( __FILE__, array( 'LLMS_Install', 'install' ) ); add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'add_action_links' ), 10, 1 ); + add_action( 'init', array( $this, 'localize' ), 0 ); add_action( 'init', array( $this, 'init' ), 0 ); add_action( 'init', array( $this, 'integrations' ), 1 ); add_action( 'init', array( $this, 'processors' ), 5 ); @@ -415,9 +407,10 @@ public function add_action_links( $links ) { */ public function localize() { - require_once LLMS_PLUGIN_DIR . 'includes/functions/llms-functions-l10n.php'; llms_load_textdomain( 'lifterlms' ); } } + + diff --git a/includes/admin/class-llms-admin-permalinks.php b/includes/admin/class-llms-admin-permalinks.php new file mode 100644 index 0000000000..1b9db173f9 --- /dev/null +++ b/includes/admin/class-llms-admin-permalinks.php @@ -0,0 +1,269 @@ +id ) { + $this->settings_init(); + $this->settings_save(); + } + } + + /** + * Show the available permalink settings + */ + public function settings_init() { + add_settings_section( 'lifterlms-permalink', __( 'LifterLMS Permalinks', 'lifterlms' ), array( $this, 'settings' ), 'permalink' ); + + $this->permalinks = llms_get_permalink_structure(); + } + + public function settings() { + ?> +

+ + +

+ + + + +

+ +

+ + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false, 'hierarchical' => false, 'rewrite' => array( - 'slug' => _x( 'course', 'course url slug', 'lifterlms' ), + 'slug' => $permalinks['course_base'], 'with_front' => false, 'feeds' => true, ), 'query_var' => true, 'supports' => array( 'title', 'author', 'editor', 'thumbnail', 'comments', 'custom-fields', 'page-attributes', 'revisions', 'llms-clone-post', 'llms-export-post', 'llms-sales-page' ), - 'has_archive' => ( $catalog_id && get_post( $catalog_id ) ) ? get_page_uri( $catalog_id ) : _x( 'courses', 'course archive url slug', 'lifterlms' ), + 'has_archive' => ( $catalog_id && get_post( $catalog_id ) ) ? get_page_uri( $catalog_id ) : $permalinks['courses_base'], 'show_in_nav_menus' => true, 'menu_position' => 52, ) @@ -552,7 +553,7 @@ public static function register_post_types() { 'show_in_menu' => 'edit.php?post_type=course', 'hierarchical' => false, 'rewrite' => array( - 'slug' => _x( 'lesson', 'lesson url slug', 'lifterlms' ), + 'slug' => $permalinks['lesson_base'], 'with_front' => false, 'feeds' => true, ), @@ -591,7 +592,7 @@ public static function register_post_types() { 'show_in_menu' => 'edit.php?post_type=course', 'hierarchical' => false, 'rewrite' => array( - 'slug' => _x( 'quiz', 'quiz url slug', 'lifterlms' ), + 'slug' => $permalinks['quiz_base'], 'with_front' => false, 'feeds' => true, ), @@ -673,7 +674,7 @@ public static function register_post_types() { ), 'query_var' => true, 'supports' => array( 'title', 'editor', 'thumbnail', 'comments', 'custom-fields', 'page-attributes', 'revisions', 'llms-sales-page' ), - 'has_archive' => ( $membership_page_id && get_post( $membership_page_id ) ) ? get_page_uri( $membership_page_id ) : _x( 'memberships', 'membership archive url slug', 'lifterlms' ), + 'has_archive' => ( $membership_page_id && get_post( $membership_page_id ) ) ? get_page_uri( $membership_page_id ) : $permalinks['memberships_base'], 'show_in_nav_menus' => true, 'menu_position' => 52, ) @@ -902,7 +903,7 @@ public static function register_post_types() { array( 'map_meta_cap' => true, ), - _x( 'certificate-template', 'slug', 'lifterlms' ), + $permalinks['certificate_template_base'], /** * Filters the WordPress user capability required for a user to manage certificate templates on the admin panel. * @@ -935,7 +936,7 @@ public static function register_post_types() { 'capabilities' => self::get_post_type_caps( 'my_certificate' ), 'map_meta_cap' => false, ), - _x( 'certificate', 'slug', 'lifterlms' ), + $permalinks['certificate_base'], /** * Filters the needed capability to generate and allow a UI for managing `llms_my_certificate` post type in the admin. * @@ -1275,6 +1276,8 @@ public static function register_taxonomy( $name, $object, $data ) { */ public static function register_taxonomies() { + $permalinks = llms_get_permalink_structure(); + // Course cat. self::register_taxonomy( 'course_cat', @@ -1300,7 +1303,7 @@ public static function register_taxonomies() { 'show_admin_column' => true, 'show_ui' => true, 'rewrite' => array( - 'slug' => _x( 'course-category', 'slug', 'lifterlms' ), + 'slug' => $permalinks['course_category_base'], 'with_front' => false, 'hierarchical' => true, ), @@ -1333,7 +1336,7 @@ public static function register_taxonomies() { 'show_admin_column' => true, 'show_ui' => true, 'rewrite' => array( - 'slug' => _x( 'course-difficulty', 'slug', 'lifterlms' ), + 'slug' => $permalinks['course_difficulty_base'], 'with_front' => false, ), 'show_in_llms_rest' => true, @@ -1365,7 +1368,7 @@ public static function register_taxonomies() { 'show_admin_column' => true, 'show_ui' => true, 'rewrite' => array( - 'slug' => _x( 'course-tag', 'slug', 'lifterlms' ), + 'slug' => $permalinks['course_tag_base'], 'with_front' => false, ), 'show_in_llms_rest' => true, @@ -1397,7 +1400,7 @@ public static function register_taxonomies() { 'show_admin_column' => true, 'show_ui' => true, 'rewrite' => array( - 'slug' => _x( 'course-track', 'slug', 'lifterlms' ), + 'slug' => $permalinks['course_track_base'], 'with_front' => false, 'hierarchical' => true, ), @@ -1431,7 +1434,7 @@ public static function register_taxonomies() { 'query_var' => true, 'show_admin_column' => true, 'rewrite' => array( - 'slug' => _x( 'membership-category', 'slug', 'lifterlms' ), + 'slug' => $permalinks['membership_category_base'], 'with_front' => false, 'hierarchical' => true, ), @@ -1465,7 +1468,7 @@ public static function register_taxonomies() { 'query_var' => true, 'show_admin_column' => true, 'rewrite' => array( - 'slug' => _x( 'membership-tag', 'slug', 'lifterlms' ), + 'slug' => $permalinks['membership_tag_base'], 'with_front' => false, ), 'show_in_llms_rest' => true, diff --git a/includes/functions/llms-functions-l10n.php b/includes/functions/llms-functions-l10n.php index 5ec7a2cb41..382e615baa 100644 --- a/includes/functions/llms-functions-l10n.php +++ b/includes/functions/llms-functions-l10n.php @@ -20,12 +20,7 @@ */ function llms_get_locale( $domain = 'lifterlms' ) { - if ( function_exists( 'determine_locale' ) ) { - $locale = determine_locale(); - } else { - // @TODO: This can be removed when minimum supported version is 5.0. - $locale = is_admin() ? get_user_locale() : get_locale(); - } + $locale = determine_locale(); /** * Filter the plugin's locale @@ -96,6 +91,8 @@ function llms_load_textdomain( $domain, $plugin_dir = null, $language_dir = null $plugin_dir = $plugin_dir ? $plugin_dir : LLMS_PLUGIN_DIR; $language_dir = $language_dir ? $language_dir : 'languages'; + unload_textdomain( $domain ); + /** * Load from the custom LifterLMS "safe" directory (if it exists). * @@ -112,3 +109,109 @@ function llms_load_textdomain( $domain, $plugin_dir = null, $language_dir = null load_plugin_textdomain( $domain, false, sprintf( '%1$s/%2$s', basename( $plugin_dir ), $language_dir ) ); } + +/** + * Retrieve the current permalink structure. If no structure is set, the default structure is returned. + * + * Note: this should be called on install or update of LifterLMS at a time when the site language is known and set. + * + * @since [version] + * + * @return array + */ +function llms_get_permalink_structure() { + $saved_permalinks = (array) get_option( 'llms_permalinks', array() ); + + $permalinks = wp_parse_args( + // Remove false or empty entries so we can use the default values. + array_filter( $saved_permalinks ), + array( + 'course_base' => _x( 'course', 'course url slug', 'lifterlms' ), + 'courses_base' => _x( 'courses', 'course archive url slug', 'lifterlms' ), + 'memberships_base' => _x( 'memberships', 'membership archive url slug', 'lifterlms' ), + 'lesson_base' => _x( 'lesson', 'lesson url slug', 'lifterlms' ), + 'quiz_base' => _x( 'quiz', 'quiz url slug', 'lifterlms' ), + 'certificate_template_base' => _x( 'certificate-template', 'slug', 'lifterlms' ), + 'certificate_base' => _x( 'certificate', 'slug', 'lifterlms' ), + 'course_category_base' => _x( 'course-category', 'slug', 'lifterlms' ), + 'course_tag_base' => _x( 'course-tag', 'slug', 'lifterlms' ), + 'course_track_base' => _x( 'course-track', 'slug', 'lifterlms' ), + 'course_difficulty_base' => _x( 'course-difficulty', 'slug', 'lifterlms' ), + 'membership_category_base' => _x( 'membership-category', 'slug', 'lifterlms' ), + 'membership_tag_base' => _x( 'membership-tag', 'slug', 'lifterlms' ), + ) + ); + + array_filter( $permalinks, 'untrailingslashit' ); + + if ( $saved_permalinks !== $permalinks ) { + update_option( 'llms_permalinks', $permalinks ); + } + + return $permalinks; +}; +/** + * Set the permalink structure and only allow keys we know about. + * + * @since [version] + * + * @param array $permalinks + * + * @return void + */ +function llms_set_permalink_structure( $permalinks ) { + $defaults = llms_get_permalink_structure(); + + $permalinks = wp_parse_args( + // Only allow values whose keys are in the defaults array. + array_intersect_key( $permalinks, $defaults ), + $defaults + ); + + array_filter( $permalinks, 'untrailingslashit' ); + + update_option( 'llms_permalinks', $permalinks ); +} + +/** + * Switch LifterLMS language to site language. + * + * @param string $textdomain Text domain. Defaults to lifterlms. + * @param string $plugin_dir Plugin directory. Defaults to null. + * @param string $language_dir Language directory. Defaults to null. + * + * @since [version] + */ +function llms_switch_to_site_locale( $textdomain = 'lifterlms', $plugin_dir = null, $language_dir = null ) { + global $wp_locale_switcher; + + if ( function_exists( 'switch_to_locale' ) && isset( $wp_locale_switcher ) ) { + switch_to_locale( get_locale() ); + + // Filter on plugin_locale so load_plugin_textdomain loads the correct locale. + add_filter( 'plugin_locale', 'get_locale' ); + + llms_load_textdomain( $textdomain, $plugin_dir, $language_dir ); + } +} + +/** + * Switch LifterLMS language to original. + * + * @param string $textdomain Text domain. Defaults to lifterlms. + * @param string $plugin_dir Plugin directory. Defaults to null. + * @param string $language_dir Language directory. Defaults to null. + * + * @since [version] + */ +function llms_restore_locale( $textdomain = 'lifterlms', $plugin_dir = null, $language_dir = null ) { + global $wp_locale_switcher; + + if ( function_exists( 'restore_previous_locale' ) && isset( $wp_locale_switcher ) ) { + restore_previous_locale(); + + remove_filter( 'plugin_locale', 'get_locale' ); + + llms_load_textdomain( $textdomain, $plugin_dir, $language_dir ); + } +} diff --git a/includes/llms.functions.core.php b/includes/llms.functions.core.php index 5343de2f32..78561fa7f4 100644 --- a/includes/llms.functions.core.php +++ b/includes/llms.functions.core.php @@ -10,6 +10,8 @@ defined( 'ABSPATH' ) || exit; +require_once 'functions/llms-functions-l10n.php'; + require_once 'functions/llms-functions-access-plans.php'; require_once 'functions/llms-functions-deprecated.php'; require_once 'functions/llms-functions-forms.php'; diff --git a/lifterlms.php b/lifterlms.php index 4840a763fd..139f6f3638 100644 --- a/lifterlms.php +++ b/lifterlms.php @@ -52,6 +52,8 @@ require_once LLMS_PLUGIN_DIR . 'class-lifterlms.php'; } +register_activation_hook( __FILE__, array( 'LLMS_Install', 'install' ) ); + /** * Returns the main instance of LifterLMS * diff --git a/packages/llms-e2e-test-utils/src/visit-page.js b/packages/llms-e2e-test-utils/src/visit-page.js index 7cd6b53c87..da90927fb9 100644 --- a/packages/llms-e2e-test-utils/src/visit-page.js +++ b/packages/llms-e2e-test-utils/src/visit-page.js @@ -9,5 +9,5 @@ const { createURL } = require( '@wordpress/e2e-test-utils' ); * @return {void} */ export async function visitPage( path, query ) { - await page.goto( createURL( path, query ) ); + return await page.goto( createURL( path, query ) ); } diff --git a/tests/phpunit/unit-tests/class-llms-test-assets.php b/tests/phpunit/unit-tests/class-llms-test-assets.php index 10417bdb15..1b78ea0f5b 100644 --- a/tests/phpunit/unit-tests/class-llms-test-assets.php +++ b/tests/phpunit/unit-tests/class-llms-test-assets.php @@ -723,64 +723,6 @@ public function test_register_script_undefined() { } - /** - * Test register_script() with translations - * - * @since 5.5.0 - * - * @return void - */ - public function test_register_script_with_translations() { - - // LLMS_PLUGIN_URL gets messed up in the testing environment. - $handler = function( $defaults ) { - $defaults['base_url'] = plugins_url() . '/lifterlms'; - return $defaults; - }; - add_filter( 'llms_get_script_asset_defaults', $handler ); - - - $handle = 'llms-test-messages'; - $file = 'assets/js/llms-test-messages.js'; - $md5 = md5( $file ); - $json = file_get_contents( LLMS_Unit_Test_Files::get_asset_path( sprintf( 'lifterlms-en_US-%s.json', $md5 ) ) ); - - $scripts = array( $handle => array( 'translate' => true ) ); - $this->main->define( 'scripts', $scripts ); - - $dirs = array( - WP_LANG_DIR . '/lifterlms', // "Safe" directory. - WP_LANG_DIR . '/plugins', // Default language directory. - plugin_dir_path( LLMS_PLUGIN_FILE ) . 'languages', // Plugin language directory. - ); - - foreach ( $dirs as $dir ) { - - // Load a language file. - $file = LLMS_Unit_Test_Files::copy_asset( sprintf( 'lifterlms-en_US-%s.json', $md5 ), $dir ); - $this->main->register_script( $handle ); - - // The script's translation path should be the intended directory. - $this->assertEquals( $dir, wp_scripts()->registered[ $handle ]->translations_path, $dir ); - - // If we load the script's textdomain we'll see JSON matching the mock file. - $this->assertEquals( $json, load_script_textdomain( $handle, 'lifterlms', $dir ), $dir ); - - // Clean up. - LLMS_Unit_Test_Files::remove( $file ); - wp_deregister_script( $handle ); - - } - - // No files found. - $this->main->register_script( $handle ); - $this->assertNull( wp_scripts()->registered[ $handle ]->translations_path ); - $this->assertFalse( load_script_textdomain( $handle, 'lifterlms' ) ); - - remove_filter( 'llms_get_script_asset_defaults', $handler ); - - } - /** * Test register_style() for a custom asset (added via a filter) *