Files
test-repo/docs/.vitepress/theme/components/appendix/browser-frontend/InternationalizationDemo.vue
T
sanbuphy 260d17ee8b feat: 添加多个附录交互式组件和文档更新
- 添加浏览器前端组件:无障碍访问、国际化、实时通信
- 添加 Transformer 注意力机制系列组件
- 更新 Canvas、数据追踪等现有组件
- 修复 ESLint 变量名冲突问题
- 完善相关附录文档
2026-02-24 08:34:53 +08:00

466 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="demo-wrapper">
<div class="demo-header" dir="ltr">
<span class="icon">🌍</span>
<span>浏览器原生的本地化转换 (i18n) 演示</span>
</div>
<div class="intro-text" dir="ltr">
请在下方切换用户的本地化偏好环境代号体验浏览器引擎在不修改任何底层数据逻辑的前提下是如何同时处理**语言字典****弹性换行****排版反转 (RTL)** 以及**原生数据格式转换**
</div>
<!-- 顶层控制面板 -->
<div class="controls-panel" dir="ltr">
<label for="env-selector" class="control-label">🌐 模拟操作系统/浏览器偏好环境</label>
<select id="env-selector" v-model="currentLocale" class="env-select">
<option value="zh-CN">🇨🇳 zh-CN (简体中文)</option>
<option value="en-US">🇺🇸 en-US (美国英语)</option>
<option value="de-DE">🇩🇪 de-DE (德国德语) - 关注文字长度爆增</option>
<option value="ar-SA">🇸🇦 ar-SA (沙特阿拉伯语) - 关注 RTL 排版全量反转</option>
</select>
</div>
<div class="lab-container">
<!-- 实验室 1排版与字典 -->
<div class="lab-section">
<h3 class="lab-title" dir="ltr">实战区 1依赖 Flex 面向字典与排版进行重构</h3>
<p class="lab-desc" dir="ltr">
由于我们在 CSS 中使用了弹性的 Flex 布局并且没有写死 `margin-left` 而是用了 `gap` `justify-content`当切换到阿拉伯语时`dir="rtl"` 属性会指挥浏览器**完美镜像反转**整个布局当切换到德语时超长的按钮文字会自动引发弹性换行而不会溢出
</p>
<!-- 核心演示区域响应 RTL -->
<div class="lab-window" :dir="layoutDirection">
<header class="app-nav">
<div class="logo-area">
<span class="logo"></span>
<span class="appName">{{ dictionary[currentLocale].appName }}</span>
</div>
<div class="links-area">
<a href="#">{{ dictionary[currentLocale].navIndex }}</a>
<a href="#">{{ dictionary[currentLocale].navMe }}</a>
</div>
</header>
<main class="app-content">
<div class="alert-box">
{{ dictionary[currentLocale].alertDesc }}
</div>
<div class="action-bar">
<button class="btn btn-primary">{{ dictionary[currentLocale].btnPay }}</button>
<button class="btn btn-ghost">{{ dictionary[currentLocale].btnBack }} <span dir="ltr"></span></button>
</div>
</main>
</div>
</div>
<!-- 实验室 2Intl API 底层转换 -->
<div class="lab-section rtl-ignore-section">
<h3 class="lab-title" dir="ltr">实战区 2使用 Intl 引擎接管数据呈现</h3>
<p class="lab-desc" dir="ltr">
彻底抛弃正则表达式的截取与拼接看看原生的 <code>Intl.NumberFormat</code> <code>Intl.DateTimeFormat</code> 是如何根据我们上方选择的环境代号将下方固定不变的底层二进制数据无缝格式化的
</p>
<div class="data-compare-window" dir="ltr">
<!-- 金钱数据对比 -->
<div class="data-row">
<div class="raw-data">
<span class="data-label">底层内存数值 (Float):</span>
<code class="data-code">1459800.5</code>
</div>
<div class="data-arrow">
引擎介入<br/>
</div>
<div class="intl-data">
<span class="data-label">DOM 最终呈现:</span>
<span class="formatted-val highlight-money">{{ formattedAmount }}</span>
</div>
</div>
<!-- 日期数据对比 -->
<div class="data-row">
<div class="raw-data">
<span class="data-label">底层内存数值 (Timestamp):</span>
<code class="data-code">1757430000000</code>
</div>
<div class="data-arrow">
引擎介入<br/>
</div>
<div class="intl-data">
<span class="data-label">DOM 最终呈现:</span>
<span class="formatted-val highlight-date">{{ formattedDate }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const currentLocale = ref('zh-CN')
// 极其简易的本地化字典
const dictionary = {
'zh-CN': {
appName: '企业云服务',
navIndex: '控制台首页',
navMe: '账户设置',
alertDesc: '您有一个待支付的云服务器实例账单,请在 24 小时内完成续费操作以免停机。',
btnPay: '立即确认并支付款项',
btnBack: '取消并返回'
},
'en-US': {
appName: 'Enterprise Cloud',
navIndex: 'Dashboard',
navMe: 'Account',
alertDesc: 'You have a pending cloud server instance bill. Please renew within 24 hours to avoid suspension.',
btnPay: 'Confirm & Proceed to Pay',
btnBack: 'Cancel'
},
'de-DE': {
appName: 'Unternehmenscloud',
navIndex: 'Startseite',
navMe: 'Kontoeinstellungen',
alertDesc: 'Sie haben eine ausstehende Rechnung für Ihre Cloud-Server-Instanz. Bitte verlängern Sie innerhalb von 24 Stunden, um eine Aussetzung zu vermeiden.',
btnPay: 'Bestätigen und sofortigen Zahlungsvorgang abschließen', // 故意设置的德语超长合成词
btnBack: 'Abbrechen'
},
'ar-SA': {
appName: 'سحابة المؤسسة',
navIndex: 'لوحة القيادة',
navMe: 'إعدادات الحساب',
alertDesc: 'لديك فاتورة معلقة لمثيل خادم السحابة الخاص بك. يرجى التجديد خلال 24 ساعة لتجنب التعليق.',
btnPay: 'تأكيد والمتابعة للدفع',
btnBack: 'إلغاء والعودة'
}
}
// 固定的底层原始数据
const RAW_TIMESTAMP = 1757430000000 // 模拟某个固定时间 2025-09-09(近似)
const RAW_MONEY = 1459800.5
// 计算布局方向 (核心知识点:处理 RTL)
const layoutDirection = computed(() => {
return currentLocale.value === 'ar-SA' ? 'rtl' : 'ltr'
})
// 原生 Intl 货币格式化
const formattedAmount = computed(() => {
let currency = 'CNY'
if (currentLocale.value === 'en-US') currency = 'USD'
if (currentLocale.value === 'de-DE') currency = 'EUR'
if (currentLocale.value === 'ar-SA') currency = 'SAR'
return new Intl.NumberFormat(currentLocale.value, {
style: 'currency',
currency: currency,
minimumFractionDigits: 2
}).format(RAW_MONEY)
})
// 原生 Intl 日期格式化
const formattedDate = computed(() => {
return new Intl.DateTimeFormat(currentLocale.value, {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
}).format(new Date(RAW_TIMESTAMP))
})
</script>
<style scoped>
.demo-wrapper {
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 1.8rem;
margin: 2rem 0;
font-family: var(--vp-font-family-base);
}
.demo-header {
display: flex;
align-items: center;
gap: 0.8rem;
font-size: 1.3rem;
font-weight: 700;
color: var(--vp-c-text-1);
margin-bottom: 1rem;
border-bottom: 2px solid var(--vp-c-divider);
padding-bottom: 0.8rem;
}
.intro-text {
font-size: 0.95rem;
color: var(--vp-c-text-2);
margin-bottom: 1.5rem;
line-height: 1.6;
}
.controls-panel {
background: var(--vp-c-brand-soft);
border: 1px solid var(--vp-c-brand-1);
border-radius: 8px;
padding: 1rem 1.5rem;
margin-bottom: 2rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
@media (min-width: 640px) {
.controls-panel {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
.control-label {
font-weight: 700;
color: var(--vp-c-brand-1);
font-size: 1rem;
}
.env-select {
padding: 0.4rem 1rem;
border-radius: 6px;
border: 2px solid var(--vp-c-brand-1);
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
font-weight: 600;
cursor: pointer;
min-width: 280px;
}
/* 两个实战区的公共样式 */
.lab-container {
display: flex;
flex-direction: column;
gap: 2.5rem;
}
.lab-title {
font-size: 1.15rem;
font-weight: 700;
color: var(--vp-c-text-1);
margin: 0 0 0.8rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.lab-title::before {
content: '';
display: inline-block;
width: 4px;
height: 18px;
background: var(--vp-c-brand-1);
border-radius: 2px;
}
.lab-desc {
font-size: 0.9rem;
color: var(--vp-c-text-2);
line-height: 1.5;
margin-bottom: 1.2rem;
}
.lab-desc code {
color: var(--vp-c-brand-1);
background: var(--vp-c-bg-alt);
padding: 0.1rem 0.3rem;
border-radius: 4px;
}
/* 实验室 1 的内部排版沙盒 */
.lab-window {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 6px 12px rgba(0,0,0,0.08);
background: var(--vp-c-bg);
transition: all 0.3s ease;
}
.app-nav {
display: flex;
justify-content: space-between;
align-items: center;
background: #1e293b;
color: white;
padding: 1rem 1.5rem;
}
.logo-area {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 700;
font-size: 1.1rem;
}
.logo { color: #38bdf8; font-size: 1.3rem; }
.links-area {
display: flex;
gap: 1.5rem;
}
.links-area a {
color: #94a3b8;
text-decoration: none;
font-weight: 600;
font-size: 0.9rem;
transition: 0.2s;
}
.links-area a:hover { color: white; }
.app-content {
padding: 2rem;
background: #f8fafc;
}
.alert-box {
background: #fffbeb;
border-left: 4px solid #f59e0b;
padding: 1.2rem;
color: #b45309;
border-radius: 0 6px 6px 0;
margin-bottom: 1.5rem;
font-size: 0.95rem;
line-height: 1.5;
}
/* RTL 环境下,警告框的彩色边框需要自动镜像到了右侧!这通过 CSS 的逻辑属性来实现最佳!但我们还是直接利用 dir 的流式特性。我们把 border-left 改为 border-inline-start */
[dir="rtl"] .alert-box {
border-left: none;
border-right: 4px solid #f59e0b;
border-radius: 6px 0 0 6px;
}
.action-bar {
display: flex;
gap: 1rem;
flex-wrap: wrap; /* 核心知识点:弹性拉伸保护 */
}
.btn {
padding: 0.7rem 1.2rem;
font-size: 0.9rem;
font-weight: 600;
border: none;
border-radius: 6px;
cursor: pointer;
flex: 1; /* 弹性填满剩余空间,对抗超长德语 */
min-width: 150px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.btn-primary { background: #2563eb; color: white; }
.btn-primary:hover { background: #1d4ed8; }
.btn-ghost { background: #e2e8f0; color: #475569; border: 1px solid #cbd5e1;}
.btn-ghost:hover { background: #cbd5e1; }
.dark .app-content { background: var(--vp-c-bg-alt); }
.dark .alert-box { background: rgba(245, 158, 11, 0.1); color: #fcd34d; }
.dark .btn-ghost { background: var(--vp-c-bg-soft); color: var(--vp-c-text-1); border-color: var(--vp-c-divider); }
.dark .btn-ghost:hover { background: var(--vp-c-divider); }
/* 实验室 2 的转换展示面板 */
.data-compare-window {
display: flex;
flex-direction: column;
gap: 1.2rem;
padding: 1.5rem;
border-radius: 8px;
border: 1px dashed var(--vp-c-divider);
background: var(--vp-c-bg-soft);
}
.data-row {
display: flex;
flex-direction: column;
align-items: stretch;
background: var(--vp-c-bg);
padding: 1.2rem;
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
box-shadow: 0 2px 4px rgba(0,0,0,0.03);
}
@media (min-width: 768px) {
.data-row {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
.raw-data, .intl-data {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.intl-data {
text-align: left;
}
@media (min-width: 768px) {
.intl-data { text-align: right; }
}
.data-label {
font-size: 0.75rem;
font-weight: 700;
color: var(--vp-c-text-3);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.data-code {
font-family: monospace;
background: #1e293b;
color: #a7f3d0;
padding: 0.5rem 0.8rem;
border-radius: 6px;
font-size: 1.05rem;
font-weight: bold;
word-break: break-all;
}
.data-arrow {
color: var(--vp-c-brand-1);
font-weight: 700;
font-size: 0.9rem;
text-align: center;
padding: 1rem;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
@media (max-width: 767px) {
.data-arrow { padding: 0.5rem; }
}
.formatted-val {
font-size: 1.2rem;
font-weight: 800;
letter-spacing: -0.5px;
}
.highlight-money { color: #f59e0b; } /* 显眼的金色/橙色代表金钱 */
.highlight-date { color: #3b82f6; } /* 蓝色代表日期体系 */
.dark .data-code { background: #000; color: #10b981; border: 1px solid #333; }
</style>