网易考拉商品详情页前端性能优化实战
网易考拉作为跨境电商平台,商品详情页具有全球供应链、多语言多币种、海外直邮/保税仓、正品保障、海淘用户特征明显等特点。本文结合考拉海购的业务特性,分享跨境场景下的性能优化方案。
一、考拉海购详情页业务特点分析
1.1 页面结构特征
javascript
┌─────────────────────────────────────────────────────────────────┐
│ 网易考拉商品详情页 - 跨境电商C2C/B2C模式 │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 全球化头部(多语言切换+多币种切换+关税计算器+购物车) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 商品展示区(高清图册+短视频+直播+AR试妆+买家秀) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 跨境核心信息区 │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 商品标题+跨境标识+正品保障+溯源码 │ │ │
│ │ │ 原价(CNY)+促销价(CNY)+税费预估+运费预估+包税标识 │ │ │
│ │ │ 发货方式(保税仓/海外直邮)+时效预估+清关进度 │ │ │
│ │ │ 库存状态+限购数量+预售标识+缺货预警 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 跨境购物工具区 │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 关税计算器+运费估算器+尺码助手+成分解析器 │ │ │
│ │ │ 多语言描述切换+多地区价格对比+海淘攻略 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 商品详情区(多语言详情+规格参数+品牌故事+物流说明) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 跨境推荐区(相似海淘商品+同品牌推荐+保税仓热卖+达人推荐)│ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 底部行动栏(加入购物车+立即购买+预约购买+收藏监控) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
1.2 跨境电商场景性能痛点
| 痛点类别 | 具体表现 | 业务影响 |
|---|---|---|
| 全球供应链复杂性 | 多仓库库存同步延迟、跨境物流时效多变 | 库存显示不准确,用户体验差 |
| 多语言多币种 | 12种语言、15种币种实时换算 | 计算复杂度高,渲染延迟 |
| 关税计算复杂 | 动态关税政策、个人物品免税额度、品类税率差异 | 计算耗时,影响下单转化 |
| 高清媒体资源 | 4K商品图片、短视频、360°展示 | 资源体积大,加载缓慢 |
| 跨境网络延迟 | API调用境外服务商、DNS解析慢 | 接口响应慢,超时率高 |
| 合规性要求 | 正品溯源、海关申报、税务合规展示 | 额外数据加载,页面复杂度高 |
| 移动端海淘用户 | 中老年用户居多、网络环境复杂 | 性能要求更高,容错率低 |
1.3 跨境场景性能基线数据
javascript
优化前性能报告(基于考拉海购真实监控):
┌─────────────────────────────────────────────────────────────────┐
│ 指标名称 │ 平均值 │ P90值 │ P99值 │ 业务阈值 │
├─────────────────────────────────────────────────────────────────┤
│ FCP (首次内容绘制) │ 3.5s │ 5.8s │ 9.2s │ <2.0s │
│ LCP (最大内容绘制) │ 5.2s │ 8.5s │ 13.8s │ <3.0s │
│ TTI (可交互时间) │ 7.8s │ 12.5s │ 19.2s │ <4.0s │
│ TBT (总阻塞时间) │ 1450ms │ 2380ms │ 3850ms │ <500ms │
│ CLS (累积布局偏移) │ 0.28 │ 0.45 │ 0.72 │ <0.1 │
│ JS Bundle Size │ 920KB │ 1180KB │ 1520KB │ <400KB │
│ 首屏接口响应时间 │ 1250ms │ 2100ms │ 3400ms │ <300ms │
│ 关税计算响应时间 │ 890ms │ 1560ms │ 2580ms │ <200ms │
│ 多语言切换响应时间 │ 650ms │ 1100ms │ 1850ms │ <150ms │
│ 高清图片加载成功率 │ 83.5% │ 74.2% │ 62.8% │ >95% │
│ 跨境支付成功率 │ 87.3% │ 79.6% │ 71.2% │ >93% │
└─────────────────────────────────────────────────────────────────┘
二、跨境场景首屏优化
2.1 全球CDN+边缘计算架构
javascript
// edge/functions/kaola-detail-render.js
// 考拉海购边缘计算:全球CDN+边缘节点预渲染
export async function onRequest(context) {
const { request, env } = context;
const url = new URL(request.url);
const productId = url.pathname.split('/').pop();
const country = request.headers.get('CF-IPCountry') || 'CN';
const region = request.cf?.region || 'unknown';
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
// 构建全球缓存键
const cacheKey = `kaola_detail_${productId}_${country}_${region}`;
let cachedResponse = await caches.default.match(cacheKey);
if (cachedResponse) {
return this.addGlobalHeaders(cachedResponse, 'GLOBAL_HIT');
}
// 获取用户位置信息
const locationInfo = this.getLocationInfo(country, region);
// 并行获取跨境首屏数据
const [productInfo, inventoryInfo, priceInfo, shippingInfo, customsInfo, globalConfig] = await Promise.all([
this.fetchProductInfo(productId, locationInfo),
this.fetchInventoryInfo(productId, locationInfo.country),
this.fetchPriceInfo(productId, locationInfo.currency),
this.fetchShippingInfo(productId, locationInfo.country),
this.fetchCustomsInfo(productId, locationInfo.country),
this.fetchGlobalConfig(locationInfo.country)
]);
// 组装跨境首屏HTML
const html = this.generateGlobalHTML({
productInfo,
inventoryInfo,
priceInfo,
shippingInfo,
customsInfo,
globalConfig,
locationInfo,
timestamp: Date.now()
});
// 全球边缘缓存(根据地区设置不同TTL)
const ttl = this.getGlobalCacheTTL(locationInfo.country, productInfo.hotScore);
const response = new Response(html, {
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': `public, max-age=${ttl}, s-maxage=${ttl}`,
'X-Global-Cache': 'HIT',
'X-User-Region': locationInfo.region,
'X-User-Country': locationInfo.country,
'Vary': 'Accept-Language, CF-IPCountry'
}
});
// 异步写入全球缓存
caches.default.put(cacheKey, response.clone());
return this.addGlobalHeaders(response, 'GLOBAL_MISS');
}
// 获取地理位置信息
getLocationInfo(country, region) {
const countryConfigs = {
'CN': { currency: 'CNY', language: 'zh-CN', taxFreeLimit: 5000, vatRate: 0.13 },
'US': { currency: 'USD', language: 'en-US', taxFreeLimit: 800, vatRate: 0.08 },
'JP': { currency: 'JPY', language: 'ja-JP', taxFreeLimit: 50000, vatRate: 0.10 },
'KR': { currency: 'KRW', language: 'ko-KR', taxFreeLimit: 600000, vatRate: 0.10 },
'DE': { currency: 'EUR', language: 'de-DE', taxFreeLimit: 430, vatRate: 0.19 },
'UK': { currency: 'GBP', language: 'en-GB', taxFreeLimit: 390, vatRate: 0.20 },
'AU': { currency: 'AUD', language: 'en-AU', taxFreeLimit: 900, vatRate: 0.10 },
'SG': { currency: 'SGD', language: 'en-SG', taxFreeLimit: 400, vatRate: 0.07 }
};
const defaultConfig = countryConfigs['CN'];
const config = countryConfigs[country] || defaultConfig;
return {
country,
region,
currency: config.currency,
language: config.language,
taxFreeLimit: config.taxFreeLimit,
vatRate: config.vatRate,
timezone: this.getTimezoneForRegion(region),
shippingZone: this.getShippingZone(country)
};
}
// 生成全球化HTML
generateGlobalHTML({ productInfo, inventoryInfo, priceInfo, shippingInfo, customsInfo, globalConfig, locationInfo }) {
const { currency, language, taxFreeLimit, vatRate } = locationInfo;
const { originalPrice, salePrice, tax, shippingCost, totalPrice } = priceInfo;
// 计算关税和税费
const dutyAmount = this.calculateDuty(productInfo, customsInfo, taxFreeLimit, vatRate);
const finalTotalPrice = totalPrice + dutyAmount;
return `<!DOCTYPE html>
<html lang="${language}" data-currency="${currency}" data-country="${locationInfo.country}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>${this.generateGlobalTitle(productInfo, locationInfo)}</title>
<link rel="preconnect" href="https://img.kaola.com">
<link rel="preconnect" href="https://api.kaola.com">
<link rel="dns-prefetch" href="https://global-cdn.kaola.com">
<style>
/* 内联关键CSS - 跨境优化版 */
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
.skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%); background-size: 200% 100%; animation: skeleton-loading 1.5s infinite; }
@keyframes skeleton-loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
/* 跨境头部 */
.kaola-global-header { position: sticky; top: 0; z-index: 1000; padding: 10px 16px; background: linear-gradient(135deg, #e74c3c, #c0392b); color: white; }
.global-language-selector { display: flex; align-items: center; gap: 8px; }
.global-currency-selector { display: flex; align-items: center; gap: 4px; }
/* 跨境价格区 */
.crossborder-price-section { padding: 16px; background: linear-gradient(180deg, #fff8f0 0%, #ffffff 100%); }
.original-price { font-size: 14px; color: #999; text-decoration: line-through; }
.sale-price { font-size: 32px; font-weight: 700; color: #e74c3c; }
.sale-price::before { content: '${this.getCurrencySymbol(currency)}'; font-size: 18px; }
.tax-estimate { display: inline-block; padding: 4px 8px; background: #fff3cd; color: #856404; border-radius: 4px; font-size: 12px; margin-top: 8px; }
.shipping-info { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-size: 13px; color: #666; }
.free-shipping-badge { display: inline-flex; align-items: center; padding: 4px 8px; background: #d4edda; color: #155724; border-radius: 4px; font-size: 11px; }
.customs-clearance { display: flex; align-items: center; gap: 4px; margin-top: 8px; font-size: 12px; color: #17a2b8; }
/* 跨境标识 */
.crossborder-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.authenticity-badge { display: inline-flex; align-items: center; gap: 4px; padding: 6px 10px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; border-radius: 6px; font-size: 12px; }
.traceability-badge { display: inline-flex; align-items: center; gap: 4px; padding: 6px 10px; background: linear-gradient(135deg, #11998e, #38ef7d); color: white; border-radius: 6px; font-size: 12px; }
.warehouse-badge { display: inline-flex; align-items: center; gap: 4px; padding: 6px 10px; background: linear-gradient(135deg, #f093fb, #f5576c); color: white; border-radius: 6px; font-size: 12px; }
/* 库存与发货 */
.inventory-shipping { padding: 16px; background: #fff; margin-top: 12px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
.stock-status { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.stock-indicator { width: 8px; height: 8px; border-radius: 50%; background: #28a745; }
.stock-indicator.low { background: #ffc107; }
.stock-indicator.out { background: #dc3545; }
.shipping-method { display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f8f9fa; border-radius: 8px; margin-bottom: 8px; }
.delivery-time { font-size: 14px; color: #333; }
.delivery-zone { font-size: ; color: #666; }
/* 跨境工具 */
.crossborder-tools { padding: 16px; background: #fff; margin-top: 12px; border-radius: 12px; }
.tool-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.tool-item { display: flex; flex-direction: column; align-items: center; padding: 12px 8px; background: #f8f9fa; border-radius: 8px; cursor: pointer; transition: all 0.2s; }
.tool-item:hover { background: #e9ecef; transform: translateY(-2px); }
.tool-icon { font-size: 24px; margin-bottom: 6px; }
.tool-label { font-size: 11px; color: #666; text-align: center; }
/* 骨架屏 */
.skeleton-header { height: 56px; }
.skeleton-gallery { height: 320px; margin: 12px; border-radius: 12px; }
.skeleton-price { height: 48px; margin: 16px; border-radius: 8px; }
.skeleton-info { height: 120px; margin: 12px; border-radius: 12px; }
</style>
</head>
<body>
<div id="app">
<!-- 骨架屏占位 -->
<div class="skeleton skeleton-header"></div>
<div class="skeleton skeleton-gallery"></div>
<div class="skeleton skeleton-price"></div>
<div class="skeleton skeleton-info"></div>
</div>
<!-- 预加载全局配置到全局变量 -->
<script>
window.__KAOLA_GLOBAL_CONFIG__ = {
productInfo: ${JSON.stringify(productInfo)},
inventoryInfo: ${JSON.stringify(inventoryInfo)},
priceInfo: ${JSON.stringify(priceInfo)},
shippingInfo: ${JSON.stringify(shippingInfo)},
customsInfo: ${JSON.stringify(customsInfo)},
globalConfig: ${JSON.stringify(globalConfig)},
locationInfo: ${JSON.stringify(locationInfo)},
dutyCalculation: {
dutyAmount: ${dutyAmount},
finalTotalPrice: ${finalTotalPrice},
taxFreeLimit: ${taxFreeLimit},
vatRate: ${vatRate}
},
serverTime: ${Date.now()},
cdnDomains: ${JSON.stringify(globalConfig.cdnDomains)}
};
</script>
<!-- 加载主应用 -->
<script src="https://global-cdn.kaola.com/app/v2.1.0/kaola-main.js" async crossorigin></script>
</body>
</html>`;
}
// 获取全球缓存TTL
getGlobalCacheTTL(country, hotScore) {
// 热门商品缓存时间短,冷门商品缓存时间长
// 不同地区根据网络状况调整
const regionMultipliers = {
'CN': 1, // 国内最快
'HK': 1.2, // 香港
'JP': 1.5, // 日本
'US': 2, // 美国
'EU': 2.5, // 欧洲
'OTHER': 3 // 其他地区
};
let baseTTL = hotScore > 80 ? 45 : (hotScore > 50 ? 90 : 180);
return Math.round(baseTTL * (regionMultipliers[country] || regionMultipliers['OTHER']));
}
// 添加全球缓存头
addGlobalHeaders(response, cacheStatus) {
const newResponse = new Response(response.body, response);
newResponse.headers.set('X-Global-Cache-Status', cacheStatus);
newResponse.headers.set('X-CDN-Region', response.headers.get('cf-ray') || 'unknown');
return newResponse;
}
2.2 跨境SSR服务架构
javascript
// server/kaola-ssr.js
// 网易考拉SSR服务 - 跨境场景优化版
import { renderToString } from 'react-dom/server';
import { createServerComponent } from './server-components';
import { GlobalDataPrefetcher } from './services/globalDataPrefetcher';
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class KaolaSSRService {
constructor() {
this.prefetcher = new GlobalDataPrefetcher();
this.templateCache = new Map();
this.globalCacheRegions = ['CN', 'US', 'EU', 'APAC'];
}
// 主渲染方法
async renderPage(ctx) {
const { productId, lang, currency, country } = ctx.query;
// 构建全球化缓存键
const cacheKey = this.buildGlobalCacheKey(productId, lang, currency, country);
// 尝试从缓存获取
const cached = await this.getFromGlobalCache(cacheKey);
if (cached) {
return this.addGlobalCacheHeaders(cached, 'HIT');
}
// 获取用户全球化配置
const globalConfig = this.getGlobalConfig(ctx);
// 数据预取 - 跨境场景特殊优化
const preloadContext = await this.prefetcher.prefetch({
productId,
globalConfig,
userId: ctx.state.userId,
deviceInfo: ctx.state.deviceInfo,
sessionInfo: ctx.state.sessionInfo
});
// 服务端渲染
const html = await this.renderHTML(preloadContext, globalConfig);
// 写入全球缓存
const ttl = this.getGlobalCacheTTL(productId, preloadContext.productInfo?.hotScore, country);
await this.setGlobalCache(cacheKey, html, ttl);
return this.addGlobalCacheHeaders(html, 'MISS');
}
// 获取全球化配置
getGlobalConfig(ctx) {
const acceptLanguage = ctx.request.headers['accept-language'] || 'zh-CN';
const cfIpCountry = ctx.request.headers['cf-ipcountry'] || 'CN';
const cfRegion = ctx.request.cf?.region || 'unknown';
// 解析语言和货币偏好
const language = this.parseLanguagePreference(acceptLanguage);
const currency = this.parseCurrencyPreference(cfIpCountry, ctx.query.currency);
const country = cfIpCountry;
return {
language,
currency,
country,
region: cfRegion,
timezone: this.getRegionalTimezone(cfRegion),
shippingZone: this.getShippingZone(country),
taxConfig: this.getTaxConfiguration(country),
complianceConfig: this.getComplianceConfig(country),
featureFlags: this.getFeatureFlags(country, language)
};
}
// 跨境数据预取器
async prefetch({ productId, globalConfig, userId, deviceInfo, sessionInfo }) {
const { language, currency, country, taxConfig, complianceConfig } = globalConfig;
// 并行获取各类数据,按优先级排序
const [
productInfo, // 商品基本信息 - 最高优先级
inventoryData, // 库存信息 - 高优先级
pricingData, // 价格信息 - 高优先级
shippingOptions, // 配送选项 - 高优先级
customsData, // 海关数据 - 高优先级
authenticityData, // 正品数据 - 高优先级
globalReviews, // 全球评价 - 中优先级
relatedProducts, // 相关商品 - 中优先级
brandStory, // 品牌故事 - 低优先级
complianceDocs // 合规文档 - 低优先级
] = await Promise.all([
this.fetchProductInfo(productId, language),
this.fetchInventoryInfo(productId, country),
this.fetchPricingInfo(productId, currency, country, taxConfig),
this.fetchShippingOptions(productId, country),
this.fetchCustomsInfo(productId, country, complianceConfig),
this.fetchAuthenticityInfo(productId),
this.fetchGlobalReviews(productId, language),
this.fetchRelatedProducts(productId, country),
this.fetchBrandStory(productId, language),
this.fetchComplianceDocs(productId, country, complianceConfig)
]);
// 计算跨境核心数据
const crossborderData = this.calculateCrossborderData({
productInfo,
inventoryData,
pricingData,
shippingOptions,
customsData,
taxConfig,
country
});
return {
productInfo,
inventoryData,
pricingData,
shippingOptions,
customsData,
authenticityData,
globalReviews,
relatedProducts,
brandStory,
complianceDocs,
crossborderData,
globalConfig,
userId,
deviceInfo,
sessionInfo,
renderTime: Date.now()
};
}
// 计算跨境核心数据
calculateCrossborderData({ productInfo, inventoryData, pricingData, shippingOptions, customsData, taxConfig, country }) {
const { originalPrice, salePrice, baseCurrency } = pricingData;
const { dutyRate, vatRate, taxFreeLimit, personalAllowance } = taxConfig;
// 汇率转换
const exchangeRate = this.getExchangeRate(baseCurrency, pricingData.displayCurrency);
const convertedOriginalPrice = originalPrice * exchangeRate;
const convertedSalePrice = salePrice * exchangeRate;
// 税费计算
const subtotal = convertedSalePrice * pricingData.quantity;
const dutyCalculation = this.calculateDutyAmount(subtotal, customsData.category, dutyRate, personalAllowance);
const vatCalculation = this.calculateVAT(subtotal + dutyCalculation.amount, vatRate);
const totalTaxAmount = dutyCalculation.amount + vatCalculation.amount;
const finalTotalPrice = subtotal + totalTaxAmount + pricingData.shippingCost;
// 配送时效计算
const deliveryEstimate = this.calculateDeliveryEstimate(shippingOptions, country);
// 库存状态
const stockStatus = this.determineStockStatus(inventoryData, country);
// 正品保障级别
const authenticityLevel = this.determineAuthenticityLevel(productInfo, authenticityData);
return {
pricing: {
originalPrice: convertedOriginalPrice,
salePrice: convertedSalePrice,
exchangeRate,
currency: pricingData.displayCurrency,
quantity: pricingData.quantity,
subtotal,
shippingCost: pricingData.shippingCost,
dutyAmount: dutyCalculation.amount,
vatAmount: vatCalculation.amount,
totalTaxAmount,
finalTotalPrice,
taxBreakdown: {
duty: dutyCalculation,
vat: vatCalculation
}
},
shipping: {
options: shippingOptions,
estimate: deliveryEstimate,
fastestOption: shippingOptions.reduce((fastest, option) =>
option.estimatedDays < fastest.estimatedDays ? option : fastest
),
cheapestOption: shippingOptions.reduce((cheapest, option) =>
option.cost < cheapest.cost ? option : cheapest
)
},
inventory: {
status: stockStatus,
availableQuantity: inventoryData.availableQuantity,
reservedQuantity: inventoryData.reservedQuantity,
restockDate: inventoryData.restockDate,
warehouseLocation: inventoryData.warehouseLocation,
isPreorder: inventoryData.isPreorder,
preorderEndDate: inventoryData.preorderEndDate
},
authenticity: {
level: authenticityLevel,
certifications: authenticityData.certifications,
traceabilityCode: authenticityData.traceabilityCode,
verificationUrl: authenticityData.verificationUrl,
brandAuthorized: authenticityData.brandAuthorized
},
compliance: {
isRestricted: customsData.isRestricted,
requiresPrescription: customsData.requiresPrescription,
ageRestriction: customsData.ageRestriction,
importLicense: customsData.importLicense,
prohibitedInRegion: customsData.prohibitedInRegion
}
};
}
// 税费计算
calculateDutyAmount(subtotal, productCategory, dutyRate, personalAllowance) {
// 检查是否超过免税额
if (subtotal <= personalAllowance) {
return { amount: 0, rate: 0, isDutyFree: true, allowance: personalAllowance };
}
// 计算应税金额
const taxableAmount = subtotal - personalAllowance;
// 获取品类税率
const categoryRate = dutyRate[productCategory] || dutyRate['default'] || 0.1;
// 计算关税
const dutyAmount = taxableAmount * categoryRate;
return {
amount: parseFloat(dutyAmount.toFixed(2)),
rate: categoryRate,
taxableAmount,
isDutyFree: false,
allowance: personalAllowance,
category: productCategory
};
}
// VAT计算
calculateVAT(amount, vatRate) {
const vatAmount = amount * vatRate;
return {
amount: parseFloat(vatAmount.toFixed(2)),
rate: vatRate,
taxableAmount: amount
};
}
// 配送时效计算
calculateDeliveryEstimate(shippingOptions, destinationCountry) {
const estimates = shippingOptions.map(option => ({
method: option.method,
estimatedDays: option.estimatedDays,
cost: option.cost,
reliability: option.reliability,
trackingAvailable: option.trackingAvailable
}));
// 计算加权平均时效
const weightedAverage = estimates.reduce((sum, option) => {
return sum + (option.estimatedDays * option.reliability);
}, 0) / estimates.reduce((sum, option) => sum + option.reliability, 0);
return {
options: estimates,
averageDays: Math.round(weightedAverage),
fastestDays: Math.min(...estimates.map(e => e.estimatedDays)),
mostReliable: estimates.reduce((best, option) =>
option.reliability > best.reliability ? option : best
),
destinationCountry
};
}
// 库存状态判断
determineStockStatus(inventoryData, country) {
const { availableQuantity, reservedQuantity, isPreorder, restockDate } = inventoryData;
const totalAvailable = availableQuantity - reservedQuantity;
if (totalAvailable <= 0) {
if (isPreorder) {
return {
status: 'preorder',
label: '预售中',
message: `预计${restockDate}开始发货`,
canOrder: true
};
}
return {
status: 'out_of_stock',
label: '暂时缺货',
message: `预计${restockDate}补货`,
canOrder: false
};
}
if (totalAvailable <= 10) {
return {
status: 'low_stock',
label: '仅剩少量',
message: `仅剩${totalAvailable}件,欲购从速`,
canOrder: true,
urgencyLevel: 'high'
};
}
if (totalAvailable <= 50) {
return {
status: 'limited_stock',
label: '库存紧张',
message: `剩余${totalAvailable}件`,
canOrder: true,
urgencyLevel: 'medium'
};
}
return {
status: 'in_stock',
label: '现货充足',
message: '下单后24小时内发货',
canOrder: true,
urgencyLevel: 'low'
};
}
// 正品保障级别判断
determineAuthenticityLevel(productInfo, authenticityData) {
let score = 0;
const factors = [];
// 官方授权加分
if (authenticityData.brandAuthorized) {
score += 40;
factors.push('品牌官方授权');
}
// 溯源码加分
if (authenticityData.traceabilityCode) {
score += 30;
factors.push('正品溯源码');
}
// 认证证书加分
if (authenticityData.certifications?.length > 0) {
score += Math.min(authenticityData.certifications.length * 10, 20);
factors.push(`${authenticityData.certifications.length}项权威认证`);
}
// 平台质检加分
if (productInfo.qualityChecked) {
score += 10;
factors.push('考拉质检');
}
// 确定级别
let level, badge, description;
if (score >= 80) {
level = 'premium';
badge = '🏆 考拉严选·正品保证';
description = '多重正品保障,假一赔十';
} else if (score >= 60) {
level = 'verified';
badge = '✅ 正品保障';
description = '官方认证,品质保证';
} else if (score >= 40) {
level = 'standard';
badge = '🔍 正品承诺';
description = '平台正品承诺';
} else {
level = 'basic';
badge = '📦 正品行货';
description = '正规渠道采购';
}
return { level, score, badge, description, factors };
}
// 渲染HTML模板
async renderHTML(context, globalConfig) {
const { productInfo, crossborderData, globalConfig: gConfig } = context;
const { language, currency, country } = gConfig;
// 生成结构化数据(SEO优化)
const structuredData = this.generateStructuredData(context, globalConfig);
// 生成页面标题(多语言优化)
const pageTitle = this.generateGlobalTitle(productInfo, crossborderData, globalConfig);
// 生成Meta标签
const metaTags = this.generateMetaTags(context, globalConfig);
// 渲染React组件
const appHtml = renderToString(
<KaolaDetailApp context={context} globalConfig={globalConfig} />
);
// 组装完整HTML
return `<!DOCTYPE html>
<html lang="${language}" data-currency="${currency}" data-country="${country}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>${pageTitle}</title>
${metaTags}
${structuredData}
<!-- 预加载关键资源 -->
<link rel="preload" href="https://global-cdn.kaola.com/app/main.css" as="style">
<link rel="preload" href="https://global-cdn.kaola.com/app/main.js" as="script">
<link rel="preconnect" href="https://img.kaola.com">
<link rel="preconnect" href="https://api.kaola.com">
<link rel="dns-prefetch" href="https://eu-cdn.kaola.com">
<link rel="dns-prefetch" href="https://us-cdn.kaola.com">
<link rel="dns-prefetch" href="https://jp-cdn.kaola.com">
<style>
/* 内联关键CSS - 跨境优化版 */
${this.getCriticalCSS(globalConfig)}
</style>
</head>
<body>
<div id="root">${appHtml}</div>
<!-- 客户端水合脚本 -->
<script>
window.__KAOLA_INITIAL_STATE__ = ${JSON.stringify(context)};
window.__KAOLA_GLOBAL_CONFIG__ = ${JSON.stringify(globalConfig)};
window.__KAOLA_RENDER_TIME__ = ${Date.now()};
window.__KAOLA_SERVER_REGION__ = '${globalConfig.region}';
</script>
<script src="https://global-cdn.kaola.com/app/main.js" defer crossorigin></script>
</body>
</html>`;
}
// 获取全球化缓存键
buildGlobalCacheKey(productId, lang, currency, country) {
const normalizedLang = lang || 'zh-CN';
const normalizedCurrency = currency || 'CNY';
const normalizedCountry = country || 'CN';
return `kaola_detail_${productId}_${normalizedLang}_${normalizedCurrency}_${normalizedCountry}`;
}
// 获取全球缓存TTL
getGlobalCacheTTL(productId, hotScore, country) {
// 根据国家/地区设置不同的缓存策略
const regionalMultipliers = {
'CN': 1, // 国内最快更新
'HK': 1.5, // 香港
'TW': 1.5, // 台湾
'JP': 2, // 日本
'KR': 2, // 韩国
'US': 2.5, // 美国
'EU': 3, // 欧洲
'AU': 2.5, // 澳洲
'OTHER': 4 // 其他地区
};
let baseTTL;
if (hotScore > 90) {
baseTTL = 30; // 超热商品:30秒
} else if (hotScore > 70) {
baseTTL = 60; // 热销商品:1分钟
} else if (hotScore > 50) {
baseTTL = 120; // 一般商品:2分钟
} else {
baseTTL = 300; // 冷门商品:5分钟
}
const multiplier = regionalMultipliers[country] || regionalMultipliers['OTHER'];
return Math.round(baseTTL * multiplier);
}
}
export const kaolaSSR = new KaolaSSRService();
2.3 跨境关键CSS优化
javascript
// server/utils/criticalCSS.js
// 网易考拉关键CSS生成 - 跨境优化版
class KaolaCriticalCSS {
constructor() {
this.criticalSelectors = [
// 全球化头部
'.kaola-global-header',
'.global-language-selector',
'.global-currency-selector',
'.region-selector',
// 跨境价格区
'.crossborder-price-section',
'.original-price',
'.sale-price',
'.tax-estimate',
'.shipping-info',
'.free-shipping-badge',
'.customs-clearance',
// 跨境标识
'.crossborder-badges',
'.authenticity-badge',
'.traceability-badge',
'.warehouse-badge',
'.official-authorization',
// 库存与发货
'.inventory-shipping',
'.stock-status',
'.stock-indicator',
'.shipping-method',
'.delivery-time',
'.delivery-zone',
'.preorder-notice',
// 跨境工具
'.crossborder-tools',
'.tool-grid',
'.tool-item',
'.tool-icon',
'.tool-label',
// 关税计算器
'.duty-calculator',
'.tax-breakdown',
'.exchange-rate-info',
'.personal-allowance',
// 正品溯源
'.authenticity-section',
'.traceability-code',
'.verification-badge',
'.certification-list',
// 骨架屏
'.skeleton',
'.skeleton-animation'
];
}
getCriticalCSS(globalConfig = {}) {
const { language = 'zh-CN', currency = 'CNY' } = globalConfig;
return `
/* ===== 网易考拉关键CSS - 跨境优化版 ===== */
/* CSS重置与基础 */
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;line-height:1.6;color:#333;background:#f8f9fa}
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
/* 骨架屏动画 */
.skeleton{background:linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite}
@keyframes skeleton-loading{0%{background-position:200% 0}100%{background-position:-200% 0}}
/* 全球化头部 */
.kaola-global-header{position:sticky;top:0;z-index:1000;padding:10px 16px;background:linear-gradient(135deg,#e74c3c,#c0392b);color:#fff;display:flex;align-items:center;justify-content:space-between}
.global-language-selector{display:flex;align-items:center;gap:8px;cursor:pointer}
.global-language-selector select{background:rgba(255,255,255,0.2);border:none;color:#fff;padding:4px 8px;border-radius:4px;font-size:12px}
.global-language-selector select option{color:#333}
.global-currency-selector{display:flex;align-items:center;gap:4px;cursor:pointer}
.global-currency-selector span{font-size:14px;font-weight:600}
.region-selector{display:flex;align-items:center;gap:4px;font-size:12px;opacity:0.9}
/* 跨境价格区 */
.crossborder-price-section{padding:16px;background:linear-gradient(180deg,#fff8f0 0%,#ffffff 100%)}
.original-price{font-size:14px;color:#999;text-decoration:line-through;margin-right:8px}
.sale-price{font-size:32px;font-weight:700;color:#e74c3c}
.sale-price::before{content:'${this.getCurrencySymbol(currency)}';font-size:18px}
.tax-estimate{display:inline-block;padding:4px 8px;background:#fff3cd;color:#856404;border-radius:4px;font-size:12px;margin-top:8px}
.shipping-info{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:13px;color:#666}
.free-shipping-badge{display:inline-flex;align-items:center;padding:4px 8px;background:#d4edda;color:#155724;border-radius:4px;font-size:11px}
.customs-clearance{display:flex;align-items:center;gap:4px;margin-top:8px;font-size:12px;color:#17a2b8}
/* 跨境标识 */
.crossborder-badges{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.authenticity-badge{display:inline-flex;align-items:center;gap:4px;padding:6px 10px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border-radius:6px;font-size:12px}
.traceability-badge{display:inline-flex;align-items:center;gap:4px;padding:6px 10px;background:linear-gradient(135deg,#11998e,#38ef7d);color:#fff;border-radius:6px;font-size:12px}
.warehouse-badge{display:inline-flex;align-items:center;gap:4px;padding:6px 10px;background:linear-gradient(135deg,#f093fb,#f5576c);color:#fff;border-radius:6px;font-size:12px}
.official-authorization{display:inline-flex;align-items:center;gap:4px;padding:6px 10px;background:linear-gradient(135deg,#4facfe,#00f2fe);color:#fff;border-radius:6px;font-size:12px}
/* 库存与发货 */
.inventory-shipping{padding:16px;background:#fff;margin-top:12px;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,0.08)}
.stock-status{display:flex;align-items:center;gap:8px;margin-bottom:12px}
.stock-indicator{width:8px;height:8px;border-radius:50%;background:#28a745}
.stock-indicator.low{background:#ffc107}
.stock-indicator.out{background:#dc3545}
.stock-text{font-size:14px;color:#333;font-weight:500}
.stock-quantity{font-size:12px;color:#666;margin-left:auto}
.shipping-method{display:flex;align-items:center;justify-content:space-between;padding:12px;background:#f8f9fa;border-radius:8px;margin-bottom:8px}
.shipping-method:last-child{margin-bottom:0}
.method-info{display:flex;align-items:center;gap:8px}
.method-icon{width:24px;height:24px;background:#e9ecef;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px}
.method-name{font-size:14px;color:#333;font-weight:500}
.method-desc{font-size:12px;color:#666}
.delivery-time{font-size:14px;color:#28a745;font-weight:600}
.delivery-zone{font-size:12px;color:#666}
.preorder-notice{display:flex;align-items:center;gap:8px;padding:12px;background:#fff3cd;border-radius:8px;margin-top:12px;font-size:13px;color:#856404}
/* 跨境工具 */
.crossborder-tools{padding:16px;background:#fff;margin-top:12px;border-radius:12px}
.tool-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
.tool-item{display:flex;flex-direction:column;align-items:center;padding:12px 8px;background:#f8f9fa;border-radius:8px;cursor:pointer;transition:all 0.2s}
.tool-item:hover{background:#e9ecef;transform:translateY(-2px)}
.tool-item:active{transform:translateY(0)}
.tool-icon{font-size:24px;margin-bottom:6px}
.tool-label{font-size:11px;color:#666;text-align:center;line-height:1.3}
.tool-badge{position:absolute;top:-4px;right:-4px;width:16px;height:16px;background:#e74c3c;color:#fff;border-radius:50%;font-size:10px;display:flex;align-items:center;justify-content:center}
/* 关税计算器 */
.duty-calculator{padding:16px;background:linear-gradient(135deg,#f5f7fa 0%,#c3cfe2 100%);margin-top:12px;border-radius:12px}
.calculator-title{font-size:16px;font-weight:600;color:#333;margin-bottom:12px;display:flex;align-items:center;gap:8px}
.calculator-form{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.form-group{display:flex;flex-direction:column;gap:4px}
.form-label{font-size:12px;color:#666}
.form-input{padding:8px 12px;border:1px solid #ddd;border-radius:6px;font-size:14px;outline:none;transition:border-color 0.2s}
.form-input:focus{border-color:#e74c3c}
.calculator-result{margin-top:12px;padding:12px;background:#fff;border-radius:8px;display:flex;align-items:center;justify-between}
.result-label{font-size:14px;color:#666}
.result-value{font-size:20px;font-weight:700;color:#e74c3c}
.result-value::before{content:'${this.getCurrencySymbol(currency)}';font-size:14px}
/* 正品溯源 */
.authenticity-section{padding:16px;background:#fff;margin-top:12px;border-radius:12px}
.section-title{font-size:16px;font-weight:600;color:#333;margin-bottom:12px;display:flex;align-items:center;gap:8px}
.traceability-code{display:flex;align-items:center;gap:12px;padding:12px;background:#f8f9fa;border-radius:8px}
.code-display{font-family:monospace;font-size:16px;color:#333;letter-spacing:2px}
.verify-btn{padding:8px 16px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;border-radius:6px;font-size:14px;cursor:pointer}
.certification-list{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.cert-item{display:flex;align-items:center;gap:4px;padding:6px 10px;background:#e8f5e9;border-radius:4px;font-size:11px;color:#2e7d32}
/* 响应式适配 */
@media (max-width: 375px){
.sale-price{font-size:26px}
.tool-grid{grid-template-columns:repeat(2,1fr)}
.calculator-form{grid-template-columns:1fr}
.crossborder-badges{flex-direction:column}
}
`;
}
getCurrencySymbol(currency) {
const symbols = {
'CNY': '¥',
'USD': '$',
'EUR': '€',
'GBP': '£',
'JPY': '¥',
'KRW': '₩',
'AUD': 'A$',
'CAD': 'C$',
'SGD': 'S$',
'HKD': 'HK$'
};
return symbols[currency] || currency;
}
}
export const kaolaCriticalCSS = new KaolaCriticalCSS();
三、跨境税费计算引擎
3.1 高性能跨境税费计算引擎
javascript
// engines/crossborderTaxEngine.js
// 网易考拉跨境税费计算引擎 - 高性能版本
class CrossborderTaxEngine {
constructor(config) {
this.config = config;
this.cache = new Map();
this.calculationQueue = [];
this.batchSize = 50;
this.flushInterval = 100; // 100ms批量处理
this.rateUpdateInterval = 3600000; // 1小时更新汇率
}
// 初始化计算引擎
initialize() {
// 定时刷新汇率
setInterval(() => {
this.refreshExchangeRates();
}, this.rateUpdateInterval);
// 定时刷新关税税率
setInterval(() => {
this.refreshDutyRates();
}, this.rateUpdateInterval * 2);
// 批量处理队列
setInterval(() => {
this.processBatch();
}, this.flushInterval);
// 清理过期缓存
setInterval(() => {
this.cleanExpiredCache();
}, 300000); // 每5分钟清理
}
// 计算跨境税费(高性能版本)
async calculateTax(params) {
const {
productId,
basePrice,
baseCurrency,
targetCurrency,
quantity = 1,
country,
productCategory,
customerType = 'individual',
sessionId = this.generateSessionId()
} = params;
// 构建缓存键
const cacheKey = this.buildTaxCacheKey(params);
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
// 检查缓存
const cached = this.cache.get(cacheKey);
if (cached && !this.isCacheExpired(cached)) {
return cached.result;
}
// 添加到计算队列
return new Promise((resolve, reject) => {
this.calculationQueue.push({
params,
cacheKey,
resolve,
reject,
timestamp: Date.now(),
sessionId
});
// 队列过长时触发紧急处理
if (this.calculationQueue.length >= this.batchSize * 2) {
this.processBatch(true);
}
});
}
// 批量处理计算任务
async processBatch(urgent = false) {
if (this.calculationQueue.length === 0) return;
const batch = urgent
? this.calculationQueue.splice(0, this.batchSize)
: this.calculationQueue.splice(0, Math.min(this.batchSize, this.calculationQueue.length));
const results = await Promise.allSettled(
batch.map(async ({ params, cacheKey, sessionId }) => {
try {
const result = await this.doCalculateTax(params, sessionId);
return { cacheKey, result, success: true };
} catch (error) {
return { cacheKey, error, success: false };
}
})
);
// 处理结果并更新缓存
results.forEach(({ cacheKey, result, error, success }) => {
if (success) {
this.cache.set(cacheKey, {
result,
timestamp: Date.now(),
ttl: this.getCacheTTL(params.productId, params.country),
sessionId
});
// 通知等待的请求
const queueItem = batch.find(item => item.cacheKey === cacheKey);
if (queueItem) {
queueItem.resolve(result);
}
} else {
const queueItem = batch.find(item => item.cacheKey === cacheKey);
if (queueItem) {
queueItem.reject(error);
}
}
});
}
// 实际税费计算逻辑
async doCalculateTax(params, sessionId) {
const {
productId,
basePrice,
baseCurrency,
targetCurrency,
quantity,
country,
productCategory,
customerType
} = params;
// 并行获取所需数据
const [
exchangeRate,
dutyRate,
vatRate,
taxFreeAllowance,
complianceRules,
productClassification
] = await Promise.all([
this.getExchangeRate(baseCurrency, targetCurrency),
this.getDutyRate(productCategory, country),
this.getVATRate(country),
this.getTaxFreeAllowance(country, customerType),
this.getComplianceRules(productId, country),
this.getClassifyProduct(productId, productCategory)
]);
// 汇率转换
const convertedPrice = basePrice * exchangeRate;
const subtotal = convertedPrice * quantity;
// 税费计算
const dutyCalculation = this.calculateDuty({
subtotal,
dutyRate,
taxFreeAllowance,
productClassification,
complianceRules
});
const vatCalculation = this.calculateVAT({
subtotal,
dutyAmount: dutyCalculation.amount,
vatRate,
complianceRules
});
// 其他费用
const additionalFees = await this.calculateAdditionalFees({
subtotal,
country,
productClassification,
complianceRules
});
// 总费用
const totalTaxAmount = dutyCalculation.amount + vatCalculation.amount + additionalFees.total;
const finalTotalPrice = subtotal + totalTaxAmount;
// 生成税费明细
const taxBreakdown = this.generateTaxBreakdown({
dutyCalculation,
vatCalculation,
additionalFees,
exchangeRate,
baseCurrency,
targetCurrency
});
return {
calculationId: sessionId,
input: {
basePrice,
baseCurrency,
targetCurrency,
quantity,
country,
productCategory,
exchangeRate
},
pricing: {
subtotal: parseFloat(subtotal.toFixed(2)),
dutyAmount: parseFloat(dutyCalculation.amount.toFixed(2)),
vatAmount: parseFloat(vatCalculation.amount.toFixed(2)),
additionalFees: parseFloat(additionalFees.total.toFixed(2)),
totalTaxAmount: parseFloat(totalTaxAmount.toFixed(2)),
finalTotalPrice: parseFloat(finalTotalPrice.toFixed(2))
},
taxBreakdown,
compliance: {
isCompliant: complianceRules.isAllowed,
restrictions: complianceRules.restrictions,
warnings: complianceRules.warnings,
requiredDocuments: complianceRules.requiredDocuments
},
metadata: {
calculatedAt: Date.now(),
currency: targetCurrency,
exchangeRateSource: 'live_api',
dutyRateSource: 'official_customs',
vatRateSource: 'official_tax_authority'
}
};
}
// 税费计算
calculateDuty({ subtotal, dutyRate, taxFreeAllowance, productClassification, complianceRules }) {
// 检查是否符合免税条件
const isDutyFree = this.checkDutyFreeEligibility({
subtotal,
taxFreeAllowance,
productClassification,
complianceRules
});
if (isDutyFree.eligible) {
return {
amount: 0,
rate: 0,
taxableAmount: 0,
isDutyFree: true,
reason: isDutyFree.reason,
allowance: taxFreeAllowance,
savings: subtotal
};
}
// 计算应税金额
let taxableAmount = Math.max(0, subtotal - taxFreeAllowance);
// 应用产品特定减免
if (productClassification.dutyReductions) {
taxableAmount = this.applyDutyReductions(taxableAmount, productClassification.dutyReductions);
}
// 计算关税
let dutyAmount = taxableAmount * dutyRate.rate;
// 应用最低关税
if (dutyRate.minimumAmount && dutyAmount < dutyRate.minimumAmount) {
dutyAmount = dutyRate.minimumAmount;
}
// 应用最高关税上限
if (dutyRate.maximumAmount && dutyAmount > dutyRate.maximumAmount) {
dutyAmount = dutyRate.maximumAmount;
}
return {
amount: parseFloat(dutyAmount.toFixed(2)),
rate: dutyRate.rate,
taxableAmount: parseFloat(taxableAmount.toFixed(2)),
isDutyFree: false,
allowance: taxFreeAllowance,
category: dutyRate.category,
reason: null,
savings: 0
};
}
// VAT计算
calculateVAT({ subtotal, dutyAmount, vatRate, complianceRules }) {
// 检查VAT豁免
if (complianceRules.vatExempt) {
return {
amount: 0,
rate: 0,
taxableAmount: 0,
isVATExempt: true,
reason: complianceRules.vatExemptReason
};
}
// 计算应税基数(含税基包含关税)
const taxableAmount = subtotal + dutyAmount;
const vatAmount = taxableAmount * vatRate.rate;
return {
amount: parseFloat(vatAmount.toFixed(2)),
rate: vatRate.rate,
taxableAmount: parseFloat(taxableAmount.toFixed(2)),
isVATExempt: false,
reason: null
};
}
// 计算附加费用
async calculateAdditionalFees({ subtotal, country, productClassification, complianceRules }) {
const fees = {
handlingFee: 0,
processingFee: 0,
inspectionFee: 0,
storageFee: 0,
documentationFee: 0
};
// 处理费
if (complianceRules.requiresProcessing) {
fees.processingFee = this.calculateProcessingFee(subtotal, country);
}
// 检验费(特定品类)
if (productClassification.requiresInspection) {
fees.inspectionFee = this.calculateInspectionFee(productClassification.category, country);
}
// 仓储费(特殊商品)
if (productClassification.storageRequired) {
fees.storageFee = this.calculateStorageFee(subtotal, country);
}
// 单证费
if (complianceRules.requiresDocumentation) {
fees.documentationFee = this.calculateDocumentationFee(complianceRules.requiredDocuments);
}
const total = Object.values(fees).reduce((sum, fee) => sum + fee, 0);
return {
breakdown: fees,
total: parseFloat(total.toFixed(2))
};
}
// 生成税费明细
generateTaxBreakdown({ dutyCalculation, vatCalculation, additionalFees, exchangeRate, baseCurrency, targetCurrency }) {
return {
summary: {
totalTaxAmount: dutyCalculation.amount + vatCalculation.amount + additionalFees.total,
dutyPercentage: dutyCalculation.isDutyFree ? 0 : (dutyCalculation.amount / (dutyCalculation.amount + vatCalculation.amount + additionalFees.total) * 100),
vatPercentage: vatCalculation.isVATExempt ? 0 : (vatCalculation.amount / (dutyCalculation.amount + vatCalculation.amount + additionalFees.total) * 100),
feesPercentage: additionalFees.total > 0 ? (additionalFees.total / (dutyCalculation.amount + vatCalculation.amount + additionalFees.total) * 100) : 0
},
details: {
duty: {
description: dutyCalculation.isDutyFree ? '免税商品' : '进口关税',
rate: dutyCalculation.rate,
taxableAmount: dutyCalculation.taxableAmount,
amount: dutyCalculation.amount,
currency: targetCurrency,
reason: dutyCalculation.reason
},
vat: {
description: vatCalculation.isVATExempt ? 'VAT豁免' : '增值税',
rate: vatCalculation.rate,
taxableAmount: vatCalculation.taxableAmount,
amount: vatCalculation.amount,
currency: targetCurrency,
reason: vatCalculation.reason
},
additionalFees: {
description: '附加费用',
breakdown: additionalFees.breakdown,
total: additionalFees.total,
currency: targetCurrency
}
},
exchangeRate: {
rate: exchangeRate,
from: baseCurrency,
to: targetCurrency,
source: 'live_market_data',
updatedAt: Date.now()
}
};
}
// 检查免税资格
checkDutyFreeEligibility({ subtotal, taxFreeAllowance, productClassification, complianceRules }) {
// 个人物品免税额度检查
if (subtotal <= taxFreeAllowance.personal) {
return {
eligible: true,
reason: `个人物品免税额度内(${taxFreeAllowance.personal} ${taxFreeAllowance.currency})`
};
}
// 特定品类免税检查
if (productClassification.dutyFreeCategories) {
const isInDutyFreeCategory = productClassification.dutyFreeCategories.some(
cat => cat === productClassification.category
);
if (isInDutyFreeCategory) {
return {
eligible: true,
reason: '该品类享受免税政策'
};
}
}
// 贸易协定免税检查
if (complianceRules.tradeAgreements) {
const hasApplicableAgreement = complianceRules.tradeAgreements.some(
agreement => agreement.appliesTo === productClassification.category
);
if (hasApplicableAgreement) {
return {
eligible: true,
reason: `适用${complianceRules.tradeAgreements[0].name}贸易协定免税`
};
}
}
return {
eligible: false,
reason: null
};
}
// 获取汇率
async getExchangeRate(fromCurrency, toCurrency) {
const cacheKey = `exchange_rate_${fromCurrency}_${toCurrency}`;
const cached = this.cache.get(cacheKey);
if (cached && !this.isCacheExpired(cached, 300)) { // 汇率缓存5分钟
return cached.result;
}
try {
// 调用汇率API
const response = await fetch(`https://api.exchangerate-api.com/v4/latest/${fromCurrency}`);
const data = await response.json();
const rate = data.rates[toCurrency];
// 缓存汇率
this.cache.set(cacheKey, {
result: rate,
timestamp: Date.now(),
ttl: 300
});
return rate;
} catch (error) {
console.error('Failed to fetch exchange rate:', error);
// 返回缓存的旧汇率或默认值
return cached?.result || 1;
}
}
// 获取关税税率
async getDutyRate(category, country) {
const cacheKey = `duty_rate_${category}_${country}`;
const cached = this.cache.get(cacheKey);
if (cached && !this.isCacheExpired(cached, 3600)) { // 关税税率缓存1小时
return cached.result;
}
try {
// 调用关税税率API
const response = await fetch(`https://api.customs.gov/${country}/duty-rates/${category}`);
const data = await response.json();
const dutyRate = {
rate: data.rate,
category: data.category,
minimumAmount: data.minimumAmount,
maximumAmount: data.maximumAmount,
effectiveDate: data.effectiveDate,
source: 'official_customs'
};
// 缓存关税税率
this.cache.set(cacheKey, {
result: dutyRate,
timestamp: Date.now(),
ttl: 3600
});
return dutyRate;
} catch (error) {
console.error('Failed to fetch duty rate:', error);
// 返回默认税率
return {
rate: 0.1, // 默认10%
category: category,
minimumAmount: 0,
maximumAmount: null,
effectiveDate: new Date().toISOString(),
source: 'default'
};
}
}
// 获取VAT税率
async getVATRate(country) {
const cacheKey = `vat_rate_${country}`;
const cached = this.cache.get(cacheKey);
if (cached && !this.isCacheExpired(cached, 7200)) { // VAT税率缓存2小时
return cached.result;
}
try {
// 调用VAT税率API
const response = await fetch(`https://api.taxauthority.gov/${country}/vat-rate`);
const data = await response.json();
const vatRate = {
rate: data.rate,
reducedRate: data.reducedRate,
superReducedRate: data.superReducedRate,
effectiveDate: data.effectiveDate,
source: 'official_tax_authority'
};
// 缓存VAT税率
this.cache.set(cacheKey, {
result: vatRate,
timestamp: Date.now(),
ttl: 7200
});
return vatRate;
} catch (error) {
console.error('Failed to fetch VAT rate:', error);
// 返回默认VAT税率
return {
rate: 0.2, // 默认20%
reducedRate: 0.1,
superReducedRate: 0.05,
effectiveDate: new Date().toISOString(),
source: 'default'
};
}
}
// 获取免税额度
async getTaxFreeAllowance(country, customerType) {
const cacheKey = `tax_free_allowance_${country}_${customerType}`;
const cached = this.cache.get(cacheKey);
if (cached && !this.isCacheExpired(cached, 86400)) { // 免税额度缓存24小时
return cached.result;
}
try {
// 调用免税额度API
const response = await fetch(`https://api.customs.gov/${country}/tax-free-allowance/${customerType}`);
const data = await response.json();
const allowance = {
personal: data.personalAllowance,
currency: data.currency,
familyAllowance: data.familyAllowance,
frequentTravelerAllowance: data.frequentTravelerAllowance,
effectiveDate: data.effectiveDate,
source: 'official_customs'
};
// 缓存免税额度
this.cache.set(cacheKey, {
result: allowance,
timestamp: Date.now(),
ttl: 86400
});
return allowance;
} catch (error) {
console.error('Failed to fetch tax free allowance:', error);
// 返回默认免税额度
return {
personal: 5000, // 默认5000
currency: 'CNY',
familyAllowance: 10000,
frequentTravelerAllowance: 15000,
effectiveDate: new Date().toISOString(),
source: 'default'
};
}
}
// 获取合规规则
async getComplianceRules(productId, country) {
const cacheKey = `compliance_rules_${productId}_${country}`;
const cached = this.cache.get(cacheKey);
if (cached && !this.isCacheExpired(cached, 3600)) {
return cached.result;
}
try {
// 调用合规规则API
const response = await fetch(`https://api.compliance.kaola.com/rules/${productId}/${country}`);
const data = await response.json();
const rules = {
isAllowed: data.isAllowed,
restrictions: data.restrictions || [],
warnings: data.warnings || [],
requiredDocuments: data.requiredDocuments || [],
vatExempt: data.vatExempt || false,
vatExemptReason: data.vatExemptReason,
requiresProcessing: data.requiresProcessing || false,
requiresInspection: data.requiresInspection || false,
requiresDocumentation: data.requiresDocumentation || false,
tradeAgreements: data.tradeAgreements || [],
prohibitions: data.prohibitions || []
};
// 缓存合规规则
this.cache.set(cacheKey, {
result: rules,
timestamp: Date.now(),
ttl: 3600
});
return rules;
} catch (error) {
console.error('Failed to fetch compliance rules:', error);
// 返回默认合规规则
return {
isAllowed: true,
restrictions: [],
warnings: [],
requiredDocuments: [],
vatExempt: false,
vatExemptReason: null,
requiresProcessing: false,
requiresInspection: false,
requiresDocumentation: false,
tradeAgreements: [],
prohibitions: []
};
}
}
// 产品分类
async getClassifyProduct(productId, providedCategory) {
const cacheKey = `product_classification_${productId}`;
const cached = this.cache.get(cacheKey);
if (cached && !this.isCacheExpired(cached, 86400)) {
return cached.result;
}
try {
// 调用产品分类API
const response = await fetch(`https://api.classification.kaola.com/classify/${productId}`);
const data = await response.json();
const classification = {
category: data.category || providedCategory,
hsCode: data.hsCode,
dutyReductions: data.dutyReductions || [],
requiresInspection: data.requiresInspection || false,
storageRequired: data.storageRequired || false,
restrictedCountries: data.restrictedCountries || [],
ageRestriction: data.ageRestriction,
prescriptionRequired: data.prescriptionRequired || false
};
// 缓存产品分类
this.cache.set(cacheKey, {
result: classification,
timestamp: Date.now(),
ttl: 86400
});
return classification;
} catch (error) {
console.error('Failed to classify product:', error);
// 返回默认分类
return {
category: providedCategory || 'general',
hsCode: null,
dutyReductions: [],
requiresInspection: false,
storageRequired: false,
restrictedCountries: [],
ageRestriction: null,
prescriptionRequired: false
};
}
}
// 辅助计算方法
calculateProcessingFee(subtotal, country) {
const feeStructure = {
'CN': subtotal * 0.02, // 中国:2%
'US': Math.max(5, subtotal * 0.015), // 美国:最低5美元
'EU': Math.max(3, subtotal * 0.01), // 欧盟:最低3欧元
'JP': Math.max(500, subtotal * 0.01), // 日本:最低500日元
'KR': Math.max(3000, subtotal * 0.015), // 韩国:最低3000韩元
'DEFAULT': subtotal * 0.02
};
return feeStructure[country] || feeStructure['DEFAULT'];
}
calculateInspectionFee(category, country) {
const inspectionRates = {
'cosmetics': { 'CN': 50, 'US': 35, 'EU': 40, 'JP': 5000, 'KR': 35000 },
'food': { 'CN': 80, 'US': 55, 'EU': 60, 'JP': 8000, 'KR': 55000 },
'electronics': { 'CN': 30, 'US': 25, 'EU': 30, 'JP': 3000, 'KR': 25000 },
'clothing': { 'CN': 20, 'US': 15, 'EU': 20, 'JP': 2000, 'KR': 15000 },
'DEFAULT': { 'CN': 40, 'US': 30, 'EU': 35, 'JP': 4000, 'KR': 30000 }
};
const categoryRates = inspectionRates[category] || inspectionRates['DEFAULT'];
return categoryRates[country] || categoryRates['CN'];
}
calculateStorageFee(subtotal, country) {
const storageRates = {
'CN': subtotal * 0.005, // 中国:0.5%/天
'US': subtotal * 0.008, // 美国:0.8%/天
'EU': subtotal * 0.006, // 欧盟:0.6%/天
'JP': subtotal * 0.004, // 日本:0.4%/天
'KR': subtotal * 0.007, // 韩国:0.7%/天
'DEFAULT': subtotal * 0.006
};
return storageRates[country] || storageRates['DEFAULT'];
}
calculateDocumentationFee(documents) {
const baseFee = 10; // 基础单证费
const perDocFee = 5; // 每份额外单证费
return baseFee + (documents.length * perDocFee);
}
applyDutyReductions(taxableAmount, reductions) {
let reducedAmount = taxableAmount;
reductions.forEach(reduction => {
if (reduction.type === 'percentage') {
reducedAmount *= (1 - reduction.value);
} else if (reduction.type === 'fixed') {
reducedAmount -= reduction.value;
}
});
return Math.max(0, reducedAmount);
}
// 缓存管理
buildTaxCacheKey(params) {
const { productId, basePrice, baseCurrency, targetCurrency, quantity, country, productCategory } = params;
const roundedPrice = Math.floor(basePrice * 100) / 100;
return `tax_${productId}_${roundedPrice}_${baseCurrency}_${targetCurrency}_${quantity}_${country}_${productCategory}`;
}
getCacheTTL(productId, country) {
// 热门商品缓存时间短,冷门商品缓存时间长
// 不同国家根据政策更新频率调整
const countryMultipliers = {
'CN': 1, // 中国政策相对稳定
'US': 1.5, // 美国
'EU': 2, // 欧盟政策变化较频繁
'JP': 1.5, // 日本
'KR': 1.5, // 韩国
'OTHER': 3 // 其他地区
};
const hotProducts = this.config.hotProducts || new Set();
const baseTTL = hotProducts.has(productId) ? 60 : 300; // 1分钟或5分钟
return Math.round(baseTTL * (countryMultipliers[country] || countryMultipliers['OTHER']));
}
isCacheExpired(cached, customTTL = null) {
const ttl = customTTL || cached.ttl;
return Date.now() - cached.timestamp > ttl * 1000;
}
cleanExpiredCache() {
const now = Date.now();
for (const [key, value] of this.cache.entries()) {
if (now - value.timestamp > value.ttl * 1000) {
this.cache.delete(key);
}
}
}
generateSessionId() {
return `tax_session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 批量税费预计算(用于热门商品)
async precomputeTaxes(productList, globalConfig) {
const precomputed = new Map();
await Promise.all(
productList.map(async (product) => {
try {
const tax = await this.calculateTax({
productId: product.id,
basePrice: product.basePrice,
baseCurrency: product.currency,
targetCurrency: globalConfig.currency,
quantity: 1,
country: globalConfig.country,
productCategory: product.category
});
precomputed.set(product.id, tax);
} catch (error) {
console.error(`Failed to precompute tax for product ${product.id}:`, error);
}
})
);
return precomputed;
}
// 刷新汇率
async refreshExchangeRates() {
// 清除所有汇率缓存
for (const [key, value] of this.cache.entries()) {
if (key.startsWith('exchange_rate_')) {
this.cache.delete(key);
}
}
console.log('Exchange rates cache cleared for refresh');
}
// 刷新关税税率
async refreshDutyRates() {
// 清除所有关税税率缓存
for (const [key, value] of this.cache.entries()) {
if (key.startsWith('duty_rate_')) {
this.cache.delete(key);
}
}
console.log('Duty rates cache cleared for refresh');
}
}
// 创建单例
let taxEngineInstance = null;
export function getCrossborderTaxEngine(config) {
if (!taxEngineInstance) {
taxEngineInstance = new CrossborderTaxEngine(config);
taxEngineInstance.initialize();
}
return taxEngineInstance;
}
3.2 实时汇率与政策更新
javascript
// services/policyUpdateService.js
// 网易考拉政策更新服务 - 实时同步
class PolicyUpdateService {
constructor() {
this.subscriptions = new Map();
this.policyCache = new Map();
this.updateChannels = new Map();
this.websocketConnections = new Map();
}
// 初始化服务
initialize() {
// 建立政策更新WebSocket连接
this.initPolicyWebSocket();
// 定时检查政策更新
this.startPolicyCheck();
// 初始化各国家/地区的政策订阅
this.initPolicySubscriptions();
}
// 初始化政策WebSocket
initPolicyWebSocket() {
// 连接政策更新服务
const ws = new WebSocket('wss://policy-updates.kaola.com/stream');
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
ws.onopen = () => {
console.log('Policy update WebSocket connected');
this.subscribeToAllPolicies(ws);
};
ws.onmessage = (event) => {
this.handlePolicyUpdate(JSON.parse(event.data));
};
ws.onerror = (error) => {
console.error('Policy WebSocket error:', error);
// 降级到轮询
this.startPolicyPolling();
};
ws.onclose = () => {
console.log('Policy WebSocket closed, reconnecting...');
setTimeout(() => this.initPolicyWebSocket(), 5000);
};
this.websocketConnections.set('policy', ws);
}
// 订阅所有政策更新
subscribeToAllPolicies(ws) {
const countries = ['CN', 'US', 'EU', 'JP', 'KR', 'AU', 'SG', 'MY', 'TH', 'VN'];
const policyTypes = ['duty_rates', 'vat_rates', 'tax_free_allowances', 'compliance_rules', 'trade_agreements'];
const subscription = {
type: 'subscribe',
countries,
policyTypes,
clientId: this.generateClientId()
};
ws.send(JSON.stringify(subscription));
}
// 处理政策更新
handlePolicyUpdate(update) {
const { type, country, policyType, data, effectiveDate, source } = update;
// 更新缓存
const cacheKey = `policy_${country}_${policyType}`;
this.policyCache.set(cacheKey, {
data,
effectiveDate,
source,
updatedAt: Date.now()
});
// 通知订阅者
this.notifyPolicySubscribers(country, policyType, data);
// 触发相关服务更新
this.triggerServiceUpdates(country, policyType, data);
// 记录政策变更日志
this.logPolicyChange(country, policyType, data, effectiveDate);
}
// 通知政策订阅者
notifyPolicySubscribers(country, policyType, data) {
const subscribers = this.subscriptions.get(`${country}_${policyType}`) || [];
const globalSubscribers = this.subscriptions.get(`global_${policyType}`) || [];
[...subscribers, ...globalSubscribers].forEach(subscriber => {
try {
subscriber.callback({
country,
policyType,
data,
timestamp: Date.now()
});
} catch (error) {
console.error('Error notifying policy subscriber:', error);
}
});
}
// 触发服务更新
triggerServiceUpdates(country, policyType, data) {
// 通知税费计算引擎
if (policyType === 'duty_rates' || policyType === 'vat_rates' || policyType === 'tax_free_allowances') {
this.notifyTaxEngine(country, policyType, data);
}
// 通知合规检查服务
if (policyType === 'compliance_rules' || policyType === 'trade_agreements') {
this.notifyComplianceService(country, policyType, data);
}
// 通知产品分类服务
if (policyType === 'restricted_products' || policyType === 'prohibited_items') {
this.notifyClassificationService(country, policyType, data);
}
}
// 通知税费计算引擎
notifyTaxEngine(country, policyType, data) {
// 发送内部消息或调用API
if (window.taxEngine) {
window.taxEngine.clearCacheForCountry(country);
}
}
// 通知合规检查服务
notifyComplianceService(country, policyType, data) {
// 发送内部消息或调用API
if (window.complianceService) {
window.complianceService.clearCacheForCountry(country);
}
}
// 通知产品分类服务
notifyClassificationService(country, policyType, data) {
// 发送内部消息或调用API
if (window.classificationService) {
window.classificationService.clearCacheForCountry(country);
}
}
// 记录政策变更日志
logPolicyChange(country, policyType, data, effectiveDate) {
const logEntry = {
country,
policyType,
data,
effectiveDate,
changedAt: new Date().toISOString(),
source: 'policy_update_service'
};
// 发送到日志系统
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/policy-changes', JSON.stringify(logEntry));
} else {
fetch('/api/policy-changes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
}).catch(() => {});
}
}
// 订阅政策更新
subscribe(country, policyType, callback) {
const subscriptionKey = `${country}_${policyType}`;
if (!this.subscriptions.has(subscriptionKey)) {
this.subscriptions.set(subscriptionKey, []);
}
const subscriptionId = `${subscriptionKey}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.subscriptions.get(subscriptionKey).push({
id: subscriptionId,
callback,
subscribedAt: Date.now()
});
// 立即返回当前政策数据
const currentData = this.policyCache.get(`policy_${country}_${policyType}`);
if (currentData) {
callback({
country,
policyType,
data: currentData.data,
timestamp: currentData.updatedAt
});
}
return subscriptionId;
}
// 取消订阅
unsubscribe(subscriptionId) {
for (const [key, subscribers] of this.subscriptions.entries()) {
const index = subscribers.findIndex(s => s.id === subscriptionId);
if (index !== -1) {
subscribers.splice(index, 1);
if (subscribers.length === 0) {
this.subscriptions.delete(key);
}
return true;
}
}
return false;
}
// 获取当前政策
getCurrentPolicy(country, policyType) {
return this.policyCache.get(`policy_${country}_${policyType}`);
}
// 启动政策检查(轮询备用方案)
startPolicyCheck() {
setInterval(() => {
this.checkForPolicyUpdates();
}, 300000); // 每5分钟检查一次
}
// 检查政策更新
async checkForPolicyUpdates() {
try {
const response = await fetch('/api/policy-updates/check');
const updates = await response.json();
updates.forEach(update => {
this.handlePolicyUpdate(update);
});
} catch (error) {
console.error('Failed to check for policy updates:', error);
}
}
// 启动政策轮询(WebSocket失败时的备用方案)
startPolicyPolling() {
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
}
this.pollingTimer = setInterval(() => {
this.fetchPolicyUpdates();
}, 60000); // 每1分钟轮询
}
// 获取政策更新
async fetchPolicyUpdates() {
try {
const response = await fetch('/api/policy-updates/since/' + this.lastUpdateTimestamp);
const updates = await response.json();
if (updates.length > 0) {
this.lastUpdateTimestamp = Math.max(...updates.map(u => u.updatedAt));
updates.forEach(update => {
this.handlePolicyUpdate(update);
});
}
} catch (error) {
console.error('Failed to fetch policy updates:', error);
}
}
// 初始化政策订阅
initPolicySubscriptions() {
// 为各国家/地区初始化政策订阅
const countries = ['CN', 'US', 'EU', 'JP', 'KR', 'AU', 'SG'];
const policyTypes = ['duty_rates', 'vat_rates', 'tax_free_allowances'];
countries.forEach(country => {
policyTypes.forEach(policyType => {
this.subscribe(country, policyType, (update) => {
console.log(`Policy updated for ${country} - ${policyType}:`, update.data);
});
});
});
}
// 生成客户端ID
generateClientId() {
return `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 清除指定国家的缓存
clearCacheForCountry(country) {
const keysToDelete = [];
for (const [key, value] of this.policyCache.entries()) {
if (key.includes(`_${country}_`)) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => this.policyCache.delete(key));
}
}
export const policyUpdateService = new PolicyUpdateService();
四、多语言多币种优化
4.1 智能国际化引擎
javascript
// i18n/intelligentI18n.js
// 网易考拉智能国际化引擎
class IntelligentI18n {
constructor() {
this.currentLocale = 'zh-CN';
this.currentCurrency = 'CNY';
this.translations = new Map();
this.currencyFormats = new Map();
this.rtlLanguages = new Set(['ar', 'he', 'fa', 'ur']);
this.loadedNamespaces = new Set();
this.fallbackChain = ['zh-CN', 'en-US', 'ja-JP'];
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
this.init();
}
// 初始化
async init() {
// 检测用户语言和货币偏好
await this.detectUserPreferences();
// 加载初始翻译
await this.loadTranslations(this.currentLocale, ['common', 'product', 'checkout']);
// 初始化货币格式化
this.initCurrencyFormats();
// 监听语言切换事件
this.setupLanguageListeners();
}
// 检测用户偏好
async detectUserPreferences() {
// 从URL参数获取
const urlParams = new URLSearchParams(window.location.search);
const langParam = urlParams.get('lang');
const currencyParam = urlParams.get('currency');
// 从Cookie获取
const cookieLang = this.getCookie('kaola_lang');
const cookieCurrency = this.getCookie('kaola_currency');
// 从浏览器设置获取
const browserLang = navigator.language || navigator.userLanguage;
const browserCurrency = this.detectBrowserCurrency();
// 从地理位置获取
const geoCurrency = await this.detectGeoCurrency();
// 优先级:URL参数 > Cookie > 地理位置 > 浏览器设置 > 默认值
this.currentLocale = langParam || cookieLang || this.normalizeLocale(browserLang) || 'zh-CN';
this.currentCurrency = currencyParam || cookieCurrency || geoCurrency || 'CNY';
// 保存到Cookie
this.setCookie('kaola_lang', this.currentLocale, 365);
this.setCookie('kaola_currency', this.currentCurrency, 365);
}
// 标准化语言代码
normalizeLocale(locale) {
const localeMap = {
'zh': 'zh-CN',
'zh-TW': 'zh-TW',
'zh-HK': 'zh-TW',
'en': 'en-US',
'ja': 'ja-JP',
'ko': 'ko-KR',
'de': 'de-DE',
'fr': 'fr-FR',
'es': 'es-ES',
'it': 'it-IT',
'pt': 'pt-BR',
'ru': 'ru-RU',
'ar': 'ar-SA',
'th': 'th-TH',
'vi': 'vi-VN',
'id': 'id-ID'
};
const normalized = locale.toLowerCase().replace('_', '-');
// 精确匹配
if (localeMap[normalized]) {
return localeMap[normalized];
}
// 前缀匹配
for (const [key, value] of Object.entries(localeMap)) {
if (normalized.startsWith(key)) {
return value;
}
}
return null;
}
// 检测浏览器货币偏好
detectBrowserCurrency() {
// 基于语言推断货币
const languageCurrencyMap = {
'zh': 'CNY',
'en-US': 'USD',
'en-GB': 'GBP',
'ja': 'JPY',
'ko': 'KRW',
'de': 'EUR',
'fr': 'EUR',
'es': 'EUR',
'it': 'EUR',
'pt': 'BRL',
'ru': 'RUB',
'ar': 'SAR',
'th': 'THB',
'vi': 'VND',
'id': 'IDR'
};
const browserLang = (navigator.language || 'en-US').toLowerCase();
for (const [lang, currency] of Object.entries(languageCurrencyMap)) {
if (browserLang.startsWith(lang.toLowerCase())) {
return currency;
}
}
return 'USD'; // 默认美元
}
// 检测地理位置货币
async detectGeoCurrency() {
try {
// 使用免费IP地理定位API
const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
const countryCurrencyMap = {
'CN': 'CNY',
'US': 'USD',
'JP': 'JPY',
'KR': 'KRW',
'DE': 'EUR',
'FR': 'EUR',
'GB': 'GBP',
'AU': 'AUD',
'CA': 'CAD',
'SG': 'SGD',
'HK': 'HKD',
'TW': 'TWD',
'TH': 'THB',
'VN': 'VND',
'ID': 'IDR',
'MY': 'MYR',
'PH': 'PHP',
'IN': 'INR',
'BR': 'BRL',
'MX': 'MXN',
'RU': 'RUB',
'AE': 'AED',
'SA': 'SAR'
};
return countryCurrencyMap[data.country_code] || 'USD';
} catch (error) {
console.error('Failed to detect geo currency:', error);
return null;
}
}
// 加载翻译文件
async loadTranslations(locale, namespaces = ['common']) {
const loadPromises = namespaces.map(namespace => {
if (this.loadedNamespaces.has(`${locale}_${namespace}`)) {
return Promise.resolve();
}
return this.fetchTranslation(locale, namespace).then(translations => {
if (!this.translations.has(locale)) {
this.translations.set(locale, {});
}
this.translations.get(locale)[namespace] = translations;
this.loadedNamespaces.add(`${locale}_${namespace}`);
});
});
await Promise.all(loadPromises);
}
// 获取翻译文件
async fetchTranslation(locale, namespace) {
try {
// 尝试从CDN加载
const response = await fetch(`https://global-cdn.kaola.com/i18n/${locale}/${namespace}.json`);
if (response.ok) {
return await response.json();
}
throw new Error(`Translation file not found: ${locale}/${namespace}`);
} catch (error) {
console.warn(`Failed to load translation for ${locale}/${namespace}:`, error);
// 尝试加载回退语言的翻译
if (this.fallbackChain.includes(locale)) {
const fallbackIndex = this.fallbackChain.indexOf(locale);
if (fallbackIndex < this.fallbackChain.length - 1) {
const fallbackLocale = this.fallbackChain[fallbackIndex + 1];
console.log(`Trying fallback locale: ${fallbackLocale}`);
return this.fetchTranslation(fallbackLocale, namespace);
}
}
return {}; // 返回空对象
}
}
// 初始化货币格式化
initCurrencyFormats() {
const currencyFormatConfigs = {
'CNY': { style: 'currency', currency: 'CNY', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'USD': { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'EUR': { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'GBP': { style: 'currency', currency: 'GBP', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'JPY': { style: 'currency', currency: 'JPY', minimumFractionDigits: 0, maximumFractionDigits: 0 },
'KRW': { style: 'currency', currency: 'KRW', minimumFractionDigits: 0, maximumFractionDigits: 0 },
'AUD': { style: 'currency', currency: 'AUD', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'CAD': { style: 'currency', currency: 'CAD', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'CHF': { style: 'currency', currency: 'CHF', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'HKD': { style: 'currency', currency: 'HKD', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'SGD': { style: 'currency', currency: 'SGD', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'SEK': { style: 'currency', currency: 'SEK', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'NOK': { style: 'currency', currency: 'NOK', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'DKK': { style: 'currency', currency: 'DKK', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'NZD': { style: 'currency', currency: 'NZD', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'MXN': { style: 'currency', currency: 'MXN', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'BRL': { style: 'currency', currency: 'BRL', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'RUB': { style: 'currency', currency: 'RUB', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'INR': { style: 'currency', currency: 'INR', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'THB': { style: 'currency', currency: 'THB', minimumFractionDigits: 2, maximumFractionDigits: 2 },
'PHP': { style: 'currency', currency: 'PHP', minimumFractionDigits: 2, maximumFractionDigits: 2 }
};
for (const [currency, config] of Object.entries(currencyFormatConfigs)) {
this.currencyFormats.set(currency, new Intl.NumberFormat(this.getLocaleForCurrency(currency), config));
}
}
// 获取货币的默认语言环境
getLocaleForCurrency(currency) {
const currencyLocaleMap = {
'CNY': 'zh-CN',
'USD': 'en-US',
'EUR': 'de-DE',
'GBP': 'en-GB',
'JPY': 'ja-JP',
'KRW': 'ko-KR',
'AUD': 'en-AU',
'CAD': 'en-CA',
'CHF': 'de-CH',
'HKD': 'zh-HK',
'SGD': 'en-SG',
'SEK': 'sv-SE',
'NOK': 'nb-NO',
'DKK': 'da-DK',
'NZD': 'en-NZ',
'MXN': 'es-MX',
'BRL': 'pt-BR',
'RUB': 'ru-RU',
'INR': 'en-IN',
'THB': 'th-TH',
'PHP': 'en-PH'
};
return currencyLocaleMap[currency] || 'en-US';
}
// 翻译函数
t(key, params = {}, locale = this.currentLocale) {
// 支持嵌套键,如 'product.title'
const keys = key.split('.');
let translation = this.translations.get(locale) || {};
for (const k of keys) {
if (translation && typeof translation === 'object' && k in translation) {
translation = translation[k];
} else {
// 尝试回退链
translation = this.findFallbackTranslation(keys, locale);
break;
}
}
// 如果不是字符串,返回键名
if (typeof translation !== 'string') {
return key;
}
// 替换参数
return this.interpolate(translation, params);
}
// 查找回退翻译
findFallbackTranslation(keys, locale) {
const fallbackIndex = this.fallbackChain.indexOf(locale);
for (let i = fallbackIndex + 1; i < this.fallbackChain.length; i++) {
const fallbackLocale = this.fallbackChain[i];
let translation = this.translations.get(fallbackLocale) || {};
for (const k of keys) {
if (translation && typeof translation === 'object' && k in translation) {
translation = translation[k];
} else {
translation = null;
break;
}
}
if (typeof translation === 'string') {
return translation;
}
}
return null;
}
// 插值替换
interpolate(template, params) {
return template.replace(/\{(\w+)\}/g, (match, key) => {
return params[key] !== undefined ? String(params[key]) : match;
});
}
// 格式化货币
formatCurrency(amount, currency = this.currentCurrency, locale = this.currentLocale) {
const formatter = this.currencyFormats.get(currency);
if (formatter) {
return formatter.format(amount);
}
// 回退到简单格式化
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount);
}
// 格式化数字
formatNumber(number, options = {}, locale = this.currentLocale) {
return new Intl.NumberFormat(locale, options).format(number);
}
// 格式化日期
formatDate(date, options = {}, locale = this.currentLocale) {
const defaultOptions = {
year: 'numeric',
month: 'long',
day: 'numeric'
};
return new Intl.DateTimeFormat(locale, { ...defaultOptions, ...options }).format(new Date(date));
}
// 格式化时间
formatTime(date, options = {}, locale = this.currentLocale) {
const defaultOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
return new Intl.DateTimeFormat(locale, { ...defaultOptions, ...options }).format(new Date(date));
}
// 格式化相对时间
formatRelativeTime(date, locale = this.currentLocale) {
const now = new Date();
const diff = now - new Date(date);
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const months = Math.floor(days / 30);
const years = Math.floor(months / 12);
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
if (years > 0) return rtf.format(-years, 'year');
if (months > 0) return rtf.format(-months, 'month');
if (days > 0) return rtf.format(-days, 'day');
if (hours > 0) return rtf.format(-hours, 'hour');
if (minutes > 0) return rtf.format(-minutes, 'minute');
return rtf.format(-seconds, 'second');
}
// 切换语言
async switchLanguage(locale) {
const normalizedLocale = this.normalizeLocale(locale) || 'zh-CN';
if (normalizedLocale === this.currentLocale) {
return;
}
// 显示加载状态
this.showLoadingIndicator();
try {
// 加载新语言的翻译
await this.loadTranslations(normalizedLocale, ['common', 'product', 'checkout', 'shipping']);
// 更新当前语言
this.currentLocale = normalizedLocale;
// 保存到Cookie
this.setCookie('kaola_lang', normalizedLocale, 365);
// 更新HTML lang属性
document.documentElement.lang = normalizedLocale;
// 更新页面方向
this.updateDocumentDirection();
// 触发语言切换事件
this.dispatchLanguageChangeEvent(normalizedLocale);
} finally {
this.hideLoadingIndicator();
}
}
// 切换货币
async switchCurrency(currency) {
if (this.currencyFormats.has(currency)) {
this.currentCurrency = currency;
this.setCookie('kaola_currency', currency, 365);
this.dispatchCurrencyChangeEvent(currency);
}
}
// 更新文档方向
updateDocumentDirection() {
const isRTL = this.rtlLanguages.has(this.currentLocale.split('-')[0]);
document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
}
// 设置语言监听器
setupLanguageListeners() {
// 监听语言切换事件
document.addEventListener('languagechange', (e) => {
this.switchLanguage(e.detail.locale);
});
// 监听货币切换事件
document.addEventListener('currencychange', (e) => {
this.switchCurrency(e.detail.currency);
});
// 监听网络状态变化,离线时切换到缓存的语言
window.addEventListener('online', () => {
this.syncTranslations();
});
}
// 同步翻译(在线时)
async syncTranslations() {
try {
await this.loadTranslations(this.currentLocale, ['common', 'product']);
} catch (error) {
console.warn('Failed to sync translations:', error);
}
}
// 显示加载指示器
showLoadingIndicator() {
const indicator = document.createElement('div');
indicator.id = 'i18n-loading-indicator';
indicator.innerHTML = '<div class="spinner"></div><span>Loading...</span>';
indicator.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255,255,255,0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
`;
document.body.appendChild(indicator);
}
// 隐藏加载指示器
hideLoadingIndicator() {
const indicator = document.getElementById('i18n-loading-indicator');
if (indicator) {
indicator.remove();
}
}
// 派发语言切换事件
dispatchLanguageChangeEvent(locale) {
const event = new CustomEvent('i18n:languageChanged', {
detail: { locale, previousLocale: this.currentLocale }
});
document.dispatchEvent(event);
}
// 派发货币切换事件
dispatchCurrencyChangeEvent(currency) {
const event = new CustomEvent('i18n:currencyChanged', {
detail: { currency, previousCurrency: this.currentCurrency }
});
document.dispatchEvent(event);
}
// Cookie操作
getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/;SameSite=Lax`;
}
// 预加载语言
async preloadLanguages(locales) {
const preloadPromises = locales.map(locale =>
this.loadTranslations(locale, ['common', 'product'])
);
await Promise.all(preloadPromises);
}
// 获取支持的语言列表
getSupportedLanguages() {
return [
{ code: 'zh-CN', name: '简体中文', nativeName: '简体中文' },
{ code: 'zh-TW', name: '繁体中文', nativeName: '繁體中文' },
{ code: 'en-US', name: 'English', nativeName: 'English' },
{ code: 'ja-JP', name: 'Japanese', nativeName: '日本語' },
{ code: 'ko-KR', name: 'Korean', nativeName: '한국어' },
{ code: 'de-DE', name: 'German', nativeName: 'Deutsch' },
{ code: 'fr-FR', name: 'French', nativeName: 'Français' },
{ code: 'es-ES', name: 'Spanish', nativeName: 'Español' },
{ code: 'it-IT', name: 'Italian', nativeName: 'Italiano' },
{ code: 'pt-BR', name: 'Portuguese', nativeName: 'Português' },
{ code: 'ru-RU', name: 'Russian', nativeName: 'Русский' },
{ code: 'ar-SA', name: 'Arabic', nativeName: 'العربية' },
{ code: 'th-TH', name: 'Thai', nativeName: 'ไทย' },
{ code: 'vi-VN', name: 'Vietnamese', nativeName: 'Tiếng Việt' },
{ code: 'id-ID', name: 'Indonesian', nativeName: 'Bahasa Indonesia' }
];
}
// 获取支持的货币列表
getSupportedCurrencies() {
return [
{ code: 'CNY', name: 'Chinese Yuan', symbol: '¥', flag: '🇨🇳' },
{ code: 'USD', name: 'US Dollar', symbol: '$', flag: '🇺🇸' },
{ code: 'EUR', name: 'Euro', symbol: '€', flag: '🇪🇺' },
{ code: 'GBP', name: 'British Pound', symbol: '£', flag: '🇬🇧' },
{ code: 'JPY', name: 'Japanese Yen', symbol: '¥', flag: '🇯🇵' },
{ code: 'KRW', name: 'South Korean Won', symbol: '₩', flag: '🇰🇷' },
{ code: 'AUD', name: 'Australian Dollar', symbol: 'A$', flag: '🇦🇺' },
{ code: 'CAD', name: 'Canadian Dollar', symbol: 'C$', flag: '🇨🇦' },
{ code: 'CHF', name: 'Swiss Franc', symbol: 'Fr', flag: '🇨🇭' },
{ code: 'HKD', name: 'Hong Kong Dollar', symbol: 'HK$', flag: '🇭🇰' },
{ code: 'SGD', name: 'Singapore Dollar', symbol: 'S$', flag: '🇸🇬' },
{ code: 'SEK', name: 'Swedish Krona', symbol: 'kr', flag: '🇸🇪' },
{ code: 'NOK', name: 'Norwegian Krone', symbol: 'kr', flag: '🇳🇴' },
{ code: 'DKK', name: 'Danish Krone', symbol: 'kr', flag: '🇩🇰' },
{ code: 'NZD', name: 'New Zealand Dollar', symbol: 'NZ$', flag: '🇳🇿' },
{ code: 'MXN', name: 'Mexican Peso', symbol: '$', flag: '🇲🇽' },
{ code: 'BRL', name: 'Brazilian Real', symbol: 'R$', flag: '🇧🇷' },
{ code: 'RUB', name: 'Russian Ruble', symbol: '₽', flag: '🇷🇺' },
{ code: 'INR', name: 'Indian Rupee', symbol: '₹', flag: '🇮🇳' },
{ code: 'THB', name: 'Thai Baht', symbol: '฿', flag: '🇹🇭' },
{ code: 'PHP', name: 'Philippine Peso', symbol: '₱', flag: '🇵🇭' }
];
}
}
export const intelligentI18n = new IntelligentI18n();
4.2 智能语言切换优化
javascript
// components/SmartLanguageSwitcher.js
// 网易考拉智能语言切换组件
import { intelligentI18n } from '../i18n/intelligentI18n';
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class SmartLanguageSwitcher extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.currentLocale = intelligentI18n.currentLocale;
this.supportedLanguages = intelligentI18n.getSupportedLanguages();
}
connectedCallback() {
this.render();
this.setupEventListeners();
this.preloadLanguageFlags();
}
render() {
const currentLang = this.supportedLanguages.find(l => l.code === this.currentLocale);
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.language-switcher {
position: relative;
display: inline-block;
}
.switcher-button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
}
.switcher-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5);
}
.switcher-button:active {
transform: translateY(0);
}
.current-flag {
font-size: 20px;
}
.current-lang {
text-transform: uppercase;
}
.dropdown-arrow {
font-size: 10px;
transition: transform 0.3s ease;
}
.language-switcher.open .dropdown-arrow {
transform: rotate(180deg);
}
.dropdown-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
min-width: 200px;
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
overflow: hidden;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.3s ease;
z-index: 1000;
}
.language-switcher.open .dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.search-box {
padding: 12px;
border-bottom: 1px solid #eee;
}
.search-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
}
.search-input:focus {
border-color: #667eea;
}
.language-list {
max-height: 300px;
overflow-y: auto;
}
.language-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
cursor: pointer;
transition: background 0.2s;
}
.language-item:hover {
background: #f8f9fa;
}
.language-item.active {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
}
.language-item.active .language-name {
color: #667eea;
font-weight: 600;
}
.flag {
font-size: 24px;
width: 32px;
text-align: center;
}
.language-info {
flex: 1;
}
.language-name {
font-size: 14px;
color: #333;
margin-bottom: 2px;
}
.native-name {
font-size: 12px;
color: #888;
}
.check-mark {
color: #667eea;
font-size: 16px;
opacity: 0;
transition: opacity 0.2s;
}
.language-item.active .check-mark {
opacity: 1;
}
.loading-spinner {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
color: #888;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #eee;
border-top-color: #667eea;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.popular-badge {
font-size: 10px;
padding: 2px 6px;
background: #ff6b6b;
color: white;
border-radius: 4px;
margin-left: auto;
}
/* 移动端适配 */
@media (max-width: 480px) {
.dropdown-menu {
min-width: 280px;
right: -50px;
}
}
</style>
<div class="language-switcher" id="switcher">
<button class="switcher-button" id="switcherBtn" aria-haspopup="listbox" aria-expanded="false">
<span class="current-flag">${currentLang?.flag || '🌐'}</span>
<span class="current-lang">${currentLang?.code.split('-')[0] || 'EN'}</span>
<span class="dropdown-arrow">▼</span>
</button>
<div class="dropdown-menu" id="dropdown" role="listbox">
<div class="search-box">
<input
type="text"
class="search-input"
placeholder="Search language..."
id="searchInput"
autocomplete="off"
>
</div>
<div class="language-list" id="languageList">
<!-- 语言列表将通过JS动态生成 -->
</div>
</div>
</div>
`;
}
setupEventListeners() {
const switcher = this.shadowRoot.getElementById('switcher');
const switcherBtn = this.shadowRoot.getElementById('switcherBtn');
const dropdown = this.shadowRoot.getElementById('dropdown');
const searchInput = this.shadowRoot.getElementById('searchInput');
const languageList = this.shadowRoot.getElementById('languageList');
// 切换下拉菜单
switcherBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleDropdown();
});
// 点击外部关闭
document.addEventListener('click', (e) => {
if (!switcher.contains(e.target)) {
this.closeDropdown();
}
});
// 搜索功能
searchInput.addEventListener('input', (e) => {
this.filterLanguages(e.target.value);
});
// 键盘导航
searchInput.addEventListener('keydown', (e) => {
this.handleKeyboardNavigation(e);
});
// 语言选择
languageList.addEventListener('click', (e) => {
const languageItem = e.target.closest('.language-item');
if (languageItem) {
const langCode = languageItem.dataset.langCode;
this.switchLanguage(langCode);
}
});
}
toggleDropdown() {
const switcher = this.shadowRoot.getElementById('switcher');
const isOpen = switcher.classList.contains('open');
if (isOpen) {
this.closeDropdown();
} else {
this.openDropdown();
}
}
openDropdown() {
const switcher = this.shadowRoot.getElementById('switcher');
const switcherBtn = this.shadowRoot.getElementById('switcherBtn');
const dropdown = this.shadowRoot.getElementById('dropdown');
switcher.classList.add('open');
switcherBtn.setAttribute('aria-expanded', 'true');
// 生成语言列表
this.renderLanguageList(this.supportedLanguages);
// 聚焦搜索框
setTimeout(() => {
this.shadowRoot.getElementById('searchInput')?.focus();
}, 100);
}
closeDropdown() {
const switcher = this.shadowRoot.getElementById('switcher');
const switcherBtn = this.shadowRoot.getElementById('switcherBtn');
switcher.classList.remove('open');
switcherBtn.setAttribute('aria-expanded', 'false');
}
renderLanguageList(languages) {
const languageList = this.shadowRoot.getElementById('languageList');
if (languages.length === 0) {
languageList.innerHTML = `
<div class="loading-spinner">
<div class="spinner"></div>
</div>
`;
return;
}
const popularLanguages = ['zh-CN', 'en-US', 'ja-JP', 'ko-KR'];
languageList.innerHTML = languages.map(lang => {
const isActive = lang.code === this.currentLocale;
const isPopular = popularLanguages.includes(lang.code);
return `
<div
class="language-item ${isActive ? 'active' : ''}"
data-lang-code="${lang.code}"
role="option"
aria-selected="${isActive}"
>
<span class="flag">${lang.flag}</span>
<div class="language-info">
<div class="language-name">${lang.name}</div>
<div class="native-name">${lang.nativeName}</div>
</div>
${isPopular ? '<span class="popular-badge">Popular</span>' : ''}
<span class="check-mark">✓</span>
</div>
`;
}).join('');
}
filterLanguages(query) {
const filtered = this.supportedLanguages.filter(lang =>
lang.name.toLowerCase().includes(query.toLowerCase()) ||
lang.nativeName.toLowerCase().includes(query.toLowerCase()) ||
lang.code.toLowerCase().includes(query.toLowerCase())
);
this.renderLanguageList(filtered);
}
handleKeyboardNavigation(e) {
const items = this.shadowRoot.querySelectorAll('.language-item');
const activeElement = document.activeElement;
const currentIndex = Array.from(items).indexOf(activeElement.closest('.language-item'));
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
const nextIndex = (currentIndex + 1) % items.length;
items[nextIndex]?.focus();
break;
case 'ArrowUp':
e.preventDefault();
const prevIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1;
items[prevIndex]?.focus();
break;
case 'Enter':
if (activeElement.classList.contains('language-item')) {
const langCode = activeElement.dataset.langCode;
this.switchLanguage(langCode);
}
break;
case 'Escape':
this.closeDropdown();
break;
}
}
async switchLanguage(langCode) {
if (langCode === this.currentLocale) {
this.closeDropdown();
return;
}
// 显示加载状态
const switcherBtn = this.shadowRoot.getElementById('switcherBtn');
const originalContent = switcherBtn.innerHTML;
switcherBtn.innerHTML = `
<div class="spinner" style="width:16px;height:16px;border-width:2px;"></div>
<span>Switching...</span>
`;
switcherBtn.disabled = true;
try {
// 切换语言
await intelligentI18n.switchLanguage(langCode);
this.currentLocale = langCode;
// 更新按钮显示
const newLang = this.supportedLanguages.find(l => l.code === langCode);
switcherBtn.innerHTML = `
<span class="current-flag">${newLang?.flag || '🌐'}</span>
<span class="current-lang">${newLang?.code.split('-')[0] || 'EN'}</span>
<span class="dropdown-arrow">▼</span>
`;
// 触发自定义事件
this.dispatchEvent(new CustomEvent('language-switched', {
detail: { locale: langCode, previousLocale: this.currentLocale }
}));
} catch (error) {
console.error('Failed to switch language:', error);
// 恢复原始状态
switcherBtn.innerHTML = originalContent;
} finally {
switcherBtn.disabled = false;
this.closeDropdown();
}
}
preloadLanguageFlags() {
// 预加载国旗emoji的SVG版本(可选优化)
// 这里可以预加载高分辨率的国家旗帜图标
}
}
// 注册自定义元素
customElements.define('smart-language-switcher', SmartLanguageSwitcher);
export { SmartLanguageSwitcher };
五、全球CDN与资源优化
5.1 全球CDN智能调度
javascript
// cdn/globalCDNOptimizer.js
// 网易考拉全球CDN智能调度器
class GlobalCDNOptimizer {
constructor() {
this.cdnProviders = {
primary: {
name: 'kaola-global-cdn',
regions: {
'CN': 'https://cn-cdn.kaola.com',
'APAC': 'https://apac-cdn.kaola.com',
'EU': 'https://eu-cdn.kaola.com',
'US': 'https://us-cdn.kaola.com',
'SA': 'https://sa-cdn.kaola.com',
'AF': 'https://af-cdn.kaola.com'
}
},
secondary: {
name: 'cloudflare-kaola',
baseUrl: 'https://kaola.cdn.cloudflare.net'
},
tertiary: {
name: 'aws-kaola',
baseUrl: 'https://kaola-cdn.s3.amazonaws.com'
}
};
this.resourceTypes = {
images: { priority: 1, compression: 'aggressive', formats: ['webp', 'avif', 'jpg'] },
videos: { priority: 2, compression: 'moderate', formats: ['mp4', 'webm'] },
scripts: { priority: 1, compression: 'none', formats: ['js'] },
styles: { priority: 1, compression: 'aggressive', formats: ['css'] },
fonts: { priority: 3, compression: 'moderate', formats: ['woff2', 'woff'] },
documents: { priority: 2, compression: 'aggressive', formats: ['pdf', 'txt'] }
};
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
this.performanceMetrics = new Map();
this.healthChecks = new Map();
this.smartRoutingEnabled = true;
}
// 获取最优CDN URL
getOptimalCDNUrl(resourcePath, resourceType, userLocation = null) {
const typeConfig = this.resourceTypes[resourceType] || this.resourceTypes.images;
// 确定用户位置
const location = userLocation || this.detectUserLocation();
// 选择CDN提供商
const provider = this.selectBestProvider(location, resourceType);
// 生成优化的资源URL
const optimizedPath = this.optimizeResourcePath(resourcePath, typeConfig);
// 构建完整URL
const baseUrl = this.getBaseUrl(provider, location, resourceType);
return {
url: `${baseUrl}${optimizedPath}`,
provider: provider.name,
region: location,
format: this.getBestFormat(typeConfig, userLocation),
compression: typeConfig.compression,
cacheTTL: this.getCacheTTL(resourceType, location)
};
}
// 检测用户位置
detectUserLocation() {
// 从Cloudflare头信息获取
const cfCountry = this.getHeader('CF-IPCountry');
const cfRegion = this.getHeader('CF-Region');
if (cfCountry) {
return this.mapCountryToRegion(cfCountry, cfRegion);
}
// 从其他CDN头信息获取
const country = this.getHeader('X-Country') || this.getHeader('X-Geo-Country');
if (country) {
return this.mapCountryToRegion(country);
}
// 从IP地址推断
return this.inferLocationFromIP();
}
// 映射国家到区域
mapCountryToRegion(country, region = null) {
const regionMapping = {
'CN': { region: 'CN', subregion: 'East Asia' },
'HK': { region: 'APAC', subregion: 'East Asia' },
'TW': { region: 'APAC', subregion: 'East Asia' },
'MO': { region: 'APAC', subregion: 'East Asia' },
'JP': { region: 'APAC', subregion: 'East Asia' },
'KR': { region: 'APAC', subregion: 'East Asia' },
'SG': { region: 'APAC', subregion: 'Southeast Asia' },
'MY': { region: 'APAC', subregion: 'Southeast Asia' },
'TH': { region: 'APAC', subregion: 'Southeast Asia' },
'VN': { region: 'APAC', subregion: 'Southeast Asia' },
'ID': { region: 'APAC', subregion: 'Southeast Asia' },
'PH': { region: 'APAC', subregion: 'Southeast Asia' },
'IN': { region: 'APAC', subregion: 'South Asia' },
'US': { region: 'US', subregion: 'North America' },
'CA': { region: 'US', subregion: 'North America' },
'MX': { region: 'US', subregion: 'North America' },
'GB': { region: 'EU', subregion: 'Western Europe' },
'DE': { region: 'EU', subregion: 'Western Europe' },
'FR': { region: 'EU', subregion: 'Western Europe' },
'IT': { region: 'EU', subregion: 'Southern Europe' },
'ES': { region: 'EU', subregion: 'Southern Europe' },
'NL': { region: 'EU', subregion: 'Western Europe' },
'BE': { region: 'EU', subregion: 'Western Europe' },
'SE': { region: 'EU', subregion: 'Northern Europe' },
'NO': { region: 'EU', subregion: 'Northern Europe' },
'DK': { region: 'EU', subregion: 'Northern Europe' },
'FI': { region: 'EU', subregion: 'Northern Europe' },
'PL': { region: 'EU', subregion: 'Eastern Europe' },
'CZ': { region: 'EU', subregion: 'Eastern Europe' },
'AU': { region: 'APAC', subregion: 'Oceania' },
'NZ': { region: 'APAC', subregion: 'Oceania' },
'AE': { region: 'SA', subregion: 'Middle East' },
'SA': { region: 'SA', subregion: 'Middle East' },
'IL': { region: 'SA', subregion: 'Middle East' },
'ZA': { region: 'AF', subregion: 'Southern Africa' },
'NG': { region: 'AF', subregion: 'West Africa' },
'EG': { region: 'AF', subregion: 'North Africa' }
};
const mapping = regionMapping[country] || { region: 'US', subregion: 'Unknown' };
return mapping.region;
}
// 推断IP位置
inferLocationFromIP() {
// 使用免费的IP地理位置API
const ip = this.getHeader('CF-Connecting-IP') || this.getHeader('X-Forwarded-For');
if (!ip || ip === '127.0.0.1') {
return 'US'; // 默认美国
}
// 简单的IP范围映射(实际应用中应使用专业的IP地理定位服务)
const ipRanges = {
'CN': [[1, 126], [175, 191]], // A类和部分B类
'APAC': [[203, 223]], // APNIC分配范围
'EU': [[77, 95], [146, 173]], // RIPE分配范围
'US': [[3, 63], [96, 126]], // ARIN分配范围
'SA': [[189, 190]], // AfriNIC分配范围
'AF': [[41, 43]] // AfriNIC分配范围
};
// 简化处理:返回默认区域
return 'US';
}
// 选择最佳CDN提供商
selectBestProvider(location, resourceType) {
// 获取各提供商的性能分数
const providerScores = this.calculateProviderScores(location, resourceType);
// 选择分数最高的提供商
let bestProvider = this.cdnProviders.primary;
let bestScore = 0;
for (const [providerName, score] of Object.entries(providerScores)) {
if (score > bestScore) {
bestScore = score;
bestProvider = this.cdnProviders[providerName];
}
}
return bestProvider;
}
// 计算提供商分数
calculateProviderScores(location, resourceType) {
const scores = {};
for (const [providerName, provider] of Object.entries(this.cdnProviders)) {
let score = 0;
const metrics = this.performanceMetrics.get(providerName) || {};
const health = this.healthChecks.get(providerName) || { healthy: true, uptime: 100 };
// 健康检查权重
if (!health.healthy) {
score = -1000;
continue;
}
score += health.uptime * 0.3;
// 延迟分数
const latency = metrics.latency?.[location] || 100;
score += Math.max(0, 100 - latency) * 0.4;
// 可用性分数
const availability = metrics.availability?.[location] || 99.9;
score += availability * 0.2;
// 带宽分数
const bandwidth = metrics.bandwidth?.[location] || 1000;
score += Math.min(100, bandwidth / 10) * 0.1;
// 资源类型优化
if (resourceType === 'videos' && providerName === 'cloudflare') {
score += 10; // Cloudflare对视频优化更好
}
if (resourceType === 'images' && providerName === 'primary') {
score += 5; // 自有CDN对图片优化更好
}
scores[providerName] = score;
}
return scores;
}
// 优化资源路径
optimizeResourcePath(resourcePath, typeConfig) {
// 解析路径
const url = new URL(resourcePath, 'http://example.com');
const pathname = url.pathname;
// 添加优化参数
const optimizations = [];
// 图片优化
if (typeConfig === this.resourceTypes.images) {
// 添加质量参数
if (!url.searchParams.has('q')) {
optimizations.push('q=85');
}
// 添加尺寸参数(如果可能)
if (this.canOptimizeDimensions(pathname)) {
const dimensions = this.getOptimalDimensions(pathname);
if (dimensions) {
optimizations.push(`w=${dimensions.width}`);
optimizations.push(`h=${dimensions.height}`);
}
}
}
// 视频优化
if (typeConfig === this.resourceTypes.videos) {
if (!url.searchParams.has('quality')) {
optimizations.push('quality=auto');
}
}
// 添加版本号(用于缓存控制)
if (!url.searchParams.has('v')) {
optimizations.push(`v=${this.getResourceVersion(pathname)}`);
}
// 构建优化后的路径
if (optimizations.length > 0) {
url.searchParams.set('opt', optimizations.join(','));
}
return url.pathname + url.search;
}
// 获取最佳格式
getBestFormat(typeConfig, userLocation) {
const formats = typeConfig.formats;
// 根据用户浏览器支持选择最佳格式
const supportedFormats = this.detectSupportedFormats();
for (const format of formats) {
if (supportedFormats.includes(format)) {
return format;
}
}
return formats[0]; // 返回默认格式
}
// 检测支持的格式
detectSupportedFormats() {
const formats = [];
const canvas = document.createElement('canvas');
// 检测WebP支持
if (canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0) {
formats.push('webp');
}
// 检测AVIF支持
const avifTest = new Image();
avifTest.onload = () => formats.push('avif');
avifTest.onerror = () => {};
avifTest.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGyJDb2xvci3/2VtZGF0EgAKBzgABpAQIYGy