CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-i18next

React integration for i18next internationalization framework with hooks, components, and SSR support

Pending
Overview
Eval results
Files

hocs.mddocs/

Higher-Order Components

React i18next provides higher-order components (HOCs) for injecting translation functionality into class components and legacy React patterns, as well as server-side rendering support.

Capabilities

withTranslation HOC

Higher-order component that injects translation props into wrapped components, providing an alternative to hooks for class components or when hooks aren't suitable.

/**
 * HOC that injects translation props into wrapped components
 * @param ns - Namespace(s) for translations
 * @param options - Configuration options including ref forwarding and key prefix
 * @returns HOC function that wraps components with translation props
 */
function withTranslation<
  Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
  KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined
>(
  ns?: Ns,
  options?: {
    /** Enable ref forwarding to wrapped component */
    withRef?: boolean;
    /** Prefix for all translation keys */
    keyPrefix?: KPrefix;
  }
): <
  C extends React.ComponentType<React.ComponentProps<any> & WithTranslationProps>
>(
  component: C
) => React.ComponentType<Omit<React.ComponentProps<C>, keyof WithTranslation<Ns>> & WithTranslationProps>;

// Props injected by withTranslation HOC
interface WithTranslation<
  Ns extends FlatNamespace | $Tuple<FlatNamespace> | undefined = undefined,
  KPrefix extends KeyPrefix<FallbackNs<Ns>> = undefined
> {
  /** Translation function with type-safe keys */
  t: TFunction<FallbackNs<Ns>, KPrefix>;
  /** i18next instance for language changes and configuration */
  i18n: i18n;
  /** Indicates if translations are loaded and ready */
  tReady: boolean;
}

// Props that can be passed to wrapped component
interface WithTranslationProps {
  /** Custom i18next instance */
  i18n?: i18n;
  /** Enable React Suspense mode */
  useSuspense?: boolean;
}

Usage Examples:

import { withTranslation, WithTranslation } from "react-i18next";

// Class component with injected translation props
interface Props extends WithTranslation {
  username: string;
}

class UserProfile extends React.Component<Props> {
  handleLanguageChange = (lang: string) => {
    this.props.i18n.changeLanguage(lang);
  };

  render() {
    const { t, tReady, username } = this.props;
    
    if (!tReady) return <div>Loading...</div>;

    return (
      <div>
        <h1>{t('profile.title')}</h1>
        <p>{t('profile.welcome', { name: username })}</p>
        <button onClick={() => this.handleLanguageChange('es')}>
          {t('switchLanguage')}
        </button>
      </div>
    );
  }
}

export default withTranslation('user')(UserProfile);

// Functional component with HOC (alternative to useTranslation)
function Settings({ t, i18n, tReady }: WithTranslation) {
  if (!tReady) return <div>Loading...</div>;
  
  return (
    <div>
      <h2>{t('settings.title')}</h2>
      <button onClick={() => i18n.reloadResources()}>
        {t('settings.reload')}
      </button>
    </div>
  );
}

export default withTranslation('common')(Settings);

// With namespace and key prefix
const TranslatedComponent = withTranslation('dashboard', { 
  keyPrefix: 'widgets' 
})(function Widget({ t }) {
  return <div>{t('title')}</div>; // Resolves to 'dashboard:widgets.title'
});

// With ref forwarding
class RefComponent extends React.Component {
  focus() {
    // Component method
  }
  
  render() {
    const { t } = this.props;
    return <input placeholder={t('placeholder')} />;
  }
}

const TranslatedRefComponent = withTranslation('forms', { 
  withRef: true 
})(RefComponent);

// Usage with ref
function Parent() {
  const ref = useRef(null);
  
  return (
    <div>
      <TranslatedRefComponent ref={ref} />
      <button onClick={() => ref.current?.focus()}>
        Focus Input
      </button>
    </div>
  );
}

// Multiple namespaces
const MultiNSComponent = withTranslation(['common', 'user'])(
  function Profile({ t }) {
    return (
      <div>
        <h1>{t('user:title')}</h1>
        <button>{t('common:save')}</button>
      </div>
    );
  }
);

withSSR HOC

Higher-order component that adds server-side rendering support to components, including getInitialProps method for data fetching.

/**
 * HOC providing SSR support with getInitialProps method
 * @returns HOC function that adds SSR capabilities to wrapped components
 */
function withSSR(): <Props>(
  WrappedComponent: React.ComponentType<Props>
) => {
  (props: {
    initialI18nStore: Resource;
    initialLanguage: string;
  } & Props): React.FunctionComponentElement<Props>;
  getInitialProps: (ctx: unknown) => Promise<{
    initialI18nStore: Resource;
    initialLanguage: string;
  }>;
};

