Skip to content

Commit

Permalink
Merge pull request #766 from vinissimus/canary
Browse files Browse the repository at this point in the history
Release 1.3
  • Loading branch information
aralroca authored Jan 23, 2022
2 parents a68ad2b + 2a2badf commit 4540b62
Show file tree
Hide file tree
Showing 27 changed files with 1,902 additions and 2,305 deletions.
32 changes: 31 additions & 1 deletion .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,37 @@
"contributions": [
"code"
]
}
},
{

"login": "testerez",
"name": "Tom Esterez",
"avatar_url": "https://avatars.githubusercontent.com/u/815236?v=4",
"profile": "https://github.com/testerez",
"contributions": [
"code"
]
},
{
"login": "dndhm",
"name": "Dan Needham",
"avatar_url": "https://avatars.githubusercontent.com/u/1122983?v=4",
"profile": "http://www.dan-needham.com",
"contributions": [
"code",
"test",
"doc"
]
},
{
"login": "bmvantunes",
"name": "Bruno Antunes",
"avatar_url": "https://avatars.githubusercontent.com/u/9042965?v=4",
"profile": "https://www.youtube.com/BrunoAntunesPT",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "next-translate",
Expand Down
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,14 @@ In the configuration file you can use both the configuration that we specified h
| `logBuild` | Each page has a log indicating: namespaces, current language and method used to load the namespaces. With this you can disable it. | `Boolean` | `true` |
| `loader` | If you wish to disable the webpack loader and manually load the namespaces on each page, we give you the opportunity to do so by disabling this option. | `Boolean` | `true` |
| `interpolation` | Change the delimeter that is used for interpolation. | `{prefix: string; suffix: string, formatter: function }` | `{prefix: '{{', suffix: '}}'}`
| `keySeparator` | Change the separator that is used for nested keys. Set to `false` to disable keys nesting in JSON translation files. Can be useful if you want to use natural text as keys. | `string` | `false` | `'.'`
| `nsSeparator` | char to split namespace from key. You should set it to `false` if you want to use natural text as keys. | `string` | `false` | `':'`
| `defaultNS` | default namespace used if not passed to `useTranslation` or in the translation key. | `string` | `undefined`
| `staticsHoc` | The HOCs we have in our API ([appWithI18n](#appwithi18n)), do not use [hoist-non-react-statics](https://github.com/mridgway/hoist-non-react-statics) in order not to include more kb than necessary _(static values different than getInitialProps in the pages are rarely used)_. If you have any conflict with statics, you can add hoist-non-react-statics (or any other alternative) here. [See an example](docs/hoist-non-react-statics.md). | `Function` | `null`
| `extensionsRgx` | Change the regex used by the webpack loader to find Next.js pages. | `Regex` | `/\.(tsx\|ts\|js\|mjs\|jsx)$/`
| `revalidate` | If you want to have a default revalidate on each page we give you the opportunity to do so by passing a number to revalidate. You can still define getStaticProps on a page with a different revalidate amount and override this default override. | `Number` | If you don't define it, by default the pages will have no revalidate.
| `pagesInDir` | If you run `next ./my-app` to change where your pages are, you can here define `my-app/pages` so that next-translate can guess where they are. | `String` | If you don't define it, by default the pages will be searched for in the classic places like `pages` and `src/pages`.

| `localesToIgnore` | Indicate these locales to ignore when you are prefixing the default locale using a middleware (in Next +12, [learn how to do it](https://nextjs.org/docs/advanced-features/i18n-routing#prefixing-the-default-locale)) | `Array<string>` | `['default']`

## 4. API

Expand Down Expand Up @@ -281,6 +284,7 @@ The `t` function:
- **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

### withTranslation
Expand Down Expand Up @@ -324,7 +328,7 @@ Sometimes we need to do some translations with HTML inside the text (bolds, link
Example:

```jsx
// The defined dictionary enter is like:
// The defined dictionary entry is like:
// "example": "<0>The number is <1>{{count}}</1></0>",
<Trans
i18nKey="common:example"
Expand All @@ -336,7 +340,7 @@ Example:
Or using `components` prop as a object:

```jsx
// The defined dictionary enter is like:
// The defined dictionary entry is like:
// "example": "<component>The number is <b>{{count}}</b></component>",
<Trans
i18nKey="common:example"
Expand All @@ -355,6 +359,31 @@ Or using `components` prop as a object:
- `values` - Object - query params
- `fallback` - string | string[] - Optional. Fallback i18nKey if the i18nKey doesn't match.
- `defaultTrans` - string - Default translation for the key. If fallback keys are used, it will be used only after exhausting all the fallbacks.
- `ns` - Namespace to use when none is embedded in `i18nKey`

In cases where we require the functionality of the `Trans` component, but need a **string** to be interpolated, rather than the output of the `t(props.i18nKey)` function, there is also a `TransText` component, which takes a `text` prop instead of `i18nKey`.

- **Props**:
- `text` - string - The string which (optionally) contains tags requiring interpolation
- `components` - Array | Object - This behaves exactly the same as `Trans` (see above).

This is especially useful when mapping over the output of a `t()` with `returnObjects: true`:
```jsx
// The defined dictionary entry is like:
// "content-list": ["List of <link>things</link>", "with <em>tags</em>"]
const contentList = t('someNamespace:content-list', {}, { returnObjects: true });

{contentList.map((listItem: string) => (
<TransText
text={listItem}
components={{
link: <a href="some-url" />,
em: <em />,
}}
/>
)}

```
### DynamicNamespaces
Expand Down Expand Up @@ -576,7 +605,7 @@ or
}
```
> Intl.PluralRules API is **only available for modern browsers**, if you want to use it in legacy browsers you should add a [polyfill](https://github.com/eemeli/intl-pluralrules).
> Intl.PluralRules API is **only available for modern browsers**, if you want to use it in legacy browsers you should add a [polyfill](https://github.com/eemeli/intl-pluralrules).
## 6. Use HTML inside the translation
Expand Down Expand Up @@ -711,7 +740,7 @@ return {
interpolation: {
format: (value, format, lang) => {
if(format === 'number') return formatters[lang].format(value)
return value
return value
}
}
}
Expand Down Expand Up @@ -927,6 +956,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<tr>
<td align="center"><a href="https://its-just-nans.github.io"><img src="https://avatars.githubusercontent.com/u/56606507?v=4?s=100" width="100px;" alt=""/><br /><sub><b>n4n5</b></sub></a><br /><a href="https://github.com/vinissimus/next-translate/commits?author=Its-Just-Nans" title="Documentation">📖</a></td>
<td align="center"><a href="https://rubenmoya.dev"><img src="https://avatars.githubusercontent.com/u/905225?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rubén Moya</b></sub></a><br /><a href="https://github.com/vinissimus/next-translate/commits?author=rubenmoya" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/testerez"><img src="https://avatars.githubusercontent.com/u/815236?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tom Esterez</b></sub></a><br /><a href="https://github.com/vinissimus/next-translate/commits?author=testerez" title="Code">💻</a></td>
<td align="center"><a href="http://www.dan-needham.com"><img src="https://avatars.githubusercontent.com/u/1122983?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dan Needham</b></sub></a><br /><a href="https://github.com/vinissimus/next-translate/commits?author=dndhm" title="Code">💻</a> <a href="https://github.com/vinissimus/next-translate/commits?author=dndhm" title="Tests">⚠️</a> <a href="https://github.com/vinissimus/next-translate/commits?author=dndhm" title="Documentation">📖</a></td>
<td align="center"><a href="https://www.youtube.com/BrunoAntunesPT"><img src="https://avatars.githubusercontent.com/u/9042965?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bruno Antunes</b></sub></a><br /><a href="https://github.com/vinissimus/next-translate/commits?author=bmvantunes" title="Code">💻</a></td>
</tr>
</table>
Expand Down
61 changes: 61 additions & 0 deletions __tests__/Trans.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,67 @@ describe('Trans', () => {
expect(container.textContent).toContain(expected)
})

test('should work with nested keys and custom keySeparator', () => {
const i18nKey = 'ns:parent_child'
const expected = 'The number is 42'
const withSingular = {
parent: {
child: 'The number is {{ num }}',
},
}

const config = { keySeparator: '_' }

const { container } = render(
<I18nProvider
lang="en"
namespaces={{ ns: withSingular }}
config={config}
>
<Trans i18nKey={i18nKey} values={{ num: 42 }} />
</I18nProvider>
)
expect(container.textContent).toContain(expected)
})

test('should work with no keySeparator', () => {
const i18nKey = 'ns:parent.child'
const expected = 'The number is 42'
const withSingular = {
'parent.child': 'The number is {{ num }}',
}

const config = { keySeparator: false }

const { container } = render(
<I18nProvider
lang="en"
namespaces={{ ns: withSingular }}
config={config}
>
<Trans i18nKey={i18nKey} values={{ num: 42 }} />
</I18nProvider>
)
expect(container.textContent).toContain(expected)
})

test('should work with ns prop', () => {
const i18nKey = 'number'
const expected = 'The number is 42'
const ns = {
number: 'The number is 42',
}

const config = { keySeparator: false }

const { container } = render(
<I18nProvider lang="en" namespaces={{ ns }} config={config}>
<Trans i18nKey={i18nKey} ns="ns" />
</I18nProvider>
)
expect(container.textContent).toContain(expected)
})

test('should work the same way than useTranslate with default value', () => {
const i18nKey = 'ns:number'
const expected = 'The number is 42'
Expand Down
127 changes: 127 additions & 0 deletions __tests__/TransText.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from 'react'
import { render, cleanup } from '@testing-library/react'

import TransText from '../src/TransText'

describe('TransText', () => {
afterEach(cleanup)

describe('without components', () => {
test('should return the provided text', () => {
const text = 'The number is 42'

const { container } = render(
<TransText text={text} />
)
expect(container.textContent).toContain(text)
})
})

describe('with components', () => {
test('should work with HTML5 Elements', () => {
const expectedText = 'The number is 42'
const text = '<0>The number is <1>42</1></0>'
const expectedHTML = '<h1 id="u1">The number is <b id="u2">42</b></h1>'

const { container } = render(
<TransText
text={text}
components={[<h1 id="u1" />, <b id="u2" />]}
/>
)
expect(container.textContent).toContain(expectedText)
expect(container.innerHTML).toContain(expectedHTML)
})

test('should work with React Components', () => {
const text = '<0>The number is <1>42</1></0>'
const expectedText = 'The number is 42'
const expectedHTML = '<h1>The number is <b>42</b></h1>'
const H1 = (p) => <h1 {...p} />
const B = (p) => <b {...p} />

const { container } = render(
<TransText
text={text}
components={[<H1 />, <B />]}
/>
)
expect(container.textContent).toContain(expectedText)
expect(container.innerHTML).toContain(expectedHTML)
})

test('should work with very nested components', () => {
const text = '<0><1>Is</1> <2>the <3>number</3></2> 42?</0>'
const expectedText = 'Is the number 42?'
const expectedHTML = '<div><p>Is</p> <b>the <i>number</i></b> 42?</div>'

const { container } = render(
<TransText
text={text}
components={[<div />, <p />, <b />, <i />]}
/>
)
expect(container.textContent).toContain(expectedText)
expect(container.innerHTML).toContain(expectedHTML)
})

test('should work without replacing the HTMLElement if the index is incorrectly', () => {
const text = 'test <10>with bad index</10>.'
const expectedHTML = 'test with bad index.'

const { container } = render(
<TransText
text={text}
components={[<b />]}
/>
)
expect(container.innerHTML).toContain(expectedHTML)
})
})

describe('components prop as a object', () => {
test('should work with component as a object', () => {
const text = 'test <example>components as a object</example>.'
const expectedHTML = 'test <b>components as a object</b>.'

const { container } = render(
<TransText
text={text}
components={{ example: <b /> }}
/>
)
expect(container.innerHTML).toContain(expectedHTML)
})

test('should work with component as a object of React Components', () => {
const text = 'test <example>components as a object</example>.'
const expectedHTML = 'test <b class="test">components as a object</b>.'

const Component = ({ children }) => <b className="test">{children}</b>

const { container } = render(
<TransText
text={text}
components={{ example: <Component /> }}
/>
)
expect(container.innerHTML).toContain(expectedHTML)
})

test('should work with component as a object without replacing the HTMLElement if the key is incorrect', () => {
const text = 'test <example>components <thisIsIncorrect>as <u>a</u> object</thisIsIncorrect></example>.'
const expectedHTML =
'test <b class="test">components as <u>a</u> object</b>.'

const Component = ({ children }) => <b className="test">{children}</b>

const { container } = render(
<TransText
text={text}
components={{ example: <Component />, u: <u /> }}
/>
)
expect(container.innerHTML).toContain(expectedHTML)
})
})
})
45 changes: 45 additions & 0 deletions __tests__/useTranslation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,51 @@ describe('useTranslation', () => {
expect(container.textContent).toBe(expected)
})

test('should work with custom nsSeparator', () => {
const Inner = () => {
const { t } = useTranslation('a')
return t`b||test`
}

const ns = {
a: { test: 'Test from A' },
b: { test: 'Test from B' },
}

const expected = 'Test from B'

const { container } = render(
<I18nProvider lang="en" namespaces={ns} config={{ nsSeparator: '||' }}>
<Inner />
</I18nProvider>
)
expect(container.textContent).toBe(expected)
})

test('should work with natural text as key and defaultNS', () => {
const Inner = () => {
const { t } = useTranslation()
return t`progress: loading...`
}

const ns = {
a: { 'progress: loading...': 'progression: chargement...' },
}

const expected = 'progression: chargement...'

const { container } = render(
<I18nProvider
lang="en"
namespaces={ns}
config={{ nsSeparator: false, keySeparator: false, defaultNS: 'a' }}
>
<Inner />
</I18nProvider>
)
expect(container.textContent).toBe(expected)
})

test('should work with a defined default namespace | t as function', () => {
const Inner = () => {
const { t } = useTranslation('a')
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "next build"
},
"dependencies": {
"next": "11.1.0",
"next": "12.0.3",
"next-translate": "link:../../",
"react": "link:../../node_modules/react",
"react-dom": "link:../../node_modules/react-dom"
Expand Down
Loading

0 comments on commit 4540b62

Please sign in to comment.