diff --git a/README.md b/README.md index ad161392..6f2c3842 100644 --- a/README.md +++ b/README.md @@ -427,6 +427,50 @@ Remember that `['dynamic']` namespace should **not** be listed on `pages` config - `fallback`- ReactNode - Fallback to display meanwhile the namespaces are loading. - **Optional**. - `dynamic` - function - By default it uses the [loadLocaleFrom](#3-configuration) in the configuration to load the namespaces, but you can specify another destination. - **Optional**. +### useDynamicTranslation + +**Size**: ~?kb 📦 + +It's a dynamic alternative to `useTranslation` hook, combining its functionality with `DynamicNamespaces`. As a hook, it does not provide a fallback by itself, but returns a `ready` value instead, around which you can create your own logic. + +Namespaces from the `pages` configuration are immediately available. + +Example: + +```jsx +import React from 'react' +import useDynamicTranslation from 'next-translate/useDynamicTranslation' + +export default function Description() { + const { t, ready } = useDynamicTranslation("dynamic") // dynamic namespace (required) + + const title = t("dynamic:title") // Available once ready === true + const description = t("dynamic:description") + const loading = t("common:loading") // Available from the first render + + return (ready ? + <> +

{title}

+

{description}

+ + ) :

{loading}

+} +``` + +The `useDynamicTranslation` hook accepts a namespace like `useTranslation`, but also a loader function as a second parameter, like the `dynamic` prop in `DynamicNamespaces`. + +The `t` function has the same signature as `t` from `useTranslation`: + +- **Input**: + - **i18nKey**: string (namespace:key) + - **query**: Object _(optional)_ (example: { name: 'Leonard' }) + - **options**: Object _(optional)_ + - **fallback**: string | string[] - fallback if i18nKey doesn't exist. [See more](#8-fallbacks). + - **returnObjects**: boolean - Get part of the JSON with all the translations. [See more](#7-nested-translations). + - **default**: string - Default translation for the key. If fallback keys are used, it will be used only after exhausting all the fallbacks. + - **ns**: string - Namespace to use when none is embded in the `i18nKey`. +- **Output**: string + ### getT **Size**: ~1.3kb 📦 diff --git a/package.json b/package.json index a2c364cf..aa0381c9 100644 --- a/package.json +++ b/package.json @@ -40,13 +40,14 @@ "Trans*", "withTranslation*", "useTranslation*", + "useDynamicTranslation*", "setLanguage*", "index*" ], "scripts": { "build": "yarn clean && cross-env NODE_ENV=production && yarn tsc", "clean": "yarn clean:build && yarn clean:examples", - "clean:build": "rm -rf lib plugin appWith* Dynamic* I18n* index _context loadNa* setLang* Trans* useT* withT* getP* getC* *.d.ts getT transC* wrapT* types formatElements", + "clean:build": "rm -rf lib plugin appWith* Dynamic* I18n* index _context loadNa* setLang* Trans* useD* useT* withT* getP* getC* *.d.ts getT transC* wrapT* types formatElements", "clean:examples": "rm -rf examples/**/.next && rm -rf examples/**/node_modules && rm -rf examples/**/yarn.lock", "example": "yarn example:complex", "example:basic": "yarn build && cd examples/basic && yarn && yarn dev", diff --git a/src/index.tsx b/src/index.tsx index edf8f671..c39eee35 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -22,6 +22,12 @@ export interface I18n { lang: string } +export interface DynamicI18n { + t: Translate + lang: string + ready: boolean +} + export interface I18nProviderProps { lang?: string namespaces?: Record diff --git a/src/useDynamicTranslation.tsx b/src/useDynamicTranslation.tsx new file mode 100644 index 00000000..9bc3c243 --- /dev/null +++ b/src/useDynamicTranslation.tsx @@ -0,0 +1,50 @@ +import { useContext, useEffect, useMemo, useState } from 'react' +import { DynamicI18n, I18nConfig, LocaleLoader, I18nDictionary } from '.' +import transCore from './transCore' +import { InternalContext } from './I18nProvider' +import I18nContext from './_context' + +export default function useDynamicTranslation( + defaultNS: string, + dynamic?: LocaleLoader +): DynamicI18n { + const [ready, setReady] = useState(false) + const internal = useContext(InternalContext) + const ctx = useContext(I18nContext) + const config = internal.config as I18nConfig + const [pageNs, setPageNs] = useState(null) + const loadLocale = + dynamic || config.loadLocaleFrom || (() => Promise.resolve({})) + + async function loadNamespaces() { + if (typeof loadLocale !== 'function') return + + const nameSpace = await loadLocale(ctx.lang, defaultNS) + setPageNs(nameSpace) + setReady(true) + } + + useEffect(() => { + if (!defaultNS) { + setReady(true) + return + } + loadNamespaces() + }, [defaultNS]) + + return useMemo(() => { + const allNamespaces: Record = { + ...internal.ns, + ...(pageNs && { [defaultNS]: pageNs }), + } + const localesToIgnore = config.localesToIgnore || ['default'] + const ignoreLang = localesToIgnore.includes(ctx.lang) + const pluralRules = new Intl.PluralRules(ignoreLang ? undefined : ctx.lang) + + return { + ...ctx, + t: transCore({ config, allNamespaces, pluralRules, lang: ctx.lang }), + ready, + } + }, [ctx.lang, defaultNS, ready, pageNs]) +}