Skip to content

Commit

Permalink
Merge branch 'aralroca:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
hydRAnger authored Nov 28, 2023
2 parents 6c2143d + 6696e3a commit 13f2670
Show file tree
Hide file tree
Showing 10 changed files with 626 additions and 30 deletions.
57 changes: 56 additions & 1 deletion .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,60 @@
"contributions": [
"code"
]
},
{
"login": "danielpid",
"name": "dANi",
"avatar_url": "https://avatars.githubusercontent.com/u/16427301?v=4",
"profile": "https://github.com/danielpid",
"contributions": [
"code"
]
},
{
"login": "TheMatrixan",
"name": "Mateusz Lesiak",
"avatar_url": "https://avatars.githubusercontent.com/u/28862367?v=4",
"profile": "https://thematrixan.github.io/",
"contributions": [
"code"
]
},
{
"login": "Curetix",
"name": "Curetix",
"avatar_url": "https://avatars.githubusercontent.com/u/15160542?v=4",
"profile": "https://github.com/Curetix",
"contributions": [
"doc"
]
},
{
"login": "crs1138",
"name": "Honza",
"avatar_url": "https://avatars.githubusercontent.com/u/1313681?v=4",
"profile": "https://github.com/crs1138",
"contributions": [
"maintenance"
]
},
{
"login": "BandhiyaHardik",
"name": "HardikBandhiya",
"avatar_url": "https://avatars.githubusercontent.com/u/110784317?v=4",
"profile": "https://github.com/BandhiyaHardik",
"contributions": [
"doc"
]
},
{
"login": "timotew",
"name": "Tim O. Peters",
"avatar_url": "https://avatars.githubusercontent.com/u/12928383?v=4",
"profile": "https://www.linkedin.com/in/timotew/",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
Expand All @@ -493,5 +547,6 @@
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true,
"commitConvention": "angular"
"commitConvention": "angular",
"commitType": "docs"
}
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<a href="https://github.com/aralroca/next-translate/actions?query=workflow%3ACI" alt="Tests status">
<img src="https://github.com/aralroca/next-translate/workflows/CI/badge.svg" /></a>
<a href="https://twitter.com/intent/follow?screen_name=aralroca">
<img src="https://img.shields.io/twitter/follow/aralroca?style=social&logo=twitter"
<img src="https://img.shields.io/twitter/follow/aralroca?style=social&logo=x"
alt="follow on Twitter"></a>

