feat(i18n): 实现管理界面国际化支持并优化形状选择交互

添加多语言支持,将管理界面中的静态文本替换为可翻译字符串
优化形状选择交互,添加原生下拉列表和预览功能
补充形状描述的国际化文本和工具提示
This commit is contained in:
tikkhun 2025-12-12 11:16:04 +08:00
parent 7ce8be8bf9
commit 0855b22974
2 changed files with 112 additions and 22 deletions

View File

@ -20,6 +20,7 @@
var shapeListContainer = document.getElementById('yooneSnowShapeList');
var shapeAddSelect = document.getElementById('yooneSnowAddShapeSelect');
var shapeLabelPreviewHost = document.getElementById('yooneSnowAddShapeLabelPreview');
var shapeNativeOverlay = document.getElementById('yooneSnowShapeNativeOverlay');
var shapeAddButton = null;
var emojiSelect = document.getElementById('yooneSnowEmojiSelect');
var typeSelect = document.getElementById('yooneSnowAddTypeSelect');
@ -194,7 +195,7 @@
var cancelBtn = document.createElement('button');
cancelBtn.type = 'button';
cancelBtn.className = 'button yoone-snow-cancel-shape';
cancelBtn.textContent = 'Cancel';
cancelBtn.textContent = t('cancel', 'Cancel');
cancelBtn.style.marginTop = '6px';
wrapper.appendChild(previewHost);
wrapper.appendChild(nameSpan);
@ -245,7 +246,7 @@
// 条件判断 如果 wp.media 不可用则终止
if (typeof wp === 'undefined' || !wp.media) { return; }
var frame = wp.media({
title: 'Select images or SVG',
title: t('select_images_or_svg', 'Select images or SVG'),
multiple: true,
library: { type: [ 'image', 'image/svg+xml' ] }
});
@ -284,7 +285,7 @@
var removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'button yoone-snow-remove-media';
removeBtn.textContent = 'Remove';
removeBtn.textContent = t('remove', 'Remove');
removeBtn.style.marginTop = '6px';
wrapper.appendChild(img);
wrapper.appendChild(weightInput);
@ -424,7 +425,7 @@
var removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'button yoone-snow-remove-emoji';
removeBtn.textContent = 'Cancel';
removeBtn.textContent = t('cancel', 'Cancel');
removeBtn.style.marginTop = '6px';
wrapper.appendChild(preview);
wrapper.appendChild(weightInput);
@ -557,6 +558,7 @@
}
}
});
// 僅使用原生下拉 不再彈出自定義覆蓋列表
}
@ -624,7 +626,7 @@
var removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'button yoone-snow-remove-text';
removeBtn.textContent = 'Cancel';
removeBtn.textContent = t('cancel', 'Cancel');
removeBtn.style.marginTop = '6px';
wrapper.appendChild(preview);
wrapper.appendChild(weightInput);
@ -665,3 +667,54 @@
initAdminMedia();
}
})();
// 初始化內嵌下拉的預覽與點擊交互
(function(){
if (!shapeNativeOverlay) { return; }
var nodes = shapeNativeOverlay.querySelectorAll('.yoone-snow-shape-native-item');
nodes.forEach(function(btn){
var key = btn.getAttribute('data-shape-key');
var host = btn.querySelector('.yoone-snow-shape-native-preview');
if (host && host.childNodes && host.childNodes.length === 0){
var previewEl = createShapePreviewElement(key);
if (previewEl){
try { previewEl.style.backgroundColor = 'transparent'; } catch(e){}
try { previewEl.style.border = 'none'; } catch(e){}
try { previewEl.style.borderRadius = '0'; } catch(e){}
host.appendChild(previewEl);
}
}
btn.addEventListener('click', function(){
// 先更新左側預覽 再添加卡片 並關閉內嵌下拉
if (shapeLabelPreviewHost){
while (shapeLabelPreviewHost.firstChild){ shapeLabelPreviewHost.removeChild(shapeLabelPreviewHost.firstChild); }
var previewEl2 = createShapePreviewElement(key);
if (previewEl2){
try { previewEl2.style.backgroundColor = 'transparent'; } catch(e){}
try { previewEl2.style.border = 'none'; } catch(e){}
try { previewEl2.style.borderRadius = '0'; } catch(e){}
shapeLabelPreviewHost.style.display = 'flex';
shapeLabelPreviewHost.appendChild(previewEl2);
}
}
addShapeBox(key);
if (shapeNativeOverlay){ shapeNativeOverlay.style.display = 'none'; }
// 重置原生下拉為未選中 清空左側預覽 保持簡潔
if (shapeAddSelect){ shapeAddSelect.value = ''; }
if (shapeLabelPreviewHost){
while (shapeLabelPreviewHost.firstChild){ shapeLabelPreviewHost.removeChild(shapeLabelPreviewHost.firstChild); }
shapeLabelPreviewHost.style.display = 'none';
}
});
});
// 外部點擊時關閉內嵌下拉
document.addEventListener('click', function(e){
var t = e.target;
var inside = (t === shapeNativeOverlay || shapeNativeOverlay.contains(t) || (shapeAddSelect && (t === shapeAddSelect || shapeAddSelect.contains(t))));
if (!inside){ shapeNativeOverlay.style.display = 'none'; }
});
})();
function t(key, fallback){
var dict = (typeof window !== 'undefined' && window.YooneSnowAdmin && window.YooneSnowAdmin.i18n) ? window.YooneSnowAdmin.i18n : {};
var val = dict[key];
return String(val || fallback || '');
}

