self::mode(), 'language' => self::current(),
'automatic' => self::resolve(get_user_locale()), 'siteLanguage' => self::resolve(self::site_locale()),
'hasPreference' => self::valid_mode($stored),
];
}
/** Per user and per site; no change to WordPress, another user, or page content. */
public static function save($mode, bool $migrate = false) {
if (!get_current_user_id() || !PostTypes::can_edit()) {
return new \WP_Error('digibl_language_forbidden', self::text('Access denied.'), ['status' => 403]);
}
if (!self::valid_mode($mode)) {
return new \WP_Error('digibl_language_invalid', self::text('Choose a supported interface language.'), ['status' => 400]);
}
$lockKey = 'digibl_language_lock_' . get_current_user_id();
$lock = \Digiter\Diagnostics\ProtocolV1::lock($lockKey, 30);
if (!$lock) {
return new \WP_Error('digibl_language_busy', self::text('A language change is already being saved.'), ['status' => 409]);
}
try {
// Refresh after taking the lock: another browser may have saved a choice.
wp_cache_delete(get_current_user_id(), 'user_meta');
if ($migrate && self::state()['hasPreference']) { return self::state(); }
if (!update_user_option(get_current_user_id(), self::OPTION, $mode, false)
&& get_user_option(self::OPTION) !== $mode) {
return new \WP_Error('digibl_language_storage', self::text('Language could not be saved. Please try again.'), ['status' => 500]);
}
return self::state();
} finally {
\Digiter\Diagnostics\ProtocolV1::unlock($lockKey, $lock);
}
}
public static function routes(): void {
register_rest_route('digiter-builder/v1', '/interface-language', [
'methods' => 'POST',
'permission_callback' => static fn() => get_current_user_id() && PostTypes::can_edit(),
'callback' => static function ($request) {
$result = self::save($request->get_param('mode'), $request->get_param('migrate') === true);
if (is_wp_error($result)) { return $result; }
$response = new \WP_REST_Response($result);
$response->header('Cache-Control', 'no-store, private');
return $response;
},
]);
}
public static function menu(): void {
add_submenu_page('digiter-builder', self::text('Interface language'), self::text('Interface language'),
PostTypes::menu_capability(), 'digibl-interface-language', [self::class, 'screen']);
}
public static function screen(): void {
if (!PostTypes::can_edit()) { wp_die(esc_html(self::text('Access denied.'))); }
echo '
' . esc_html(self::text('Interface language')) . '
';
if (isset($_GET['language_saved'])) { echo '
' . esc_html(self::text('Interface language saved.')) . '
'; }
echo '
' . esc_html(self::text('This choice is saved to your account on this site and applies on every device.')) . '
';
echo '
';
}
public static function save_form(): void {
if (!PostTypes::can_edit()) { wp_die(esc_html(self::text('Access denied.')), '', ['response' => 403]); }
check_admin_referer('digibl_interface_language');
$result = self::save(isset($_POST['mode']) && is_string($_POST['mode']) ? wp_unslash($_POST['mode']) : null);
if (is_wp_error($result)) { wp_die(esc_html($result->get_error_message()), '', ['response' => (int) $result->get_error_data()['status']]); }
wp_safe_redirect(add_query_arg('language_saved', '1', admin_url('admin.php?page=digibl-interface-language')));
exit;
}
public static function catalog(string $language): array {
$language = self::resolve($language);
if (!isset(self::$catalogs[$language])) {
$path = DIGIBL_PATH . 'languages/' . $language . '.json';
$value = is_readable($path) ? json_decode(file_get_contents($path), true) : [];
self::$catalogs[$language] = is_array($value) ? $value : [];
}
return self::$catalogs[$language];
}
public static function text(string $message): string {
$language = self::current();
$own = self::catalog($language);
if (isset($own[$message]) && is_string($own[$message]) && $own[$message] !== '') { return $own[$message]; }
return self::catalog('en')[$message] ?? $message;
}
public static function gettext(string $translation, string $message, string $domain): string {
return self::text($message);
}
/** Only Builder error messages, never successful data or authored documents. */
public static function translate_error($response, $server, $request) {
if (!str_starts_with($request->get_route(), '/digiter-builder/v1/') || $response->get_status() < 400) { return $response; }
$data = $response->get_data();
if (is_array($data) && isset($data['message']) && is_string($data['message'])) {
$data['message'] = self::text($data['message']);
$response->set_data($data);
}
return $response;
}
public static function wordpress_locale(): string {
$language = self::current();
if ($language === 'en') { return 'en_US'; }
foreach (array_unique(array_merge([get_user_locale(), self::site_locale()], get_available_languages())) as $locale) {
if (self::resolve($locale) === $language) { return $locale; }
}
return 'en_US';
}
public static function enqueue(): void {
wp_enqueue_script('digibl-i18n', DIGIBL_URL . 'assets/i18n.js', [], DIGIBL_VERSION, true);
wp_enqueue_script('digibl-language', DIGIBL_URL . 'assets/language.js', ['digibl-i18n'], DIGIBL_VERSION, true);
$state = self::state();
wp_add_inline_script('digibl-language', 'window.DIGIBL_LANGUAGE_CONFIG=' . wp_json_encode($state + [
'policy' => self::policy(), 'catalog' => self::catalog($state['language']),
'baseUrl' => DIGIBL_URL . 'languages/', 'version' => DIGIBL_VERSION,
'endpoint' => rest_url('digiter-builder/v1/interface-language'), 'nonce' => wp_create_nonce('wp_rest'),
'storageKey' => 'dbs-language-v2-' . get_current_blog_id() . '-' . get_current_user_id(),
], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ';', 'before');
}
}