You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Ionic中预加载依赖内JSON翻译文件以支持离线使用

解决Ionic应用离线时无法加载Transloco翻译JSON的问题

问题背景

  • 基于Transloco封装了共享翻译库,内置所有JSON翻译文件,为多个Angular应用提供服务
  • Angular Web应用运行完全正常;Ionic应用在线时正常,但离线状态下无法加载库内的JSON翻译资源
  • 拒绝采用将JSON转为JS函数的方案,需实现在线时预加载翻译文件并缓存,离线时直接使用缓存内容的逻辑

解决方案思路

利用Ionic本地存储(@ionic/storage-angular)缓存翻译数据,结合网络状态判断实现逻辑:

  1. 在线状态:优先从翻译库加载最新JSON,加载成功后存入本地缓存
  2. 离线状态:直接从本地缓存读取翻译内容
  3. 完整保留原有API自定义翻译的逻辑,不影响现有功能

修改后的TranslocoHttpLoader实现

先安装依赖:

npm install @ionic/storage-angular

修改翻译加载器代码:

import { Injectable, Inject } from '@angular/core';
import { TranslocoLoader, Translation, TRANSLOCO_LOADER_CONFIG, TranslocoLoaderConfig } from '@ngneat/transloco';
import { from, Observable, of, combineLatest } from 'rxjs';
import { map, switchMap, catchError } from 'rxjs/operators';
import { Storage } from '@ionic/storage-angular';

@Injectable({ providedIn: 'root' })
export class TranslocoHttpLoaderService implements TranslocoLoader {
  // 缓存键前缀,避免与其他存储内容冲突
  private readonly CACHE_KEY_PREFIX = 'transloco_';

  constructor(
    @Inject(TRANSLOCO_LOADER_CONFIG)
    private translocoLoaderConfig: TranslocoLoaderConfig,
    private storage: Storage
  ) {
    // 初始化Ionic Storage
    this.initStorage();
  }

  private async initStorage() {
    await this.storage.create();
  }

  getTranslation(lang: string): Observable<Translation> {
    const cacheKey = `${this.CACHE_KEY_PREFIX}${lang}`;
    const isOnline = navigator.onLine;

    // 先尝试获取缓存的翻译内容
    const cachedTranslation$ = from(this.storage.get(cacheKey)).pipe(
      map(cached => cached || null)
    );

    // 在线时加载最新翻译并更新缓存;离线时直接返回空(后续用缓存)
    const freshTranslation$ = isOnline ? this.loadFreshTranslation(lang, cacheKey) : of(null);

    // 合并缓存与最新翻译流
    return combineLatest([cachedTranslation$, freshTranslation$]).pipe(
      switchMap(([cached, fresh]) => {
        // 优先用最新翻译,没有则用缓存;极端情况(离线无缓存)返回默认语言
        const baseTranslation = fresh || cached || (await this.getLanguage('en').then(x => x.default));
        
        if (this.translocoLoaderConfig.disableCustomLiterals) {
          return of(this.transformTranslations(baseTranslation));
        }

        // 结合API自定义翻译逻辑
        return this.getCustomLiterals().pipe(
          map(customLiterals => ({
            ...baseTranslation,
            ...customLiterals
          })),
          map(translations => this.transformTranslations(translations))
        );
      })
    );
  }

  private loadFreshTranslation(lang: string, cacheKey: string): Observable<Translation> {
    return from(this.getLanguage(lang).then(x => x.default)).pipe(
      switchMap(translation => {
        // 将最新翻译存入缓存
        return from(this.storage.set(cacheKey, translation)).pipe(
          map(() => translation)
        );
      }),
      catchError(() => {
        // 加载失败时回退到缓存内容
        return from(this.storage.get(cacheKey));
      })
    );
  }

  private getCustomLiterals(): Observable<Translation> {
    return API_LOGIC_CALL.pipe(
      map((el: any) => {
        if (!el.value) return {};

        let customLiterals = JSON.parse(el.value);
        // 扁平化翻译对象
        customLiterals = customLiterals.reduce(
          (acc: Translation, curr: any) => ({ ...acc, ...curr }),
          {} as Translation
        );
        return customLiterals;
      }),
      catchError(() => of({}))
    );
  }

  private transformTranslations(translations: Translation): Translation {
    // 转换@:语法为{{}}格式
    return transformValuesRecursively(translations, (value) => {
      if (value.startsWith('@:')) {
        return '{{' + value.slice(2) + '}}';
      }
      return value;
    });
  }

  // 原有加载语言文件的方法,保持不变
  getLanguage(lang: string) {
    switch (lang) {
      case 'es':
        return import('./i18n/es');
      case 'fr':
        return import('./i18n/fr/fr.json');
      default:
        // 'en'
        return import('./i18n/en/en.json');
    }
  }
}

// 原有递归转换工具函数,保持不变
const transformValuesRecursively = (
  object: any,
  tfunction: (value: string) => string
) => {
  const newObject = {} as any;
  Object.entries(object).forEach(([key, value]) => {
    if (typeof value === 'string') {
      newObject[key] = tfunction(value);
    } else {
      newObject[key] = transformValuesRecursively(value, tfunction);
    }
  });
  return newObject;
};

关键修改说明

  1. 引入Ionic Storage:持久化缓存翻译数据,确保离线时可读取
  2. 网络状态判断:通过navigator.onLine切换加载源,在线取最新、离线取缓存
  3. 缓存逻辑优化:在线加载成功后更新缓存,加载失败时回退到缓存
  4. 兼容原有逻辑:完整保留API自定义翻译和语法转换流程,无功能断档
  5. 异常处理:添加错误捕获,避免因网络或加载失败导致翻译失效

额外优化建议

  • 如需更精准的网络状态判断,可替换navigator.onLine为@ionic-native/network插件
  • 可添加缓存过期逻辑,定期更新缓存的翻译内容
  • 在Ionic应用的app.module.ts中导入Storage模块:
import { IonicStorageModule } from '@ionic/storage-angular';

@NgModule({
  imports: [
    // ...其他模块
    IonicStorageModule.forRoot()
  ]
})
export class AppModule {}

内容的提问来源于stack exchange,提问作者Pato

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 18:23:24