Usage Examples:

import { withSSR, WithTranslation, withTranslation } from "react-i18next";

// Component with SSR support
interface PageProps extends WithTranslation {
  data: any;
}

class HomePage extends React.Component<PageProps> {
  static async getInitialProps(ctx) {
    // Fetch page-specific data
    const data = await fetchPageData(ctx);
    return { data };
  }

  render() {
    const { t, data } = this.props;
    return (
      <div>
        <h1>{t('home.title')}</h1>
        <div>{data.content}</div>
      </div>
    );
  }
}

// Combine with translation HOC and SSR HOC
export default withSSR()(withTranslation('home')(HomePage));

// Functional component with SSR
function ProductPage({ product, t, initialI18nStore, initialLanguage }) {
  return (
    <div>
      <h1>{t('product.title', { name: product.name })}</h1>
      <p>{t('product.price', { price: product.price })}</p>
    </div>
  );
}

ProductPage.getInitialProps = async (ctx) => {
  const product = await fetchProduct(ctx.query.id);
  return { product };
};

export default withSSR()(withTranslation('products')(ProductPage));

// Next.js usage
export default function Page(props) {
  return <HomePage {...props} />;
}

export const getServerSideProps = HomePage.getInitialProps;

// Usage in Next.js App component
class MyApp extends App {
  static async getInitialProps(appContext) {
    const appProps = await App.getInitialProps(appContext);
    
    // Get SSR translation data
    const { Component } = appContext;
    if (Component.getInitialProps) {
      const pageProps = await Component.getInitialProps(appContext.ctx);
      return { ...appProps, pageProps };
    }
    
    return appProps;
  }

  render() {
    const { Component, pageProps } = this.props;
    return <Component {...pageProps} />;
  }
}

HOC Composition and Patterns

Combining HOCs

// Multiple HOCs composition
const EnhancedComponent = withSSR()(
  withTranslation('namespace')(
    MyComponent
  )
);

// With custom display names
function MyComponent({ t, customProp }) {
  return <div>{t('title')} - {customProp}</div>;
}

const Enhanced = withTranslation('custom')(MyComponent);
Enhanced.displayName = 'TranslatedMyComponent';

// Type-safe HOC composition
interface ComponentProps {
  customProp: string;
}

const TypedComponent: React.FC<ComponentProps & WithTranslation> = ({ t, customProp }) => (
  <div>{t('message')} {customProp}</div>
);

export default withTranslation()(TypedComponent);

Legacy Class Component Integration

// Traditional class component with lifecycle methods
class LegacyComponent extends React.Component<WithTranslation & { userId: string }> {
  componentDidMount() {
    const { i18n, userId } = this.props;
    
    // Load user-specific translations
    i18n.loadNamespaces(['user-specific']);
  }
  
  componentDidUpdate(prevProps) {
    const { i18n, userId } = this.props;
    
    if (prevProps.userId !== userId) {
      // Reload translations for new user
      i18n.reloadResources();
    }
  }
  
  render() {
    const { t, tReady, userId } = this.props;
    
    if (!tReady) {
      return <div>{t('loading')}</div>;
    }
    
    return (
      <div>
        <h1>{t('user.welcome', { id: userId })}</h1>
        <p>{t('user.status')}</p>
      </div>
    );
  }
}

export default withTranslation(['common', 'user'])(LegacyComponent);

Type Definitions

// Helper types for component props manipulation
type $Subtract<T extends K, K> = Omit<T, keyof K>;

// Fallback namespace resolution
type FallbackNs<Ns> = Ns extends undefined
  ? TypeOptions['defaultNS']
  : Ns extends Namespace
    ? Ns
    : TypeOptions['defaultNS'];

// HOC return type helpers
type HOCResult<C, InjectedProps> = React.ComponentType<
  Omit<React.ComponentProps<C>, keyof InjectedProps> & WithTranslationProps
>;

Migration from Class Components

When migrating from HOCs to hooks:

// Before: HOC pattern
class OldComponent extends React.Component<WithTranslation> {
  render() {
    const { t } = this.props;
    return <div>{t('message')}</div>;
  }
}
export default withTranslation()(OldComponent);

// After: Hook pattern  
function NewComponent() {
  const { t } = useTranslation();
  return <div>{t('message')}</div>;
}
export default NewComponent;

Performance Considerations

  • HOCs create wrapper components that may affect React DevTools display
  • Use withRef: true when you need to access wrapped component methods
  • Consider hooks for new components as they have less overhead
  • HOCs are still valuable for class components and complex composition patterns

Install with Tessl CLI

npx tessl i tessl/npm-react-i18next

docs

components.md

hocs.md

hooks.md

icu-macro.md

index.md

ssr.md

tile.json