View File

@ -113,6 +113,9 @@ function yoone_snow_enqueue_assets() {
$media_weights_by_url[$url] = $w;
}
}
// 形状权重默认值 映射为形状键到非负整数权重
// 权重用于控制每次生成时的相对概率 权重越大被选中越频繁
// 权重为 0 表示可选但不参与随机生成
$default_weights = array(
'dot' => 1,
'flake' => 4,
@ -127,6 +130,7 @@ function yoone_snow_enqueue_assets() {
);
$saved_weights = get_option('yoone_snow_shape_weights', $default_weights);
if (!is_array($saved_weights)) { $saved_weights = array(); }
// 合并用户保存的权重与默认值 并规范为非负整数
$shape_weights = array();
foreach ($default_weights as $k => $v) {
$val = isset($saved_weights[$k]) ? intval($saved_weights[$k]) : intval($v);
@ -319,6 +323,23 @@ function yoone_snow_admin_enqueue($hook) {
'christmas_tree' => plugins_url('assets/圣诞树.svg', __FILE__),
'reindeer' => plugins_url('assets/圣诞麋鹿.svg', __FILE__),
'christmas_berry' => plugins_url('assets/圣诞果.svg', __FILE__),
),
'i18n' => array(
'cancel' => esc_html__('Cancel', 'yoone-snow'),
'remove' => esc_html__('Remove', 'yoone-snow'),
'select_images_or_svg' => esc_html__('Select images or SVG', 'yoone-snow'),
'type' => esc_html__('Type', 'yoone-snow'),
'default' => esc_html__('Default', 'yoone-snow'),
'emoji' => esc_html__('Emoji', 'yoone-snow'),
'media' => esc_html__('Media', 'yoone-snow'),
'text' => esc_html__('Text', 'yoone-snow'),
'select_shape' => esc_html__('Select shape', 'yoone-snow'),
'select_emoji' => esc_html__('Select emoji', 'yoone-snow'),
'add_images' => esc_html__('Add Images', 'yoone-snow'),
'type_text' => esc_html__('Type text', 'yoone-snow'),
'type_emoji_or_alias' => esc_html__('Type emoji or alias', 'yoone-snow'),
'add_shapes_all_in_one' => esc_html__('Add shapes by type all in one list', 'yoone-snow'),
'shapes' => esc_html__('Shapes', 'yoone-snow')
)
));
wp_enqueue_script($admin_script_handle);
@ -370,7 +391,7 @@ function yoone_snow_register_settings() {
add_settings_field(
'yoone_snow_mixed_items',
'Shapes',
esc_html__('Shapes', 'yoone-snow'),
function() {
// 读取默认形状列表 用于初始渲染卡片
$current_list = get_option('yoone_snow_mixed_items', array('dot','flake'));
@ -390,6 +411,19 @@ function yoone_snow_register_settings() {
'reindeer' => 'Reindeer',
'christmas_berry' => 'Christmas Berry',
);
// 形状描述映射 用于在界面提示 使用国际化函数
$shape_descriptions = array(
'dot' => __('Basic dot shape simple and lightweight', 'yoone-snow'),
'flake' => __('Snowflake shape more decorative', 'yoone-snow'),
'yuanbao' => __('Yuanbao shape festive theme', 'yoone-snow'),
'coin' => __('Coin shape festive theme', 'yoone-snow'),
'santa_hat' => __('Santa hat shape seasonal theme', 'yoone-snow'),
'candy_cane' => __('Candy cane shape seasonal theme', 'yoone-snow'),
'christmas_sock' => __('Christmas sock shape seasonal theme', 'yoone-snow'),
'christmas_tree' => __('Christmas tree shape seasonal theme', 'yoone-snow'),
'reindeer' => __('Reindeer shape seasonal theme', 'yoone-snow'),
'christmas_berry' => __('Christmas berry shape seasonal theme', 'yoone-snow'),
);
// 读取已选择的媒体与 emoji 列表 用于统一卡片渲染
$current_media = get_option('yoone_snow_media_items', array());
if (!is_array($current_media)) { $current_media = array(); }
@ -411,13 +445,13 @@ function yoone_snow_register_settings() {
// 渲染默认形状卡片
foreach ($current_list as $key) {
if (!isset($options[$key])) { continue; }
echo '<div class="yoone-snow-shape-item" data-shape-key="' . esc_attr($key) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<div class="yoone-snow-shape-item" data-shape-key="' . esc_attr($key) . '" title="' . esc_attr(isset($shape_descriptions[$key]) ? $shape_descriptions[$key] : '') . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<div class="yoone-snow-shape-preview" style="width:32px;height:32px;margin-bottom:6px;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;"></div>';
echo '<span>' . esc_html($options[$key]) . '</span>';
$shapeWeightVal = isset($shape_weights_current[$key]) ? intval($shape_weights_current[$key]) : 1;
echo '<input type="number" min="0" name="yoone_snow_shape_weights[' . esc_attr($key) . ']" value="' . esc_attr($shapeWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_mixed_items[]" value="' . esc_attr($key) . '" />';
echo '<button type="button" class="button yoone-snow-cancel-shape" style="margin-top:6px;">Cancel</button>';
echo '<button type="button" class="button yoone-snow-cancel-shape" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>';
}
// 渲染 emoji 卡片
@ -429,7 +463,7 @@ function yoone_snow_register_settings() {
$emojiWeightVal = isset($emoji_weights_current[$label]) ? intval($emoji_weights_current[$label]) : 1;
echo '<input type="number" min="0" name="yoone_snow_emoji_weights[' . esc_attr($label) . ']" value="' . esc_attr($emojiWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_emoji_items[]" value="' . esc_attr($label) . '" />';
echo '<button type="button" class="button yoone-snow-remove-emoji" style="margin-top:6px;">Cancel</button>';
echo '<button type="button" class="button yoone-snow-remove-emoji" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>';
}
// 渲染媒体卡片
@ -443,7 +477,7 @@ function yoone_snow_register_settings() {
$mediaWeightVal = isset($media_weights_current[$safe_id]) ? intval($media_weights_current[$safe_id]) : 1;
echo '<input type="number" min="0" name="yoone_snow_media_weights[' . $safe_id . ']" value="' . esc_attr($mediaWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_media_items[]" value="' . $safe_id . '" />';
echo '<button type="button" class="button yoone-snow-remove-media" style="margin-top:6px;">Remove</button>';
echo '<button type="button" class="button yoone-snow-remove-media" style="margin-top:6px;">' . esc_html__('Remove', 'yoone-snow') . '</button>';
echo '</div>';
}
// 文本卡片
@ -455,41 +489,44 @@ function yoone_snow_register_settings() {
$textWeightVal = isset($text_weights_current[$label]) ? intval($text_weights_current[$label]) : 1;
echo '<input type="number" min="0" name="yoone_snow_text_weights[' . esc_attr($label) . ']" value="' . esc_attr($textWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_text_items[]" value="' . esc_attr($label) . '" />';
echo '<button type="button" class="button yoone-snow-remove-text" style="margin-top:6px;">Cancel</button>';
echo '<button type="button" class="button yoone-snow-remove-text" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>';
}
echo '<div id="yooneSnowAddUnified" class="yoone-snow-shape-item" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;gap:8px;min-width:96px;">';
echo '<div style="display:flex;gap:8px;align-items:center;">';
echo '<label>Type <select id="yooneSnowAddTypeSelect" style="min-width:180px;">';
echo '<option value="default" selected>Default</option>';
echo '<option value="emoji">Emoji</option>';
echo '<option value="media">Media</option>';
echo '<option value="text">Text</option>';
echo '<label>' . esc_html__('Type', 'yoone-snow') . ' <select id="yooneSnowAddTypeSelect" style="min-width:180px;">';
echo '<option value="default" selected>' . esc_html__('Default', 'yoone-snow') . '</option>';
echo '<option value="emoji">' . esc_html__('Emoji', 'yoone-snow') . '</option>';
echo '<option value="media">' . esc_html__('Media', 'yoone-snow') . '</option>';
echo '<option value="text">' . esc_html__('Text', 'yoone-snow') . '</option>';
echo '</select></label>';
echo '</div>';
echo '<div id="yooneSnowAddDefaultPane" style="display:flex;gap:8px;align-items:center;position:relative;">';
echo '<div id="yooneSnowAddShapeLabelPreview" style="width:32px;height:32px;display:none;align-items:center;justify-content:center;"></div>';
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">Select shape</option>';
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">' . esc_html__('Select shape', 'yoone-snow') . '</option>';
foreach ($options as $key => $label) {
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
}
echo '</select>';
echo '</div>';
echo '<div id="yooneSnowAddEmojiPane" style="display:none;gap:8px;align-items:center;">';
echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">Select emoji</option></select>';
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="Type emoji or alias" style="width:240px;" />';
echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">' . esc_html__('Select emoji', 'yoone-snow') . '</option></select>';
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="' . esc_attr__('Type emoji or alias', 'yoone-snow') . '" style="width:240px;" />';
echo '<div id="yooneSnowEmojiSuggest" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;"></div>';
echo '</div>';
echo '<div id="yooneSnowAddMediaPane" style="display:none;gap:8px;align-items:center;">';
echo '<button type="button" class="button button-primary" id="yooneSnowAddMediaUnified">Add Images</button>';
echo '<button type="button" class="button button-primary" id="yooneSnowAddMediaUnified">' . esc_html__('Add Images', 'yoone-snow') . '</button>';
echo '</div>';
echo '<div id="yooneSnowAddTextPane" style="display:none;gap:8px;align-items:center;">';
echo '<input type="text" id="yooneSnowAddTextInput" placeholder="Type text" style="width:240px;" />';
echo '<input type="text" id="yooneSnowAddTextInput" placeholder="' . esc_attr__('Type text', 'yoone-snow') . '" style="width:240px;" />';
echo '</div>';
echo '<p class="description">Add shapes by type all in one list</p>';
// 权重说明 提示用户权重影响概率且为非负整数
echo '<p class="description">' . esc_html__('Add shapes by type all in one list', 'yoone-snow') . '</p>';
echo '</div>';
echo '</div>';
echo '<p class="description">' . esc_html__('Weight controls relative probability Weight is a non negative integer Weight 0 disables a shape Probability equals shape weight divided by the sum of all shape weights Example dot 1 flake 4 flake has about four times the chance of dot', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'