{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["type ProjectTranslations = {\n  [languageCode: string]: Record<string, any>;\n};\n\ntype LanguageContent = {\n  [languageCode: string]: string[];\n};\n\ntype CustomLanguage = {\n  name: string;\n  two_letters_code: string;\n  three_letters_code: string;\n  locale: string;\n  locale_with_underscore: string;\n  android_code: string;\n  osx_code: string;\n  osx_locale: string;\n};\n\ntype CustomLanguages = {\n  [customLanguageCode: string]: CustomLanguage;\n};\n\ntype CrowdinManifest = {\n  files: string[];\n  languages: string[];\n  custom_languages: CustomLanguages;\n  timestamp: string;\n  content: LanguageContent;\n};\n\nclass TranslationService {\n  private static instance: TranslationService;\n  private translationsCache: Map<string, ProjectTranslations> = new Map();\n  private manifestCache: Map<string, CrowdinManifest> = new Map();\n  private static BASE_URL = 'https://i18n.oneweb.mercedes-benz.com';\n  private static BASE_PHRASE_URL = `${this.BASE_URL}/projects/`;\n  private static isBrowser = typeof window !== 'undefined';\n\n  private constructor() {\n    // Private constructor to prevent instantiation\n  }\n\n  public static getInstance(): TranslationService {\n    if (!TranslationService.instance) {\n      TranslationService.instance = new TranslationService();\n    }\n    return TranslationService.instance;\n  }\n\n  /**\n   * Fetches the Crowdin manifest for a given distribution hash. Used to get the list of files, languages and content in a distribution.\n   * @param distributionHash The distribution hash for Crowdin.\n   * @returns The Crowdin manifest as a JSON object.\n   */\n  private async fetchCrowdinManifest(distributionHash: string): Promise<CrowdinManifest> {\n    // Skip cache in SSR environment\n    if (TranslationService.isBrowser && this.manifestCache.has(distributionHash)) {\n      return this.manifestCache.get(distributionHash)!;\n    }\n\n    const manifestUrl = `${TranslationService.BASE_URL}/${distributionHash}/manifest.json`;\n\n    try {\n      const manifestResponse = await fetch(manifestUrl);\n      if (!manifestResponse.ok) {\n        throw new Error(`Error fetching Crowdin manifest: ${manifestResponse.statusText}`);\n      }\n      const responseManifest = await manifestResponse.json();\n      const manifest: CrowdinManifest = {\n        files: responseManifest.files,\n        languages: responseManifest.languages,\n        custom_languages: responseManifest.custom_languages,\n        timestamp: responseManifest.timestamp,\n        content: responseManifest.content,\n      };\n      // Only cache in browser environment\n      if (TranslationService.isBrowser) {\n        this.manifestCache.set(distributionHash, manifest);\n      }\n      return manifest;\n    } catch (error) {\n      console.error(error);\n      throw error;\n    }\n  }\n\n  /**\n   * Helper method to build Crowdin translation URL\n   * @param file The file name to fetch the translation for.\n   * @param distributionHash The distribution hash for Crowdin.\n   * @returns The URL and translation data\n   */\n  private async buildCrowdinTranslationUrl(file: string, distributionHash: string, timestamp: string): Promise<URL> {\n    const crowdinUrl = new URL(`${TranslationService.BASE_URL}/${distributionHash}${file}`);\n    crowdinUrl.searchParams.append('timestamp', timestamp);\n    return crowdinUrl;\n  }\n\n  /**\n   * Fetches translations for a specific file from Crowdin.\n   * @param file The file name to fetch the translation for.\n   * @param distributionHash The distribution hash for Crowdin.\n   * @returns The translation messages as a JSON object.\n   */\n  private async fetchCrowdinTranslation(\n    file: string,\n    distributionHash: string,\n    timestamp: string,\n  ): Promise<Record<string, any>> {\n    // Skip cache in SSR environment\n    if (!TranslationService.isBrowser) {\n      const crowdinUrl = await this.buildCrowdinTranslationUrl(file, distributionHash, timestamp);\n      return await this.fetchTranslation(crowdinUrl);\n    }\n\n    if (!this.translationsCache.has(distributionHash)) {\n      this.translationsCache.set(distributionHash, {});\n    }\n\n    const projectTranslations = this.translationsCache.get(distributionHash)!;\n\n    if (!projectTranslations[file]) {\n      const crowdinUrl = await this.buildCrowdinTranslationUrl(file, distributionHash, timestamp);\n      const translation = await this.fetchTranslation(crowdinUrl);\n      projectTranslations[file] = translation;\n    }\n\n    return projectTranslations[file];\n  }\n\n  /**\n   * Helper method to build Phrase translation URL\n   * @param locale The locale to fetch the translation for.\n   * @param projectId The project ID in Phrase.\n   * @param fileFormat The file format of the fetched translation.\n   * @param queryStringOpt Optional query string parameters.\n   * @returns The constructed URL for Phrase translation\n   */\n  private buildPhraseTranslationUrl(\n    locale: string,\n    projectId: string,\n    fileFormat: string,\n    queryStringOpt?: Record<string, any>,\n  ): URL {\n    const phraseUrl = new URL(`${TranslationService.BASE_PHRASE_URL}${projectId}/locales/${locale}/download`);\n    phraseUrl.searchParams.append('file_format', fileFormat);\n\n    if (queryStringOpt) {\n      Object.keys(queryStringOpt).forEach((key) => {\n        phraseUrl.searchParams.append(key, queryStringOpt[key]);\n      });\n    }\n\n    return phraseUrl;\n  }\n\n  /**\n   * Fetches translations for a specific locale from Phrase.\n   * @param locale The locale to fetch the translation for (e.g., 'en-US').\n   * @param projectId The project ID in Phrase.\n   * @param fileFormat The file format of the fetched translation (e.g., 'json').\n   * @param queryStringOpt Optional. Query string parameters to be appended to the request URL.\n   * @returns The translation messages as a JSON object.\n   */\n  private async fetchPhraseTranslation(\n    locale: string,\n    projectId: string,\n    fileFormat: string,\n    queryStringOpt?: Record<string, any>,\n  ): Promise<Record<string, any>> {\n    // Skip cache in SSR environment\n    if (!TranslationService.isBrowser) {\n      const phraseUrl = this.buildPhraseTranslationUrl(locale, projectId, fileFormat, queryStringOpt);\n      return await this.fetchTranslation(phraseUrl);\n    }\n\n    if (!this.translationsCache.has(projectId)) {\n      this.translationsCache.set(projectId, {});\n    }\n\n    const projectTranslations = this.translationsCache.get(projectId)!;\n\n    if (!projectTranslations[locale]) {\n      const phraseUrl = this.buildPhraseTranslationUrl(locale, projectId, fileFormat, queryStringOpt);\n      const translation = await this.fetchTranslation(phraseUrl);\n      projectTranslations[locale] = translation;\n    }\n\n    return projectTranslations[locale];\n  }\n\n  /**\n   * Fetches translation messages from a given URL.\n   * @param url The URL to fetch the translation messages from.\n   * @returns The translation messages as a JSON object.\n   */\n  private async fetchTranslation(url: URL): Promise<Record<string, any>> {\n    try {\n      const translationRequestResponse = await fetch(url);\n      if (!translationRequestResponse.ok) {\n        throw new Error(`Error fetching translation: ${translationRequestResponse.statusText}`);\n      }\n      const translationMessages = await translationRequestResponse.json();\n      return translationMessages;\n    } catch (error) {\n      console.error(`Error fetching translation: ${error}`);\n      throw error;\n    }\n  }\n\n  /**\n   * Fetches translations for a specific file from Crowdin.\n   * @param fileNames The file names to fetch translations for. Can be a single file name or an array of file names.\n   * @param distributionHash The distribution hash for Crowdin.\n   * @returns An object containing the translation messages by file name. If a single file name is provided, the return type will be Record<string, any>. If an array of file names is provided, the return type will be Record<string, Record<string, any>>.\n   */\n  public async getFileTranslations(\n    fileNames: string | string[],\n    distributionHash: string,\n    options?: { merge?: boolean },\n  ): Promise<Record<string, any>> {\n    const { content, timestamp } = await this.fetchCrowdinManifest(distributionHash);\n    fileNames = Array.isArray(fileNames) ? fileNames : [fileNames];\n\n    let translations: Record<string, any> = {};\n\n    for (const file of fileNames) {\n      if (content && !Object.values(content).some((fileList) => fileList.includes(file))) {\n        throw new Error(`File ${file} not found in the manifest`);\n      }\n      const fileTranslations = await this.fetchCrowdinTranslation(file, distributionHash, timestamp);\n      if (options?.merge) {\n        translations = { ...translations, ...fileTranslations };\n      } else {\n        translations[file] = fileTranslations;\n      }\n    }\n    return translations;\n  }\n\n  /**\n   * This method fetches translations for a specific locale from either Crowdin or Phrase. If only 2 arguments are provided, it fetches translations from Crowdin. If the third argument is provided (file_format), it fetches translations from Phrase.\n   * This overload was created in order to facilitate the migration from Phrase to Crowdin.\n   * @param locale the locale to fetch the translation for (e.g., 'en-US'). Crowdin locales: https://support.crowdin.com/developer/language-codes/\n   * @param distributionOrProjectID  For Crowdin: the distribution hash. For Phrase: the project ID.\n   * @param fileFormat  Optional. The file format of the fetched translation (required for Phrase).\n   * @param queryStringOpt  Optional. Query string parameters to be appended to the request URL.\n   * @returns An object containing the translation messages by locale.\n   */\n  public async getLocaleTranslations(\n    locale: string,\n    distributionOrProjectID: string,\n    fileFormat?: string,\n    queryStringOpt?: Record<string, any>,\n  ): Promise<Record<string, any>> {\n    if (fileFormat) {\n      // For Phrase\n      return this.fetchPhraseTranslation(locale, distributionOrProjectID, fileFormat, queryStringOpt);\n    }\n\n    // For Crowdin\n    const { languages, content, custom_languages, timestamp } =\n      await this.fetchCrowdinManifest(distributionOrProjectID);\n\n    // Find the best matching locale\n    const matchingLocale = this.findMatchingLocale(locale, [\n      ...languages,\n      ...(custom_languages ? Object.keys(custom_languages) : []),\n    ]);\n\n    // Use the matching locale to get translation files\n    const translationFiles = content[matchingLocale];\n\n    if (translationFiles.length === 0) {\n      throw new Error(`No translation files found for locale ${matchingLocale}`);\n    }\n\n    let translations: Record<string, any> = {};\n    await Promise.all(\n      translationFiles.map(async (file) => {\n        const fileTranslations = await this.fetchCrowdinTranslation(file, distributionOrProjectID, timestamp);\n        translations = { ...translations, ...fileTranslations };\n      }),\n    );\n\n    return translations;\n  }\n\n  /**\n   * Finds the best matching locale from available languages for a given locale.\n   * @param givenLocale The locale to find a match for (e.g., 'de-DE').\n   * @param availableLanguages Array of available language codes from the manifest ('ar', 'de', 'de-AT', 'pt-PT').\n   * @returns The matching locale string.\n   * @throws {Error} If the given locale is not found in the available languages.\n   */\n  private findMatchingLocale(givenLocale: string, availableLanguages: string[]): string {\n    // de-DE\n    if (availableLanguages.includes(givenLocale)) {\n      return givenLocale;\n    }\n\n    // support custom lower case language\n    if (availableLanguages.includes(givenLocale.toLowerCase())) {\n      return givenLocale.toLowerCase();\n    }\n\n    // de\n    const [language] = givenLocale.split('-');\n\n    const matchingLocale = availableLanguages.find((crowdinLang) => crowdinLang === language);\n\n    if (!matchingLocale) {\n      throw new Error(\n        `Locale ${givenLocale} not found in the manifest. Available languages: ${availableLanguages.join(', ')}`,\n      );\n    }\n\n    return matchingLocale;\n  }\n}\n\nconst translationService = TranslationService.getInstance();\n\n/**\n * This method fetches translations for a specific locale from either Crowdin or Phrase. If only 2 arguments are provided, it fetches translations from Crowdin. If the third argument is provided (file_format), it fetches translations from Phrase.\n * This overload was created in order to facilitate the migration from Phrase to Crowdin.\n * @param locale the locale to fetch the translation for (e.g., 'en-US'). Crowdin locales: https://support.crowdin.com/developer/language-codes/\n * @param distributionOrProjectID  For Crowdin: the distribution hash. For Phrase: the project ID.\n * @param fileFormat  Optional. The file format of the fetched translation (required for Phrase).\n * @param queryStringOpt  Optional. Query string parameters to be appended to the request URL.\n * @returns An object containing the translation messages by locale.\n */\nexport function getTranslation(\n  locale: string,\n  distributionOrProjectID: string,\n  fileFormat?: string,\n  queryStringOpt?: Record<string, any>,\n) {\n  return translationService.getLocaleTranslations(locale, distributionOrProjectID, fileFormat, queryStringOpt);\n}\n\n/**\n * @param fileNames  The file names to fetch translations for. Can be a single file name or an array of file names.\n * @param distributionHash  The distribution hash for Crowdin.\n * @returns An object containing the translation messages by file name. If a single file name is provided, the return type will be Record<string, any>. If an array of file names is provided, the return type will be Record<string, Record<string, any>>.\n */\n\nexport function getFileTranslation(\n  fileNames: string | string[],\n  distributionHash: string,\n  options?: { merge?: boolean },\n): Promise<Record<string, any>> {\n  return translationService.getFileTranslations(fileNames, distributionHash, options);\n}\n"],"mappings":"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"}