Оптимизируйте свою интернационализацию за считанные минуты

JavaScript
Оптимизируйте свою интернационализацию за считанные минуты

предисловие

В данной работе для ситуации, когда интернационализированных данных перевода много и они хранятся на сервере, рассматривается оптимизация и пишется плагин.GitHub.com/Feather Letter16449196…, для справки или использования. Если есть что-то, что нужно улучшить, я надеюсь, вы меня поправите.

фон спроса

В нашем одностраничном приложении есть две точки оптимизации функции интернационализации:

  • Интернационализированные данные перевода хранятся в базе данных. Каждый раз, когда вы переключаете языки или перезагружаете страницу, вам нужно один раз запросить интерфейс, и интерфейс, вероятно, будет задерживаться на 1 секунду, что сильно влияет на пользовательский опыт, особенно на загрузку первого экрана.

  • Поскольку все данные хранятся в базе данных, при загрузке первого экрана данные перевода недоступны перед интерфейсом запроса.

Оптимизация

Для первой проблемы используйте кеш

По второму вопросу я разделил данные перевода на две части:

статические данные перевода: Он написан на js и используется для некоторых основных данных перевода.Даже если сеть отключена, пользователь все равно может видеть обычную страницу.

Динамически переводить данные: Полученная из интерфейса эта часть данных может быть изменена пользователем через соответствующую страницу конфигурации.

Когда заходит оптимизированная страница, ее можно разделить на следующие этапы:

image

ключевой код

// 创建实例并调用
const translateManager = new TranslateManager({/*传参*/})
translateManager.update('en', (res) => {
    // 触发相应的更新视图的逻辑
    // 例如在vue中,使用了vue-i18n,你可以
    i18n.setLocaleMessage(res.data);
});


class TranslateManager {
  STORAGE_KEY: string
  expireTime: number
  requestFn: Function | null
  staticTranslateData: object
  
  /**
   * 构造函数
   * @param params 
   */
  constructor(params: constructorParams) {
    this.expireTime = params.expireTime || Infinity; // 前端缓存有效时间
    this.STORAGE_KEY = params.storageKey || 'TranslateManager';
    this.requestFn = params.requestFn;
    this.staticTranslateData = params.staticTranslateData || {};
  }
  setRequestFn(fn: Function) {
    this.requestFn = fn;
  }
  /**
   * 设置缓存
   * @param {String} language 
   */
  setCache(language: string, data: object) {
    // 请求完成后缓存
    const storageData = jsonParse(localStorage.getItem(this.STORAGE_KEY)) || {};
    storageData[language] = data;
    storageData['time'] = new Date().getTime();
    localStorage.setItem(this.STORAGE_KEY, JSON.stringify(storageData));
  }
  /**
   * 从缓存中读取
   * @param language 语种
   */
  getCache(language: string) {
    const storageData = jsonParse(localStorage.getItem(this.STORAGE_KEY)) || {};
    if (storageData && storageData['time'] && storageData[language]) {
      const time = storageData['time'];
      const now = new Date().getTime();
      // 缓存只有两小时
      if (now - time <= this.expireTime) {
        return storageData[language] || null;
      }
    }
    return null;
  }
  /**
   * 获取后端的静态国际化数据
   */
  getDynamicTranslateData(language: string) {
    if (!this.requestFn) {
      console.error('请先执行setRequestFn!');
      return Promise.reject();
    }
    return this.requestFn({language}).then((res: object) => {
      this.setCache(language, res);
      return res;
    });
  }
  /**
   * 获取前后端交集后的国际化数据
   * @param {*} language 
   */
  getMergeTranslateData(language: string) {
    // 没缓存就发起请求
    return this.getDynamicTranslateData(language).then((res: any) => {

      const data = this.staticTranslateData[language] || {};
      // 部分国际化写在了前端,把前端的国际化文件合并到后端返回的数据中
      merge(res, data)
      if (isEmptyResult(res)) {
        return Promise.reject(new Error('locale empty !!'))
      }
      return res;
    })
  }
  /**
   * 主方法
   * @param locale 语种
   * @param callback 回调函数,用于订制自己触发的渲染逻辑
   */
  update(locale: string, callback: Function) {
    let staticData = this.staticTranslateData[locale];
    const cacheData = this.getCache(locale) || {};
    // 使用静态数据和缓存数据的并集,触发第一次视图更新
    merge(cacheData, staticData);
    callback(cacheData, 'first');

    // 返回静态和动态数据的合集
    return this.getMergeTranslateData(locale).then((res: object) => {
      // 获取数据是否和动态数据不一致
      if (res && !isEqual(res, cacheData)) {
        callback(res, 'second')
      }
    })
  }
}

продлевать

Вышеупомянутый метод был использован, и ожидаемый эффект был хорошо достигнут. Как раз, когда я писал эту статью, я вдруг обнаружил, что есть еще направление для дальнейшей глубокой оптимизации: динамические данные можно разбивать на разные модули и загружать пошагово. По логике собственного дела, например, он может основываться на разрешениях пользователей. Когда мои полномочия пользователя низкие и я могу просматривать только домашнюю страницу, я могу загрузить только данные перевода модуля домашней страницы; когда мои полномочия пользователя высоки и я захожу на соответствующую страницу, я загружаю данные соответствующего модуля в это время.

Суммировать

Идея предложенного оптимизационного решения на этот раз не сложная, но требует некоторого времени для реализации.Я построил колесо по идее этой оптимизации, которое очень удобно в использовании.Включает в себя демо и комментарии Добро пожаловать в звезду:GitHub.com/Feather Letter16449196…. Если вы считаете, что эта статья была вам полезна, ставьте лайк, спасибо~

Чтобы узнать больше о передовых технологиях, подпишитесь на общедоступную учетную запись WeChat «Круг обмена передовыми технологиями сухих товаров».