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

feat: useLanguage sensor #2602

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
- [`useIdle`](./docs/useIdle.md) — tracks whether user is being inactive.
- [`useIntersection`](./docs/useIntersection.md) — tracks an HTML element's intersection. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-useintersection--demo)
- [`useKey`](./docs/useKey.md), [`useKeyPress`](./docs/useKeyPress.md), [`useKeyboardJs`](./docs/useKeyboardJs.md), and [`useKeyPressEvent`](./docs/useKeyPressEvent.md) — track keys. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usekeypressevent--demo)
- [`useLanguage`](./docs/useLanguage.md) — tracks the language of the document.
- [`useLocation`](./docs/useLocation.md) and [`useSearchParam`](./docs/useSearchParam.md) — tracks page navigation bar location state.
- [`useLongPress`](./docs/useLongPress.md) — tracks long press gesture of some element.
- [`useMedia`](./docs/useMedia.md) — tracks state of a CSS media query. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemedia--demo)
Expand Down
26 changes: 26 additions & 0 deletions docs/useLanguage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# `useLanguage`

React state hook that tracks the document's `lang` attribute.

## Usage

```jsx
import { useLanguage } from 'react-use';

const Demo = () => {
const [lang, setLang] = useLanguage();

return (
<div>
<div>
The document's current language is: {lang}
</div>

<br />

<button onClick={() => setLang("en-US") }>Change to US English</button>
<button onClick={() => setLang("es") }>Cambiar a español</button>
</div>
);
};
```
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export { default as createBreakpoint } from './factory/createBreakpoint';
// export { default as useKeyboardJs } from './useKeyboardJs';
export { default as useKeyPress } from './useKeyPress';
export { default as useKeyPressEvent } from './useKeyPressEvent';
export { default as useLanguage } from './useLanguage';
export { default as useLatest } from './useLatest';
export { default as useLifecycles } from './useLifecycles';
export { default as useList } from './useList';
Expand Down
2 changes: 2 additions & 0 deletions src/misc/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ export function off<T extends Window | Document | HTMLElement | EventTarget>(
export const isBrowser = typeof window !== 'undefined';

export const isNavigator = typeof navigator !== 'undefined';

export const isDocument = typeof document !== 'undefined';
34 changes: 34 additions & 0 deletions src/useLanguage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useEffect, useState, Dispatch, SetStateAction } from 'react';
import { isDocument } from './misc/util';

function useLanguage() {
const [lang, setLangState] = useState<string>(document.documentElement.lang);

const setLang: Dispatch<SetStateAction<string>> = (action) => {
if (typeof action === 'function') {
document.documentElement.lang = action(lang);
} else {
document.documentElement.lang = action;
}
};

useEffect(() => {
const handler = () => {
setLangState(document.documentElement.lang);
};

const observer = new MutationObserver(handler);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['lang'],
});

return () => observer.disconnect();
}, []);

return [lang, setLang] as const;
}

export default isDocument
? useLanguage
: () => ['', (_action: SetStateAction<string>) => {}] as const;
26 changes: 26 additions & 0 deletions stories/useLanguage.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { useLanguage } from '../src';
import NewTabStory from './util/NewTabStory';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const [lang, setLang] = useLanguage();

return (
<NewTabStory>
<div>
<div>The document's current language is: {lang}</div>

<br />

<button onClick={() => setLang('en-US')}>Change to US English</button>
<button onClick={() => setLang('es')}>Cambiar a español</button>
</div>
</NewTabStory>
);
};

storiesOf('Sensors/useLanguage', module)
.add('Docs', () => <ShowDocs md={require('../docs/useLanguage.md')} />)
.add('Demo', () => <Demo />);
25 changes: 25 additions & 0 deletions tests/useLanguage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { renderHook } from '@testing-library/react-hooks';
import useLanguage from '../src/useLanguage';

describe('useLanguage', () => {
it('should be defined', () => {
expect(useLanguage).toBeDefined();
});

it('should retrieve the document language', () => {
document.documentElement.lang = 'ar-SA';
const hook = renderHook(() => useLanguage());

expect(document.documentElement.lang).toBe(hook.result.current[0]);
});

it('should update document language', () => {
const hook = renderHook(() => useLanguage());

hook.result.current[1]('bn-BD');
expect(document.documentElement.lang).toBe('bn-BD');

hook.result.current[1]('bn-IN');
expect(document.documentElement.lang).toBe('bn-IN');
});
});