</div>
Expand All @@ -34,6 +34,7 @@
- [3. Configuration](#3-configuration)
- [4. API](#4-api)
- [useTranslation](#usetranslation)
- [createTranslation](#createtranslation)
- [withTranslation](#withtranslation)
- [Trans Component](#trans-component)
- [DynamicNamespaces](#dynamicnamespaces)
Expand Down Expand Up @@ -299,6 +300,15 @@ The `t` function:
- **ns**: string - Namespace to use when none is embded in the `i18nKey`.
- **Output**: string

### createTranslation

Similar than `useTranslation` but without being a hook. This helper **only works** in **app dir**.

```ts
const { t, lang } = createTranslation('ns1') // default namespace (optional)
const title = t('title')
```

### withTranslation

**Size**: ~560b 📦
Expand Down Expand Up @@ -1135,6 +1145,14 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="http://teka.dev"><img src="https://avatars.githubusercontent.com/u/4443094?v=4?s=100" width="100px;" alt="Marcelo Oliveira"/><br /><sub><b>Marcelo Oliveira</b></sub></a><br /><a href="https://github.com/aralroca/next-translate/commits?author=marcelotk15" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SimplyComplexable"><img src="https://avatars.githubusercontent.com/u/8563846?v=4?s=100" width="100px;" alt="Zack Sunderland"/><br /><sub><b>Zack Sunderland</b></sub></a><br /><a href="https://github.com/aralroca/next-translate/commits?author=SimplyComplexable" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://andrewovens.com"><img src="https://avatars.githubusercontent.com/u/107420510?v=4?s=100" width="100px;" alt="Andrew Ovens"/><br /><sub><b>Andrew Ovens</b></sub></a><br /><a href="https://github.com/aralroca/next-translate/commits?author=aovens-quantifi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/danielpid"><img src="https://avatars.githubusercontent.com/u/16427301?v=4?s=100" width="100px;" alt="dANi"/><br /><sub><b>dANi</b></sub></a><br /><a href="https://github.com/aralroca/next-translate/commits?author=danielpid" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://thematrixan.github.io/"><img src="https://avatars.githubusercontent.com/u/28862367?v=4?s=100" width="100px;" alt="Mateusz Lesiak"/><br /><sub><b>Mateusz Lesiak</b></sub></a><br /><a href="https://github.com/aralroca/next-translate/commits?author=TheMatrixan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Curetix"><img src="https://avatars.githubusercontent.com/u/15160542?v=4?s=100" width="100px;" alt="Curetix"/><br /><sub><b>Curetix</b></sub></a><br /><a href="https://github.com/aralroca/next-translate/commits?author=Curetix" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/crs1138"><img src="https://avatars.githubusercontent.com/u/1313681?v=4?s=100" width="100px;" alt="Honza"/><br /><sub><b>Honza</b></sub></a><br /><a href="#maintenance-crs1138" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BandhiyaHardik"><img src="https://avatars.githubusercontent.com/u/110784317?v=4?s=100" width="100px;" alt="HardikBandhiya"/><br /><sub><b>HardikBandhiya</b></sub></a><br /><a href="https://github.com/aralroca/next-translate/commits?author=BandhiyaHardik" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/timotew/"><img src="https://avatars.githubusercontent.com/u/12928383?v=4?s=100" width="100px;" alt="Tim O. Peters"/><br /><sub><b>Tim O. Peters</b></sub></a><br /><a href="https://github.com/aralroca/next-translate/commits?author=timotew" title="Code">💻</a></td>
</tr>
</tbody>
</table>
Expand Down
41 changes: 41 additions & 0 deletions docs/type-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Type safety consuming translations (TypeScript only)

To enable type safety consuming translations, you have to add this to the `next-translate.d.ts` file specifying the name and location of the namespaces:

```ts
import type { Paths, I18n, Translate } from 'next-translate'

type Tail<T> = T extends [unknown, ...infer Rest] ? Rest : never;

export interface TranslationsKeys {
// Example with "common" and "home" namespaces in "en" (the default language):
common: Paths<typeof import('./locales/en/common.json')>
home: Paths<typeof import('./locales/en/home.json')>
// Specify here all the namespaces you have...
}

export interface TypeSafeTranslate<Namespace extends keyof TranslationsKeys>
extends Omit<I18n, 't'> {
t: {
(
key: TranslationsKeys[Namespace],
...rest: Tail<Parameters<Translate>>
): string
<T extends string>(template: TemplateStringsArray): string
}
}

declare module 'next-translate/useTranslation' {
export default function useTranslation<
Namespace extends keyof TranslationsKeys
>(namespace: Namespace): TypeSafeTranslate<Namespace>
}
```
Then type safety should work:
<img width="472" alt="Screenshot 2023-07-17 at 19 17 13" src="https://github.com/aralroca/next-translate/assets/13313058/e9e505a7-4cc5-41e3-b2e4-b7f27fb2d181">
<img width="282" alt="Screenshot 2023-07-17 at 19 22 00" src="https://github.com/aralroca/next-translate/assets/13313058/616987b4-e49b-4cf2-b511-cdfaba57e1d2">
Reference: https://github.com/aralroca/next-translate/pull/1108
23 changes: 23 additions & 0 deletions examples/with-app-directory/next-translate.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Paths, I18n, Translate } from 'next-translate'

export interface TranslationsKeys {
common: Paths<typeof import('./locales/en/common.json')>
home: Paths<typeof import('./locales/en/home.json')>
}

export interface TypeSafeTranslate<Namespace extends keyof TranslationsKeys>
extends Omit<I18n, 't'> {
t: {
(
key: TranslationsKeys[Namespace],
...rest: Tail<Parameters<Translate>>
): string
<T extends string>(template: TemplateStringsArray): string
}
}

declare module 'next-translate/useTranslation' {
export default function useTranslation<
Namespace extends keyof TranslationsKeys
>(namespace: Namespace): TypeSafeTranslate<Namespace>
}
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next-translate",
"version": "2.4.4",
"version": "2.6.2",
"description": "Tiny and powerful i18n tools (Next plugin + API) to translate your Next.js pages.",
"license": "MIT",
"keywords": [
Expand Down Expand Up @@ -42,13 +42,14 @@
"useTranslation*",
"setLanguage*",
"index*",
"AppDirI18nProvider*"
"AppDirI18nProvider*",
"createTranslation*"
],
"scripts": {
"build": "yarn clean && cross-env NODE_ENV=production && yarn tsc",
"clean": "yarn clean:build && yarn clean:examples",
"clean:build": "rm -rf lib appWith* Dynamic* I18n* index context loadNa* setLang* Trans* useT* withT* getP* getC* *.d.ts getT transC* wrapT* types formatElements AppDirI18nProvider*",
"clean:examples": "rm -rf examples/**/.next && rm -rf examples/**/node_modules && rm -rf examples/**/yarn.lock",
"clean:build": "del lib appWith* Dynamic* I18n* index context loadNa* setLang* Trans* useT* withT* getP* getC* *.d.ts getT transC* wrapT* types formatElements AppDirI18nProvider* createTrans*",
"clean:examples": "del examples/**/.next examples/**/node_modules examples/**/yarn.lock",
"example": "yarn example:complex",
"example:basic": "yarn build && yarn --cwd examples/basic && yarn --cwd examples/basic dev",
"example:complex": "yarn build && yarn --cwd examples/complex && yarn --cwd examples/complex dev",
Expand Down Expand Up @@ -78,6 +79,7 @@
"babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
"babel-preset-minify": "0.5.2",
"cross-env": "7.0.3",
"del-cli": "^5.0.0",
"express": "4.18.2",
"husky": "7.0.4",
"jest": "27.3.1",
Expand Down
2 changes: 1 addition & 1 deletion src/I18nProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function I18nProvider({
const lang = lng || parentLang || locale || defaultLocale || ''
const config = { ...internal.config, ...newConfig }
const localesToIgnore = config.localesToIgnore || ['default']
const ignoreLang = localesToIgnore.includes(lang)
const ignoreLang = !lang || localesToIgnore.includes(lang)
const pluralRules = new Intl.PluralRules(ignoreLang ? undefined : lang)
const t = transCore({ config, allNamespaces, pluralRules, lang })

Expand Down
17 changes: 17 additions & 0 deletions src/createTranslation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import transCore from './transCore'
import wrapTWithDefaultNs from './wrapTWithDefaultNs'

// Only for App directory
export default function createTranslation(defaultNS?: string) {
const { lang, namespaces, config } = globalThis.__NEXT_TRANSLATE__ ?? {}
const localesToIgnore = config.localesToIgnore || ['default']
const ignoreLang = !lang || localesToIgnore.includes(lang)
const t = transCore({
config,
allNamespaces: namespaces,
pluralRules: new Intl.PluralRules(ignoreLang ? undefined : lang),
lang,
})

return { t: wrapTWithDefaultNs(t, defaultNS), lang }
}
53 changes: 53 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,59 @@ declare global {
}
}

//////// For type safety (next-translate.d.ts): ///////////
/*
*
* import type { Paths, I18n, Translate } from "next-translate";
*
* export interface TranslationsKeys {
* common: Paths<typeof import("./locales/en/common.json")>;
* home: Paths<typeof import("./locales/en/home.json")>;
* }
*
* export interface TypeSafeTranslate<Namespace extends keyof TranslationsKeys>
* extends Omit<I18n, "t"> {
* t: {
* (key: TranslationsKeys[Namespace], ...rest: Tail<Parameters<Translate>>): string;
* <T extends string>(template: TemplateStringsArray): string;
* };
* }
*
* declare module "next-translate/useTranslation" {
* export default function useTranslation<
* Namespace extends keyof TranslationsKeys,
* >(namespace: Namespace): TypeSafeTranslate<Namespace>;
* }
*/

type RemovePlural<Key extends string> = Key extends `${infer Prefix}${
| '_zero'
| '_one'
| '_two'
| '_few'
| '_many'
| '_other'
| `_${number}`}`
? Prefix
: Key

type Join<S1, S2> = S1 extends string
? S2 extends string
? `${S1}.${S2}`
: never
: never

// @ts-ignore
export type Paths<T> = RemovePlural<
// @ts-ignore
{
// @ts-ignore
[K in Extract<keyof T, string>]: T[K] extends Record<string, unknown>
? Join<K, Paths<T[K]>>
: K
}[Extract<keyof T, string>]
>

// TODO: Remove this in future versions > 2.0.0
function nextTranslate(nextConfig: NextConfig = {}): NextConfig {
console.log(`
Expand Down
18 changes: 2 additions & 16 deletions src/useTranslation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useContext, useMemo } from 'react'
import { I18n } from '.'
import wrapTWithDefaultNs from './wrapTWithDefaultNs'
import I18nContext from './context'
import transCore from './transCore'
import createTranslation from './createTranslation'

function useTranslationInPages(defaultNS?: string): I18n {
const ctx = useContext(I18nContext)
Expand All @@ -15,22 +15,8 @@ function useTranslationInPages(defaultNS?: string): I18n {
)
}

function useTranslationAppDir(defaultNS?: string) {
const { lang, namespaces, config } = globalThis.__NEXT_TRANSLATE__ ?? {}
const localesToIgnore = config.localesToIgnore || ['default']
const ignoreLang = localesToIgnore.includes(lang)
const t = transCore({
config,
allNamespaces: namespaces,
pluralRules: new Intl.PluralRules(ignoreLang ? undefined : lang),
lang,
})

return { t: wrapTWithDefaultNs(t, defaultNS), lang }
}

export default function useTranslation(defaultNS?: string): I18n {
const appDir = globalThis.__NEXT_TRANSLATE__
const useT = appDir?.config ? useTranslationAppDir : useTranslationInPages
const useT = appDir?.config ? createTranslation : useTranslationInPages
return useT(defaultNS)
}
Loading

0 comments on commit 13f2670

Please sign in to comment.