Security best practices when building multilingual sites with Lang Forge.
Input Sanitization for Language Parameters
php
/**
* Always validate language codes before using them.
* Lang Forge's internal functions do this automatically,
* but if you handle language codes manually, validate them.
*/
function theme_validate_language($code) {
// Language codes should be 2-5 characters, lowercase, letters and hyphens only
if (!preg_match('/^[a-z]{2,5}$/', $code)) {
return false;
}
// Check against active languages
$languages = lf_get_languages();
return isset($languages[$code]);
}
// Usage in a custom endpoint
add_action('rest_api_init', function () {
register_rest_route('mytheme/v1', '/switch-lang', [
'methods' => 'POST',
'callback' => function (WP_REST_Request $request) {
$lang = sanitize_text_field($request->get_param('lang'));
if (!theme_validate_language($lang)) {
return new WP_Error('invalid_lang', 'Invalid language code', ['status' => 400]);
}
// Proceed with language switch
$core = LF_Core::instance();
$url = $core->get_language_url($lang);
return rest_ensure_response(['redirect' => $url]);
},
'permission_callback' => '__return_true',
]);
});Escaping Translated Content
php
// ALWAYS escape translated strings before output.
// Translations are entered by humans and could contain malicious content.
// Correct - escaped
echo esc_html__('Welcome', 'theme');
// Correct - escaped attribute
echo '<input placeholder="' . esc_attr__('Search...', 'theme') . '">';
// Correct - escaped URL
echo '<a href="' . esc_url($core->get_language_url('en')) . '">English</a>';
// WRONG - unescaped (vulnerable to XSS)
// echo __('Welcome', 'theme');REST API Permission Checks
php
// Lang Forge's REST endpoints check for edit_posts or manage_options.
// If building custom endpoints that expose translation data publicly,
// be careful not to leak draft/private post information.
// Write endpoints should also validate the target language against the
// current effective plan, not just the raw lf_active_languages option.
register_rest_route('mytheme/v1', '/translations/(?P<id>d+)', [
'methods' => 'GET',
'callback' => function ($request) {
$post_id = intval($request->get_param('id'));
$post = get_post($post_id);
// Only return data for published posts to unauthenticated users
if (!$post || $post->post_status !== 'publish') {
if (!current_user_can('edit_posts')) {
return new WP_Error('not_found', 'Post not found', ['status' => 404]);
}
}
$translations = lf_get_post_translations($post_id);
// Filter out non-published translations for public requests
if (!current_user_can('edit_posts')) {
foreach ($translations as $lang => $trans_id) {
if (get_post_status($trans_id) !== 'publish') {
unset($translations[$lang]);
}
}
}
return rest_ensure_response($translations);
},
'permission_callback' => '__return_true',
]);Nonce Verification for Language Forms
php
// When building custom forms that include language selection,
// always use nonces to prevent CSRF attacks.
// Form output
echo '<form method="post">';
wp_nonce_field('lf_language_action', 'lf_language_nonce');
echo '<select name="target_language">';
foreach (lf_get_languages() as $code => $lang) {
echo '<option value="' . esc_attr($code) . '">' . esc_html($lang['name']) . '</option>';
}
echo '</select>';
echo '<button type="submit">Create Translation</button>';
echo '</form>';
// Form processing
if (isset($_POST['lf_language_nonce']) && wp_verify_nonce($_POST['lf_language_nonce'], 'lf_language_action')) {
$target_lang = sanitize_text_field($_POST['target_language']);
// Process the form...
}—