npm.io
1.0.1-alpha.1 • Published 2 months ago

@procore/cdn-translations

Licence
SEE LICENSE IN LICENSE
Version
1.0.1-alpha.1
Deps
2
Size
128 kB
Vulns
0
Weekly
0

CDN Translations

A React-based internationalization system that manages translations through a centralized service and CDN. This package provides hooks and utilities for requesting, serving, and managing translations in a distributed system.

Architecture

The package follows a pub/sub architecture using the I18nSystem for communication between components. The main components are:

  1. Translation Requesters (useRequestTranslations)

    • Components that need translations use the useRequestTranslations hook
    • Handles translation lifecycle (pending/resolved/rejected)
    • Implements timeout handling and cleanup
    • Supports both file-based and folder-based translation structures
  2. Translation Preloader (usePreloadTranslations)

    • Proactively loads translations for better performance
    • Works alongside the main translation request system
    • Helps reduce perceived loading time
  3. MFE Translation Orchestrator (useMFETranslations)

    • Main entry point for Micro-Frontend applications
    • Coordinates between saving and loading translations
    • Provides backward compatibility with traditional translation loading
    • Manages CDN feature flag integration
  4. Direct CDN Access (getTranslationsFromCDN)

    • Utility function for directly fetching translations from CDN
    • Bypasses the React hook system for non-React contexts
    • Implements proper error handling and caching
  5. Translation Cache System

    • browser-based caching for resolved translations
    • Implements request tracking to handle concurrent requests
  6. Event System

    • Implements a robust pub/sub system using SystemEvents
    • Handles translation resolution and rejection events
    • Provides cleanup mechanisms for event subscriptions
    • Uses unique identifiers for event tracking

Core Features

Translation Request Flow
  1. A component requests translations using useRequestTranslations
  2. useSaveTranslations:
    • Checks for translations in nonStandardTranslations
    • Applies locale overrides
    • Sets up event subscriptions
    • Manages request timeouts
  3. useLoadTranslations:
    • Checks for translations in nonStandardTranslations
    • Applies locale overrides
    • Check if there is a similar request being fetched already
    • Request the translation file
  4. Translations are received through the event system
  5. The component's state is updated with the received translations
Caching System
  • We use browser caching. Our current caching duration is 1 hour as max age and we use the caching strategy of stale-while-revalidate for 24 hours so even if the file is stale it will be updated in the background.
Locale Management
  • Supports locale overrides through getOverrideLocale
  • Implements fallback list generation with duplicate prevention
  • Handles fallback scenarios when translations aren't available
  • Maintains translation state for different locales
  • Provides consistent fallback hierarchy

Usage

MFEs Hook
import { useMFETranslations } from '@procore/cdn-translations';
import en from './path/to/translations/en.json';
import enOwner from './path/to/translations/en-x-owner.json';
import enBudget from './path/to/translations/en-budget.json';

function MyComponent() {
  const { status, translations } = useMFETranslations(
    {
      type: 'file',
      absolute_file_path: (locale) => `path/to/translations/${locale}.json`,
      locale: 'es-ES',
    },
    {
      // non-standard translations
      en,
      'en-US-x-owner': enOwner,
      'en-budget': enBudget,
    },
    {
      oldTranslations: translations, // translations imported by traditional way
      enableCDN: boolean, // to determine to pull translations from CDN or using the traditional way
    }
  );

  // If you don't handle the pending state, strings will appear in English first,
  // then will appear in the requested language when the strings are available
  if (status === 'pending') return <Loading />;

  return <div>{translations.someKey}</div>;
}
Packages Hook
import { useRequestTranslations } from '@procore/cdn-translations';
import en from './path/to/translations/en.json';
import enOwner from './path/to/translations/en-x-owner.json';
import enBudget from './path/to/translations/en-budget.json';

function MyComponent() {
  const { status, translations } = useRequestTranslations(
    {
      type: 'file',
      absolute_file_path: (locale) => `path/to/translations/${locale}.json`,
      locale: 'es-ES',
    },
    {
      // non-standard translations
      en,
      'en-US-x-owner': enOwner,
      'en-budget': enBudget,
    },
    {
      oldTranslations: translations, // translations imported by traditional way
      enableCDN: boolean, // to determine to pull translations from CDN or using the traditional way
    }
  );

  // If you don't handle the pending state, strings will appear in English first,
  // then will appear in the requested language when the strings are available
  if (status === 'pending') return <Loading />;

  return <div>{translations.someKey}</div>;
}
Direct CDN Access
import { getTranslationsFromCDN } from '@procore/cdn-translations';

// For non-React contexts or direct CDN access
async function loadTranslationsDirectly() {
  const translations = await getTranslationsFromCDN(
    '/path/to/translations',
    'es-ES'
  );

  if (translations) {
    console.log('Translations loaded:', translations);
  } else {
    console.log('Failed to load translations');
  }
}
Folder-based Structure
import { useRequestTranslations } from '@procore/cdn-translations';
import enCommon from './path/to/translations/en/common.json';
import enError from './path/to/translations/en/error.json';

