-
Notifications
You must be signed in to change notification settings - Fork 688
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added string util for easy translations
Signed-off-by: Sahil <sahil@harness.io>
- Loading branch information
Showing
7 changed files
with
1,056 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import React from 'react'; | ||
import { get } from 'lodash-es'; | ||
import { render } from 'mustache'; | ||
import { useStringsContext, StringKeys } from './StringsContext'; | ||
|
||
export interface UseStringsReturn { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
getString(key: StringKeys, vars?: Record<string, any>): string; | ||
} | ||
|
||
export function useStrings(): UseStringsReturn { | ||
const { data: strings, getString } = useStringsContext(); | ||
|
||
return { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
getString(key: StringKeys, vars: Record<string, any> = {}) { | ||
if (typeof getString === 'function') { | ||
return getString(key, vars); | ||
} | ||
|
||
const template = get(strings, key); | ||
|
||
if (typeof template !== 'string') { | ||
throw new Error(`No valid template with id "${key}" found in any namespace`); | ||
} | ||
|
||
return render(template, { ...vars, $: strings }); | ||
} | ||
}; | ||
} | ||
|
||
export interface StringProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> { | ||
stringID: StringKeys; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
vars?: Record<string, any>; | ||
useRichText?: boolean; | ||
tagName: keyof JSX.IntrinsicElements; | ||
} | ||
|
||
export function String(props: StringProps): React.ReactElement | null { | ||
const { stringID, vars, useRichText, tagName: Tag, ...rest } = props; | ||
const { getString } = useStrings(); | ||
|
||
try { | ||
const text = getString(stringID, vars); | ||
|
||
return useRichText ? ( | ||
<Tag {...(rest as unknown)} dangerouslySetInnerHTML={{ __html: text }} /> | ||
) : ( | ||
<Tag {...(rest as unknown)}>{text}</Tag> | ||
); | ||
} catch (e) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
return <Tag style={{ color: 'var(--red-500)' }}>{(e as any).message}</Tag>; | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
|
||
String.defaultProps = { | ||
tagName: 'span' | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react'; | ||
|
||
import type { StringsMap } from './types'; | ||
|
||
export type StringKeys = keyof StringsMap; | ||
|
||
export type { StringsMap }; | ||
|
||
export interface StringsContextValue { | ||
data: StringsMap; | ||
getString?(key: StringKeys, vars?: Record<string, any>): string; | ||
} | ||
|
||
export const StringsContext = React.createContext<StringsContextValue>({} as StringsContextValue); | ||
|
||
export function useStringsContext(): StringsContextValue { | ||
return React.useContext(StringsContext); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from 'react'; | ||
|
||
import { StringsContext, StringsContextValue, StringsMap } from './StringsContext'; | ||
|
||
export interface StringsContextProviderProps extends Pick<StringsContextValue, 'getString'> { | ||
children: React.ReactNode; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
initialStrings?: Record<string, any>; // temp prop for backward compatability | ||
} | ||
|
||
export function StringsContextProvider(props: StringsContextProviderProps): React.ReactElement { | ||
return ( | ||
<StringsContext.Provider | ||
value={{ | ||
data: { | ||
...(props.initialStrings as StringsMap) | ||
}, | ||
getString: props.getString | ||
}} | ||
> | ||
{props.children} | ||
</StringsContext.Provider> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
|
||
import { String, useStrings } from '../String'; | ||
import { StringsContext } from '../StringsContext'; | ||
|
||
const value = { | ||
data: { | ||
a: { b: 'Test Value 1' }, | ||
chaos: 'Chaos', | ||
test: '{{ $.a.b }} in template', | ||
test2: '{{ $.test }} again' | ||
} | ||
}; | ||
describe('String tests', () => { | ||
test('renders strings with simple id', () => { | ||
const { container } = render( | ||
<StringsContext.Provider value={value as any}> | ||
<String stringID={'chaos' as any} /> | ||
</StringsContext.Provider> | ||
); | ||
|
||
expect(container).toMatchInlineSnapshot(` | ||
<div> | ||
<span> | ||
Chaos | ||
</span> | ||
</div> | ||
`); | ||
}); | ||
|
||
test('renders error when key not found', () => { | ||
const { container } = render( | ||
<StringsContext.Provider value={value as any}> | ||
<String stringID={'chaos' as any} /> | ||
</StringsContext.Provider> | ||
); | ||
|
||
expect(container).toMatchInlineSnapshot(` | ||
<div> | ||
<span> | ||
Chaos | ||
</span> | ||
</div> | ||
`); | ||
}); | ||
|
||
test('renders strings with nested value', () => { | ||
const { container } = render( | ||
<StringsContext.Provider value={value as any}> | ||
<String stringID={'a.b' as any} /> | ||
</StringsContext.Provider> | ||
); | ||
|
||
expect(container).toMatchInlineSnapshot(` | ||
<div> | ||
<span> | ||
Test Value 1 | ||
</span> | ||
</div> | ||
`); | ||
}); | ||
|
||
test('renders strings with self reference values', () => { | ||
const { container } = render( | ||
<StringsContext.Provider value={value as any}> | ||
<String stringID={'test' as any} /> | ||
</StringsContext.Provider> | ||
); | ||
|
||
expect(container).toMatchInlineSnapshot(` | ||
<div> | ||
<span> | ||
Test Value 1 in template | ||
</span> | ||
</div> | ||
`); | ||
}); | ||
|
||
test('self reference only works for one level', () => { | ||
const { container } = render( | ||
<StringsContext.Provider value={value as any}> | ||
<String stringID={'test2' as any} /> | ||
</StringsContext.Provider> | ||
); | ||
|
||
expect(container).toMatchInlineSnapshot(` | ||
<div> | ||
<span> | ||
{{ $.a.b }} in template again | ||
</span> | ||
</div> | ||
`); | ||
}); | ||
}); | ||
|
||
describe('useString tests', () => { | ||
describe('getString', () => { | ||
test('works with simple id', () => { | ||
const wrapper = ({ children }: { children: React.ReactElement }): React.ReactElement => ( | ||
<StringsContext.Provider value={value as any}>{children}</StringsContext.Provider> | ||
); | ||
const { result } = renderHook(() => useStrings(), { wrapper }); | ||
|
||
expect(result.current.getString('chaos' as any)).toMatchInlineSnapshot(`"Chaos"`); | ||
}); | ||
|
||
test('works with nested values', () => { | ||
const wrapper = ({ children }: { children: React.ReactElement }): React.ReactElement => ( | ||
<StringsContext.Provider value={value as any}>{children}</StringsContext.Provider> | ||
); | ||
const { result } = renderHook(() => useStrings(), { wrapper }); | ||
|
||
expect(result.current.getString('a.b' as any)).toMatchInlineSnapshot(`"Test Value 1"`); | ||
}); | ||
|
||
test('works with self reference values', () => { | ||
const wrapper = ({ children }: { children: React.ReactElement }): React.ReactElement => ( | ||
<StringsContext.Provider value={value as any}>{children}</StringsContext.Provider> | ||
); | ||
const { result } = renderHook(() => useStrings(), { wrapper }); | ||
|
||
expect(result.current.getString('test' as any)).toMatchInlineSnapshot(`"Test Value 1 in template"`); | ||
}); | ||
|
||
test('self reference works foor only one level', () => { | ||
const wrapper = ({ children }: { children: React.ReactElement }): React.ReactElement => ( | ||
<StringsContext.Provider value={value as any}>{children}</StringsContext.Provider> | ||
); | ||
const { result } = renderHook(() => useStrings(), { wrapper }); | ||
|
||
expect(result.current.getString('test2' as any)).toMatchInlineSnapshot(`"{{ $.a.b }} in template again"`); | ||
}); | ||
}); | ||
|
||
test('Works with custom getString', () => { | ||
const { container } = render( | ||
<StringsContext.Provider value={{ ...value, getString: (key: string) => key } as any}> | ||
<String stringID={'chaos.foo.bar.baz' as any} /> | ||
</StringsContext.Provider> | ||
); | ||
|
||
expect(container).toMatchInlineSnapshot(` | ||
<div> | ||
<span> | ||
chaos.foo.bar.baz | ||
</span> | ||
</div> | ||
`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { useStrings, String } from './String'; | ||
export type { UseStringsReturn } from './String'; | ||
export { useStringsContext, StringsContext } from './StringsContext'; | ||
export type { StringKeys } from './StringsContext'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
litmus: litmus |
Oops, something went wrong.