@procore/cdn-translations
v0.3.1
Published
Library that provides a solution to pull translations from CDN
Maintainers
Keywords
Readme
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:
Translation Requesters (
useRequestTranslations)- Components that need translations use the
useRequestTranslationshook - Handles translation lifecycle (pending/resolved/rejected)
- Implements timeout handling and cleanup
- Supports both file-based and folder-based translation structures
- Components that need translations use the
Translation Preloader (
usePreloadTranslations)- Proactively loads translations for better performance
- Works alongside the main translation request system
- Helps reduce perceived loading time
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
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
Translation Cache System
- browser-based caching for resolved translations
- Implements request tracking to handle concurrent requests
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
- Implements a robust pub/sub system using
Core Features
Translation Request Flow
- A component requests translations using
useRequestTranslations useSaveTranslations:- Checks for translations in nonStandardTranslations
- Applies locale overrides
- Sets up event subscriptions
- Manages request timeouts
useLoadTranslations:- Checks for translations in nonStandardTranslations
- Applies locale overrides
- Check if there is a similar request being fetched already
- Request the translation file
- Translations are received through the event system
- 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-revalidatefor 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