function MyComponent() {
  const { status, translations } = useRequestTranslations(
    {
      type: 'folder',
      absolute_file_path: (locale, file_name) =>
        `path/to/translations/${locale}/${file_name}.json`,
      locale: 'es-ES',
      file_name: 'common',
    },
    {
      // non-standard translations
      en: { ...enError, ...enCommon },
    },
    {
      oldTranslations: translations, // translations imported by traditional way
      enableCDN: boolean, // to determine to pull translations from CDN or using the traditional way
    }
  );

  // If you don't handle the pending state, strings will appear in English first,
  // then will appear in the requested language when the strings are available
  if (status === 'pending') return <Loading />;

  return <div>{translations.someKey}</div>;
}
Preloading Translations
import { usePreloadTranslations } from '@procore/cdn-translations';

function MyComponent() {
  // Preload translations for better performance
  usePreloadTranslations(
    {
      type: 'file',
      absolute_file_path: (locale) => `path/to/translations/${locale}.json`,
      locale: 'es-ES',
    },
    {}
  );

  // ... rest of component
}

Error Handling

The system handles various error cases:

  • Invalid translation data
  • Failed translation requests
  • Network errors and timeouts
  • JSON parsing errors
  • Aborted requests
  • Timeout scenarios (configurable via OVERDUE_TIMEOUT)
  • Invalid translation formats
  • Missing or invalid locale configurations

Each error case is properly logged and communicated back to the requesting component. The system implements robust error handling with try-catch blocks and ensures proper cleanup of resources.

Performance Considerations

  • Uses efficient event-based communication
  • Handles concurrent requests gracefully
  • Implements timeout mechanisms to prevent hanging requests
  • Minimizes network requests through caching
  • Supports preloading of translations
  • Implements request deduplication to prevent duplicate CDN calls
  • Uses browser caching with stale-while-revalidate strategy

Configuration

The package can be configured through the Configuration type:

type Configuration = {
  locale: string;
} & (
  | {
      type: 'file';
      absolute_file_path: (locale: string) => string;
    }
  | {
      type: 'folder';
      absolute_file_path: (locale: string, file_name: string) => string;
      file_name: string;
    }
);

Feature Flags

The package provides functionality to manage the CDN translation feature flag:

import {
  CDN_TRANSLATION_FEATURE_FLAG_KEY,
  isCDNFeatureFlagEnabled
} from '@procore/cdn-translations';

/**
 * The key used to check for the feature flag in LD.
 */
CDN_TRANSLATION_FEATURE_FLAG_KEY;

// * Checks if the CDN feature flag is enabled in the passed I18n
isCDNFeatureFlagEnabled(
  i18n: I18n | undefined,
  value?: boolean
)

// Retrieves the Launch Darkly app ID for the CDN translation feature flag based on the domain.
getCDNTranslationLDId(current_window_domain)

Constants

The package provides configurable constants:

  • OVERDUE_TIMEOUT: Timeout duration for translation requests (default: 5000ms)
  • REQUESTING_CACHE_THRESHOLD: Time threshold for request deduplication

Markdown Diagram

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#F2F2F2', 'primaryTextColor': '#333', 'lineColor': '#888', 'nodeBorder': '#555', 'mainBkg': '#FFFFFF'}}}%%
graph TD
    subgraph Component Layer
        A[React Component]
        B(useRequestTranslations Hook)
    end

    subgraph Translation Resolution Flow
        C{Check non-standard translations}
        C1{Check if locale need to be mapped}
        C2{Check non-standard translations}
        D{Check Browser Cache}
        F[Fetch from CDN]
    end

    subgraph Core System
        G["I18nSystem <br/>(Pub/Sub Event Bus)"]
        H(Browser Cache Storage)
        I[CDN Service]
    end

    subgraph State Update
        J[Update Component State]
    end

    %% --- Connections ---
    A -- calls --> B;
    B -- initiates --> C;

    C -- "No" --> C1;
    C -- "Yes, Publish event with translations" --> G;

    C1 -- "No" --> D;
    C1 -- "Yes" --> C2;

    C2 -- "No" -->  D;
    C2 -- "Yes, Publish event with translations" --> G;

    D -- "Not Found" --> F;
    D -- "Found" --> G;

    F -- "HTTP GET" --> I;
    I -- "Translation File" --> G;

    G -- "Publishes 'Resolved' Event" --> J;
    G -- "Caches translation" --> H;

    J -- "Triggers re-render" --> A;

    %% --- Styling ---
    style A fill:#e3f2fd,stroke:#333
    style B fill:#e0f7fa,stroke:#333
    style J fill:#dcedc8,stroke:#333
    style G fill:#fff9c4,stroke:#333
    style I fill:#ffecb3,stroke:#333
    style H fill:#fce4ec,stroke:#333