vite-plugin-sw-offline 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-sw-offline",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Vite 插件:Service Worker、离线页模板与构建注入(适用于 Vite H5 / uni-app H5 等)",
5
5
  "main": "src/index.js",
6
6
  "files": [
package/runtime/sw.js CHANGED
@@ -96,6 +96,9 @@ const NETWORK_PROBE_URL = '__NETWORK_PROBE_URL__';
96
96
  /** 网络探测超时时间(毫秒,构建时注入) */
97
97
  const NETWORK_PROBE_TIMEOUT = __SW_RT_NETWORK_PROBE_TIMEOUT__;
98
98
 
99
+ /** 离线页文案(构建时注入 JSON,与 offline.html 同源) */
100
+ const OFFLINE_I18N = __OFFLINE_I18N_INJECT__;
101
+
99
102
  // ============================================
100
103
  // 二、工具函数
101
104
  // ============================================
@@ -216,23 +219,26 @@ async function purgeStaleApiEntries() {
216
219
  }
217
220
  }
218
221
 
222
+ function stringifyOfflineI18nForInlineScript() {
223
+ try {
224
+ return JSON.stringify(OFFLINE_I18N).replace(/</g, '\\u003c');
225
+ } catch (e) {
226
+ return '{}';
227
+ }
228
+ }
229
+
219
230
  /**
220
- * 返回离线页面
221
- * 优先从缓存读取完整的 offline.html,
222
- * 若缓存中也没有(极端情况),则返回一个内联兜底页面(文案与 offline.html 保持一致)
231
+ * SW 内联兜底离线页 HTML(无 search-bar;语言逻辑与 offline.html 一致)
223
232
  */
