Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accept an array as localesPath in useLocaleRequired, withLocaleRequired, and LocaleRequired #779

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/gasket-react-intl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ wrapped component will be rendered.

**Props**

- `localesPath` - (string|function) Path to endpoint with JSON files or
[thunk] that returns one. See more about [locales path] in the plugin docs.
- `localesPath` - (string|function|Array(string|function)) Path to endpoint with JSON files or
[thunk] that returns one. Also supports an array of either. See more about [locales path] in the plugin docs.
- `[options]` - (object) Optional configuration
- `loading` - (string|node) Content to render while loading, otherwise null.
- `initialProps` - (boolean) Enable `getInitialProps` to load locale files
Expand Down Expand Up @@ -99,8 +99,8 @@ content until a [split locales] file loads.

**Props**

- `localesPath` - (string|function) Path to endpoint with JSON files or
[thunk] that returns one. See more about [locales path] in the plugin docs.
- `localesPath` - (string|function|Array(string|function)) Path to endpoint with JSON files or
[thunk] that returns one. Also supports an array of either. See more about [locales path] in the plugin docs.
- `loading` - (string|node) Content to render while loading, otherwise null.

```jsx
Expand Down Expand Up @@ -134,8 +134,8 @@ hook will return the current loading status of the locale file.

**Props**

- `localesPath` - (string|function) Path to endpoint with JSON files or
[thunk] that returns one. See more about [locales path] in the plugin docs.
- `localesPath` - (string|function|Array(string|function)) Path to endpoint with JSON files or
[thunk] that returns one. Also supports an array of either. See more about [locales path] in the plugin docs.

```jsx
import { useLocaleRequired, LocaleStatus } from '@gasket/react-intl';
Expand Down
8 changes: 4 additions & 4 deletions packages/gasket-react-intl/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export type LocaleRequiredWrapper = (props: {
*/
export function withLocaleRequired<Props>(
/** Path containing locale files */
localePathPart?: LocalePathPartOrThunk,
localePathPart?: LocalePathPartOrThunk | LocalePathPartOrThunk[],
Copy link
Contributor

@jpage-godaddy jpage-godaddy Jun 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're interested, there's a MaybeMultiple (or perhaps it was called MaybeArray) type that you can import and use.

options?: {
/** Custom component to show while loading */
loading?: React.ReactNode;
Expand All @@ -50,7 +50,7 @@ export function withLocaleRequired<Props>(

export interface LocaleRequiredProps {
/** Path containing locale files */
localesPath: LocalePathPartOrThunk;
localesPath: LocalePathPartOrThunk | LocalePathPartOrThunk[];
/** Custom component to show while loading */
loading?: React.ReactNode;
}
Expand All @@ -67,7 +67,7 @@ export function LocaleRequired(
*/
export function useLocaleRequired(
/** Path containing locale files */
localePathPart: LocalePathPartOrThunk
localePathPart: LocalePathPartOrThunk | LocalePathPartOrThunk[]
): LocaleStatus;

interface NextStaticContext extends Record<string, any> {
Expand Down Expand Up @@ -148,7 +148,7 @@ export function attachGetInitialProps(
};
},
/** Path containing locale files */
localePathPart: LocalePathPartOrThunk
localePathPart: LocalePathPartOrThunk | LocalePathPartOrThunk[],
): void;

export async function attachedGetInitialProps(
Expand Down
100 changes: 55 additions & 45 deletions packages/gasket-react-intl/src/use-locale-required.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,63 @@ import { GasketIntlContext } from './context';
* React that fetches a locale file and returns loading status
* @type {import('./index').useLocaleRequired}
*/
export default function useLocaleRequired(localePathPart) {
export default function useLocaleRequired(localePathParam) {
const { locale, status = {}, dispatch } = useContext(GasketIntlContext);

// thunks are supported but with context will be browser-only (empty object)
const localePath = localeUtils.getLocalePath(localePathPart, locale);

const fileStatus = status[localePath];
if (fileStatus) return fileStatus;

// We cannot use dispatch from useReducer during SSR, so exit early.
// If you want a locale file to be ready, preload it to gasketIntl data
// or load with getStaticProps or getServerSideProps.
if (!isBrowser) return LocaleStatus.LOADING;

// Mutating status state to avoids an unnecessary render with using dispatch.
status[localePath] = LocaleStatus.LOADING;

const url = localeUtils.pathToUrl(localePath);

// Upon fetching, we will dispatch file status and messages to kick off a render.
fetch(url)
.then((r) =>
r.ok
? r.json()
: Promise.reject(
new Error(`Error loading locale file (${r.status}): ${url}`)
)
)
.then((messages) => {
dispatch({
type: LocaleStatus.LOADED,
payload: {
locale,
messages,
file: localePath
}
});
})
.catch((e) => {
console.error(e.message || e); // eslint-disable-line no-console
dispatch({
type: LocaleStatus.ERROR,
payload: {
file: localePath
}
if (!Array.isArray(localePathParam)) {
localePathParam = [localePathParam];
}

const loadingStatuses = localePathParam.map((localePathPart) => {
// thunks are supported but with context will be browser-only (empty object)
const localePath = localeUtils.getLocalePath(localePathPart, locale);

const fileStatus = status[localePath];
if (fileStatus) return fileStatus;

// We cannot use dispatch from useReducer during SSR, so exit early.
// If you want a locale file to be ready, preload it to gasketIntl data
// or load with getStaticProps or getServerSideProps.
if (!isBrowser) return LocaleStatus.LOADING;

// Mutating status state to avoids an unnecessary render with using dispatch.
status[localePath] = LocaleStatus.LOADING;

const url = localeUtils.pathToUrl(localePath);

// Upon fetching, we will dispatch file status and messages to kick off a render.
fetch(url)
.then((r) =>
r.ok
? r.json()
: Promise.reject(
new Error(`Error loading locale file (${r.status}): ${url}`)
)
)
.then((messages) => {
dispatch({
type: LocaleStatus.LOADED,
payload: {
locale,
messages,
file: localePath
}
});
})
.catch((e) => {
console.error(e.message || e); // eslint-disable-line no-console
dispatch({
type: LocaleStatus.ERROR,
payload: {
file: localePath
}
});
});
});

return LocaleStatus.LOADING;
return LocaleStatus.LOADING;
});

if (loadingStatuses.includes(LocaleStatus.ERROR)) return LocaleStatus.ERROR;
if (loadingStatuses.includes(LocaleStatus.LOADING)) return LocaleStatus.LOADING;
return LocaleStatus.LOADED;
}
50 changes: 50 additions & 0 deletions packages/gasket-react-intl/test/use-locale-required.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const { ERROR, LOADED, LOADING } = LocaleStatus;
// helper to wait for async actions
const pause = ms => new Promise((resolve) => setTimeout(resolve, ms));

// eslint-disable-next-line max-statements
describe('useLocaleRequired', function () {
let mockConfig, mockContext, dispatchMock;

Expand Down Expand Up @@ -115,6 +116,55 @@ describe('useLocaleRequired', function () {
expect(console.error).toHaveBeenCalledWith('Bad things man!');
});

describe('when localesPath is an array', () => {
it('accepts an array of locale paths, and fetches each path provided', () => {
const results = useLocaleRequired(['/locales', '/custom/locales', 'modules/module/locales']);
expect(results).toEqual(LOADING);
expect(fetch).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('/locales/en.json');
expect(fetch).toHaveBeenCalledWith('/custom/locales/en.json');
expect(fetch).toHaveBeenCalledWith('/modules/module/locales/en.json');
});

it('returns ERROR if any of the calls fail', () => {
mockContext.status['/locales/en.json'] = LOADED;
mockContext.status['/custom/locales/en.json'] = ERROR;
mockContext.status['/modules/module/locales/en.json'] = LOADING;

const result = useLocaleRequired(['/locales', '/custom/locales', 'modules/module/locales']);
expect(result).toEqual(ERROR);
});

it('returns LOADING if any of the calls are in progress and none have failed', () => {
mockContext.status['/locales/en.json'] = LOADED;
mockContext.status['/custom/locales/en.json'] = LOADED;
mockContext.status['/modules/module/locales/en.json'] = LOADING;

const result = useLocaleRequired(['/locales', '/custom/locales', 'modules/module/locales']);
expect(result).toEqual(LOADING);
});

it('returns LOADED if all calls succeed', () => {
mockContext.status['/locales/en.json'] = LOADED;
mockContext.status['/custom/locales/en.json'] = LOADED;
mockContext.status['/modules/module/locales/en.json'] = LOADED;

const result = useLocaleRequired(['/locales', '/custom/locales', 'modules/module/locales']);
expect(result).toEqual(LOADED);
});

it('handle array containing thunks', function () {
const mockThunk = jest.fn().mockReturnValue('/custom/locales');

const results = useLocaleRequired(['/locales', mockThunk, 'modules/module/locales']);
expect(results).toEqual(LOADING);
expect(fetch).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('/locales/en.json');
expect(fetch).toHaveBeenCalledWith('/custom/locales/en.json');
expect(fetch).toHaveBeenCalledWith('/modules/module/locales/en.json');
});
});

describe('SSR', function () {

beforeEach(function () {
Expand Down
Loading