# 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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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:

```typescript
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:

```typescript
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

```mermaid
%%{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
```