224
- async function getOfflineResponse() {
225
- const cache = await caches.open(CACHE_NAMES.STATIC);
226
- const offlineResponse = await cache.match(OFFLINE_PAGE);
227
- if (offlineResponse) {
228
- return offlineResponse;
229
- }
230
- // 内联兜底:仅在 offline.html 完全无法从缓存获取时才使用
231
- // 注意:文案和按钮需与 offline.html 保持一致,避免用户体验割裂
232
- console.warn('[SW] offline.html not in cache, using inline fallback');
233
- return new Response(
233
+ function buildInlineOfflineFallbackHtml() {
234
+ const messagesJson = stringifyOfflineI18nForInlineScript();
235
+ const script =
236
+ '(function(){var M=' +
237
+ messagesJson +
238
+ ';var SHORT={en:"en_US",zh:"zh_CN",ja:"ja_JP",ko:"ko_KR",ar:"ar_SA",hi:"hi_IN",pt:"pt_BR",ru:"ru_RU",th:"th_TH",tr:"tr_TR",vi:"vi_VN",es:"es_MX"};function norm(s){if(!s||typeof s!=="string")return "";s=s.trim().replace(/-/g,"_");if(s.indexOf("_")===-1)return SHORT[s.toLowerCase()]||"";var i=s.indexOf("_");return s.slice(0,i).toLowerCase()+"_"+s.slice(i+1).toUpperCase();}function resolveKey(){var u="";try{u=new URLSearchParams(location.search).get("locale")||"";}catch(e){}var st="";try{var raw=localStorage.getItem("common");if(raw){var o=JSON.parse(raw);if(o&&typeof o.locale==="string")st=o.locale;}}catch(e){}return norm(u)||norm(st)||"zh_CN";}var key=resolveKey();if(!M[key])key="zh_CN";var t=M[key]||M.zh_CN;if(!t)return;document.documentElement.setAttribute("lang",key.replace("_","-"));if(key.indexOf("ar_")===0)document.documentElement.setAttribute("dir","rtl");document.title=t.title;var el=document.getElementById("oh");if(el)el.textContent=t.heading;el=document.getElementById("ob");if(el)el.textContent=t.body;el=document.getElementById("oc");if(el)el.textContent=t.contact;el=document.getElementById("or");if(el)el.textContent=t.reload;window.__offlineContactHint=t.contactOfflineHint;})();';
239
+ return (
234
240
  '<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">' +
235
- '<title>网络连接失败</title>' +
241
+ '<title></title>' +
236
242
  '<style>*{margin:0;padding:0;box-sizing:border-box}' +
237
243
  'body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;background:#131529;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:0 20px;color:#fff}' +
238
244
  '.c{text-align:center;max-width:400px;width:100%}' +
@@ -245,18 +251,40 @@ async function getOfflineResponse() {
245
251
  '.p{background:linear-gradient(180deg,#6591FD 0%,#3D75FF 100%);color:#fff;box-shadow:0 4px 16px rgba(74,124,255,0.3);border-radius:100px}' +
246
252
  '</style></head>' +
247
253
  '<body><div class="c">' +
248
- '<h1>网络连接失败</h1>' +
249
- '<p>请检查网络设置后重试,或联系客服获取帮助,若无法重启,请您耐心等待并稍后再进行尝试。</p>' +
254
+ '<h1 id="oh"></h1>' +
255
+ '<p id="ob"></p>' +
250
256
  '<div class="btns">' +
251
- '<a class="btn s" href="javascript:void(0)" onclick="var u=localStorage.getItem(\'customerServiceUrl\');u?window.open(u,\'_blank\'):alert(\'请在网络恢复后联系客服\')">联系客服</a>' +
252
- '<button class="btn p" onclick="location.reload()">重新加载</button>' +
257
+ '<a class="btn s" href="javascript:void(0)" id="oc" onclick="var u=localStorage.getItem(\'customerServiceUrl\');u?window.open(u,\'_blank\'):alert(window.__offlineContactHint||\'\')"></a>' +
258
+ '<button class="btn p" type="button" id="or" onclick="location.reload()"></button>' +
253
259
  '</div></div>' +
260
+ '<script>' +
261
+ script +
262
+ '</script>' +
254
263
  '<script>window.addEventListener("online",function(){location.reload()});</script>' +
255
- '</body></html>',
256
- { status: 503, headers: { 'Content-Type': 'text/html; charset=utf-8' } }
264
+ '</body></html>'
257
265
  );
258
266
  }
259
267
 
268
+ /**
269
+ * 返回离线页面
270
+ * 优先从缓存读取完整的 offline.html,
271
+ * 若缓存中也没有(极端情况),则返回一个内联兜底页面(文案与 offline.html 保持一致)
272
+ */
273
+ async function getOfflineResponse() {
274
+ const cache = await caches.open(CACHE_NAMES.STATIC);
275
+ const offlineResponse = await cache.match(OFFLINE_PAGE);
276
+ if (offlineResponse) {
277
+ return offlineResponse;
278
+ }
279
+ // 内联兜底:仅在 offline.html 完全无法从缓存获取时才使用
280
+ // 注意:文案和按钮需与 offline.html 保持一致,避免用户体验割裂
281
+ console.warn('[SW] offline.html not in cache, using inline fallback');
282
+ return new Response(buildInlineOfflineFallbackHtml(), {
283
+ status: 503,
284
+ headers: { 'Content-Type': 'text/html; charset=utf-8' }
285
+ });
286
+ }
287
+
260
288
  /**
261
289
  * 预缓存离线页 HTML(STATIC)+ 背景图(IMAGE),与 fetch 分发策略一致
262
290
  */
package/src/index.js CHANGED
@@ -8,6 +8,7 @@ const path = require('path');
8
8
  const PKG_ROOT = path.join(__dirname, '..');
9
9
  const RUNTIME_DIR = path.join(PKG_ROOT, 'runtime');
10
10
  const DEFAULT_OFFLINE_HTML = path.join(PKG_ROOT, 'templates', 'default', 'offline.html');
11
+ const DEFAULT_OFFLINE_I18N_JSON = path.join(PKG_ROOT, 'templates', 'default', 'offline-i18n.json');
11
12
  /** 默认离线页背景(与模板、runtime/sw.js 中 /static/offline-bg.jpg 一致) */
12
13
  const DEFAULT_OFFLINE_BG_JPG = path.join(PKG_ROOT, 'assets', 'offline-bg.jpg');
13
14
 
@@ -43,7 +44,25 @@ function normalizeOfflineLogoPath(raw) {
43
44
  return withSlash + query;
44
45
  }
45
46
 
47
+ function loadOfflineI18nMessages() {
48
+ try {
49
+ const raw = fs.readFileSync(DEFAULT_OFFLINE_I18N_JSON, 'utf-8');
50
+ return JSON.parse(raw);
51
+ } catch (e) {
52
+ console.warn(LOG, 'offline-i18n.json missing or invalid:', e.message);
53
+ return {};
54
+ }
55
+ }
56
+
57
+ function injectOfflineI18nPlaceholder(content) {
58
+ const messages = loadOfflineI18nMessages();
59
+ const raw = JSON.stringify(messages);
60
+ const safe = raw.replace(/</g, '\\u003c');
61
+ return content.replace(/__OFFLINE_I18N_INJECT__/g, safe);
62
+ }
63
+
46
64
  function injectOfflineHtml(content, swConfig) {
65
+ content = injectOfflineI18nPlaceholder(content);
47
66
  const logo = normalizeOfflineLogoPath((swConfig && swConfig.offlineLogoPath) || '');
48
67
  if (logo) {
49
68
  content = content.replace(/__OFFLINE_LOGO__/g, logo);
@@ -116,6 +135,7 @@ function injectServiceWorkerPlaceholders(content, swConfig) {
116
135
  }
117
136
 
118
137
  function applySwJsPlaceholders(content, swConfig) {
138
+ content = injectOfflineI18nPlaceholder(content);
119
139
  content = injectServiceWorkerPlaceholders(content, swConfig);
120
140
  const apiPathsJson = JSON.stringify(resolveCacheableApiPaths(swConfig));
121
141
  content = content.replace(
@@ -0,0 +1,145 @@
1
+ {
2
+ "ar_SA": {
3
+ "title": "فشل الاتصال بالشبكة",
4
+ "heading": "فشل الاتصال بالشبكة",
5
+ "body": "يرجى التحقق من إعدادات الشبكة ثم المحاولة مرة أخرى، أو الاتصال بخدمة العملاء للمساعدة. إذا تعذّر إعادة الاتصال، يرجى الانتظار قليلاً والمحاولة لاحقًا.",
6
+ "contact": "خدمة العملاء",
7
+ "reload": "إعادة التحميل",
8
+ "copyAria": "نسخ اسم النطاق",
9
+ "copySuccess": "تم النسخ!",
10
+ "copyFail": "فشل النسخ، يرجى النسخ يدويًا.",
11
+ "contactOfflineHint": "يرجى التواصل مع خدمة العملاء بعد عودة الاتصال."
12
+ },
13
+ "en_US": {
14
+ "title": "Network connection failed",
15
+ "heading": "Network connection failed",
16
+ "body": "Please check your network settings and try again, or contact customer support for help. If the connection cannot be restored, please wait and try again later.",
17
+ "contact": "Contact support",
18
+ "reload": "Reload",
19
+ "copyAria": "Copy domain",
20
+ "copySuccess": "Copied!",
21
+ "copyFail": "Copy failed. Please copy manually.",
22
+ "contactOfflineHint": "Please contact customer support after your connection is restored."
23
+ },
24
+ "hi_IN": {
25
+ "title": "नेटवर्क कनेक्शन विफल",
26
+ "heading": "नेटवर्क कनेक्शन विफल",
27
+ "body": "कृपया अपनी नेटवर्क सेटिंग जाँचकर पुनः प्रयास करें, या सहायता के लिए ग्राहक सेवा से संपर्क करें। यदि कनेक्शन बहाल नहीं हो पाता है, कृपया प्रतीक्षा करें और बाद में फिर कोशिश करें।",
28
+ "contact": "ग्राहक सेवा",
29
+ "reload": "पुनः लोड करें",
30
+ "copyAria": "डोमेन कॉपी करें",
31
+ "copySuccess": "कॉपी हो गया!",
32
+ "copyFail": "कॉपी विफल। कृपया मैन्युअली कॉपी करें।",
33
+ "contactOfflineHint": "कनेक्शन बहाल होने के बाद कृपया ग्राहक सेवा से संपर्क करें।"
34
+ },
35
+ "ja_JP": {
36
+ "title": "ネットワーク接続に失敗しました",
37
+ "heading": "ネットワーク接続に失敗しました",
38
+ "body": "ネットワーク設定を確認してから再度お試しいただくか、カスタマーサポートまでお問い合わせください。接続が復旧しない場合は、しばらく時間をおいてから再度お試しください。",
39
+ "contact": "サポートに連絡",
40
+ "reload": "再読み込み",
41
+ "copyAria": "ドメインをコピー",
42
+ "copySuccess": "コピーしました!",
43
+ "copyFail": "コピーに失敗しました。手動でコピーしてください。",
44
+ "contactOfflineHint": "接続が復旧しましたら、カスタマーサポートにお問い合わせください。"
45
+ },
46
+ "ko_KR": {
47
+ "title": "네트워크 연결 실패",
48
+ "heading": "네트워크 연결 실패",
49
+ "body": "네트워크 설정을 확인한 뒤 다시 시도하거나 고객 지원에 문의해 주세요. 연결이 복구되지 않으면 잠시 후 다시 시도해 주세요.",
50
+ "contact": "고객 지원",
51
+ "reload": "새로고침",
52
+ "copyAria": "도메인 복사",
53
+ "copySuccess": "복사되었습니다!",
54
+ "copyFail": "복사에 실패했습니다. 직접 복사해 주세요.",
55
+ "contactOfflineHint": "연결이 복구된 후 고객 지원에 문의해 주세요."
56
+ },
57
+ "pt_BR": {
58
+ "title": "Falha na conexão de rede",
59
+ "heading": "Falha na conexão de rede",
60
+ "body": "Verifique as configurações de rede e tente novamente ou entre em contato com o suporte ao cliente para obter ajuda. Se a conexão não for restaurada, aguarde e tente mais tarde.",
61
+ "contact": "Suporte",
62
+ "reload": "Recarregar",
63
+ "copyAria": "Copiar domínio",
64
+ "copySuccess": "Copiado!",
65
+ "copyFail": "Falha ao copiar. Copie manualmente.",
66
+ "contactOfflineHint": "Entre em contato com o suporte após a conexão ser restaurada."
67
+ },
68
+ "ru_RU": {
69
+ "title": "Ошибка сетевого подключения",
70
+ "heading": "Ошибка сетевого подключения",
71
+ "body": "Проверьте настройки сети и повторите попытку или обратитесь в службу поддержки. Если подключение не восстанавливается, подождите немного и попробуйте снова позже.",
72
+ "contact": "Поддержка",
73
+ "reload": "Обновить",
74
+ "copyAria": "Копировать домен",
75
+ "copySuccess": "Скопировано!",
76
+ "copyFail": "Не удалось скопировать. Скопируйте вручную.",
77
+ "contactOfflineHint": "Обратитесь в поддержку после восстановления соединения."
78
+ },
79
+ "th_TH": {
80
+ "title": "การเชื่อมต่อเครือข่ายล้มเหลว",
81
+ "heading": "การเชื่อมต่อเครือข่ายล้มเหลว",
82
+ "body": "โปรดตรวจสอบการตั้งค่าเครือข่ายแล้วลองอีกครั้ง หรือติดต่อฝ่ายบริการลูกค้าเพื่อขอความช่วยเหลือ หากยังเชื่อมต่อไม่ได้ โปรดรอสักครู่แล้วลองใหม่ภายหลัง",
83
+ "contact": "ติดต่อฝ่ายบริการ",
84
+ "reload": "โหลดใหม่",
85
+ "copyAria": "คัดลอกโดเมน",
86
+ "copySuccess": "คัดลอกแล้ว!",
87
+ "copyFail": "คัดลอกไม่สำเร็จ โปรดคัดลอกด้วยตนเอง",
88
+ "contactOfflineHint": "โปรดติดต่อฝ่ายบริการหลังจากเชื่อมต่อกลับมาได้"
89
+ },
90
+ "tr_TR": {
91
+ "title": "Ağ bağlantısı başarısız",
92
+ "heading": "Ağ bağlantısı başarısız",
93
+ "body": "Lütfen ağ ayarlarınızı kontrol edip yeniden deneyin veya yardım için müşteri desteğiyle iletişime geçin. Bağlantı kurulamazsa lütfen bekleyip daha sonra tekrar deneyin.",
94
+ "contact": "Destek",
95
+ "reload": "Yeniden yükle",
96
+ "copyAria": "Alan adını kopyala",
97
+ "copySuccess": "Kopyalandı!",
98
+ "copyFail": "Kopyalanamadı. Lütfen elle kopyalayın.",
99
+ "contactOfflineHint": "Bağlantı düzeldiğinde müşteri desteğiyle iletişime geçin."
100
+ },
101
+ "vi_VN": {
102
+ "title": "Kết nối mạng thất bại",
103
+ "heading": "Kết nối mạng thất bại",
104
+ "body": "Vui lòng kiểm tra cài đặt mạng và thử lại, hoặc liên hệ bộ phận hỗ trợ khách hàng để được giúp đỡ. Nếu không khôi phục được kết nối, hãy đợi một lúc rồi thử lại sau.",
105
+ "contact": "Liên hệ hỗ trợ",
106
+ "reload": "Tải lại",
107
+ "copyAria": "Sao chép tên miền",
108
+ "copySuccess": "Đã sao chép!",
109
+ "copyFail": "Sao chép thất bại. Vui lòng sao chép thủ công.",
110
+ "contactOfflineHint": "Vui lòng liên hệ hỗ trợ sau khi mạng đã khôi phục."
111
+ },
112
+ "zh_CN": {
113
+ "title": "网络连接失败",
114
+ "heading": "网络连接失败",
115
+ "body": "请检查网络设置后重试,或联系客服获取帮助,若无法重启,请您耐心等待并稍后再进行尝试。",
116
+ "contact": "联系客服",
117
+ "reload": "重新加载",
118
+ "copyAria": "复制域名",
119
+ "copySuccess": "复制成功!",
120
+ "copyFail": "复制失败,请手动复制",
121
+ "contactOfflineHint": "请在网络恢复后联系客服"
122
+ },
123
+ "zh_TW": {
124
+ "title": "網路連線失敗",
125
+ "heading": "網路連線失敗",
126
+ "body": "請檢查網路設定後重試,或聯絡客服取得協助;若無法重新連線,請耐心等候並稍後再試。",
127
+ "contact": "聯絡客服",
128
+ "reload": "重新載入",
129
+ "copyAria": "複製網域",
130
+ "copySuccess": "複製成功!",
131
+ "copyFail": "複製失敗,請手動複製",
132
+ "contactOfflineHint": "請在網路恢復後聯絡客服"
133
+ },
134
+ "es_MX": {
135
+ "title": "Error de conexión de red",
136
+ "heading": "Error de conexión de red",
137
+ "body": "Verifica tu configuración de red e inténtalo de nuevo o contacta a soporte para obtener ayuda. Si la conexión no se restablece, espera un momento y vuelve a intentarlo más tarde.",
138
+ "contact": "Contactar soporte",
139
+ "reload": "Volver a cargar",
140
+ "copyAria": "Copiar dominio",
141
+ "copySuccess": "¡Copiado!",
142
+ "copyFail": "No se pudo copiar. Cópialo manualmente.",
143
+ "contactOfflineHint": "Contacta a soporte cuando se restaure la conexión."
144
+ }
145
+ }
@@ -201,7 +201,7 @@
201
201
  <div class="search-bar-input">
202
202
  <span id="typingText"></span><span class="typing-cursor">|</span>
203
203
  </div>
204
- <div class="search-bar-btn" id="copyDomainBtn" role="button" tabindex="0" aria-label="复制域名">
204
+ <div class="search-bar-btn" id="copyDomainBtn" role="button" tabindex="0" aria-label="">
205
205
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
206
206
  <circle cx="11" cy="11" r="7"></circle>
207
207
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
@@ -209,20 +209,85 @@
209
209
  </div>
210
210
  </div>
211
211
 
212
- <h1>网络连接失败</h1>
213
- <p>请检查网络设置后重试,或联系客服获取帮助,若无法重启,请您耐心等待并稍后再进行尝试。</p>
212
+ <h1 id="offline-heading">网络连接失败</h1>
213
+ <p id="offline-body">请检查网络设置后重试,或联系客服获取帮助,若无法重启,请您耐心等待并稍后再进行尝试。</p>
214
214
 
215
215
  <div class="buttons">
216
216
  <a class="btn btn-secondary" href="javascript:void(0)" id="contactBtn">联系客服</a>
217
- <button class="btn btn-primary" onclick="location.reload()">重新加载</button>
217
+ <button type="button" class="btn btn-primary" id="reloadBtn" onclick="location.reload()">重新加载</button>
218
218
  </div>
219
219
  </div>
220
220
 
221
- <div class="copy-toast" id="copyToast" aria-live="polite">复制成功!</div>
221
+ <div class="copy-toast" id="copyToast" aria-live="polite"></div>
222
222
 
223
223
  <script>
224
+ window.__OFFLINE_I18N__ = __OFFLINE_I18N_INJECT__;
224
225
  var offlineDomainText = '__OFFLINE_DOMAIN__';
225
226
 
227
+ (function applyOfflineLocale() {
228
+ var M = window.__OFFLINE_I18N__ || {};
229
+ var SHORT_TO_FULL = {
230
+ en: 'en_US',
231
+ zh: 'zh_CN',
232
+ ja: 'ja_JP',
233
+ ko: 'ko_KR',
234
+ ar: 'ar_SA',
235
+ hi: 'hi_IN',
236
+ pt: 'pt_BR',
237
+ ru: 'ru_RU',
238
+ th: 'th_TH',
239
+ tr: 'tr_TR',
240
+ vi: 'vi_VN',
241
+ es: 'es_MX'
242
+ };
243
+ function normalizeLocaleKey(s) {
244
+ if (!s || typeof s !== 'string') return '';
245
+ s = s.trim().replace(/-/g, '_');
246
+ if (s.indexOf('_') === -1) return SHORT_TO_FULL[s.toLowerCase()] || '';
247
+ var i = s.indexOf('_');
248
+ return s.slice(0, i).toLowerCase() + '_' + s.slice(i + 1).toUpperCase();
249
+ }
250
+ function resolveOfflineLocaleKey() {
251
+ var fromUrl = '';
252
+ try {
253
+ fromUrl = new URLSearchParams(window.location.search).get('locale') || '';
254
+ } catch (e) {}
255
+ var fromStorage = '';
256
+ try {
257
+ var rawCommon = localStorage.getItem('common');
258
+ if (rawCommon) {
259
+ var parsed = JSON.parse(rawCommon);
260
+ if (parsed && typeof parsed.locale === 'string') fromStorage = parsed.locale;
261
+ }
262
+ } catch (e) {}
263
+ return normalizeLocaleKey(fromUrl) || normalizeLocaleKey(fromStorage) || 'zh_CN';
264
+ }
265
+ var loc = resolveOfflineLocaleKey();
266
+ if (!M[loc]) loc = 'zh_CN';
267
+ var t = M[loc] || M.zh_CN;
268
+ if (!t) return;
269
+ document.documentElement.setAttribute('lang', loc.replace('_', '-'));
270
+ if (loc.indexOf('ar_') === 0) document.documentElement.setAttribute('dir', 'rtl');
271
+ document.title = t.title;
272
+ var el = document.getElementById('offline-heading');
273
+ if (el) el.textContent = t.heading;
274
+ el = document.getElementById('offline-body');
275
+ if (el) el.textContent = t.body;
276
+ el = document.getElementById('contactBtn');
277
+ if (el) el.textContent = t.contact;
278
+ el = document.getElementById('reloadBtn');
279
+ if (el) el.textContent = t.reload;
280
+ el = document.getElementById('copyDomainBtn');
281
+ if (el) el.setAttribute('aria-label', t.copyAria);
282
+ el = document.getElementById('copyToast');
283
+ if (el) el.textContent = t.copySuccess;
284
+ window.__offlineUiStrings = {
285
+ copySuccess: t.copySuccess,
286
+ copyFail: t.copyFail,
287
+ contactOfflineHint: t.contactOfflineHint
288
+ };
289
+ })();
290
+
226
291
  // 打字机动画
227
292
  (function() {
228
293
  var text = offlineDomainText;
@@ -266,6 +331,9 @@
266
331
  var toastTimer = null;
267
332
 
268
333
  function showCopyToast() {
334
+ if (window.__offlineUiStrings && window.__offlineUiStrings.copySuccess) {
335
+ toast.textContent = window.__offlineUiStrings.copySuccess;
336
+ }
269
337
  toast.classList.add('show');
270
338
  if (toastTimer) clearTimeout(toastTimer);
271
339
  toastTimer = setTimeout(function() {
@@ -300,7 +368,8 @@
300
368
  var text = (offlineDomainText || '').trim();
301
369
  if (!text) return;
302
370
  copyToClipboard(text).then(showCopyToast).catch(function() {
303
- alert('复制失败,请手动复制');
371
+ var msg = (window.__offlineUiStrings && window.__offlineUiStrings.copyFail) || '';
372
+ alert(msg);
304
373
  });
305
374
  }
306
375
 
@@ -319,7 +388,8 @@
319
388
  if (customerServiceUrl) {
320
389
  window.open(customerServiceUrl, '_blank');
321
390
  } else {
322
- alert('请在网络恢复后联系客服');
391
+ var msg = (window.__offlineUiStrings && window.__offlineUiStrings.contactOfflineHint) || '';
392
+ alert(msg);
323
393
  }
324
394
  });
325
395