UNPKG

ra-core

Version:

Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React

123 lines (114 loc) 4.34 kB
import { useState, useMemo } from 'react'; import set from 'lodash/set'; import get from 'lodash/get'; import cloneDeep from 'lodash/cloneDeep'; import { TranslatableContextValue } from './TranslatableContext'; import { useLocaleState } from './useLocaleState'; /** * Hook supplying the logic to translate a field value in multiple languages. * * @param options The hook options * @param {string} options.defaultLocale The locale of the default selected locale. Defaults to 'en'. * @param {string[]} options.locales An array of the supported locales. Each is an object with a locale and a name property. For example { locale: 'en', name: 'English' }. * * @returns * An object with following properties and methods: * - selectedLocale: The locale of the currently selected locale * - locales: An array of the supported locales * - getLabel: A function which returns the translated label for the given field * - getSource: A function which returns the source for the given field * - selectLocale: A function which set the selected locale */ export const useTranslatable = ( options: UseTranslatableOptions ): TranslatableContextValue => { const [localeFromUI] = useLocaleState(); const { defaultLocale = localeFromUI, locales } = options; const [selectedLocale, setSelectedLocale] = useState(defaultLocale); const context = useMemo<TranslatableContextValue>( () => ({ locales, selectedLocale: selectedLocale || 'en', selectLocale: setSelectedLocale, getRecordForLocale, }), [locales, selectedLocale] ); return context; }; export type UseTranslatableOptions = { defaultLocale?: string; locales: string[]; }; /** * Returns a record where translatable fields have their values set to the value of the given locale. * This is necessary because the fields rely on the RecordContext to get their values and have no knowledge of the locale. * * Given the record { title: { en: 'title_en', fr: 'title_fr' } } and the locale 'fr', * the record for the locale 'fr' will be { title: 'title_fr' } */ export const getRecordForLocale = (record: {} | undefined, locale: string) => { if (!record) { return record; } // Get all paths of the record const paths = getRecordPaths(record); // For each path, if a path ends with the locale, set the value of the path without the locale // to the value of the path with the locale const recordForLocale = paths.reduce((acc, path) => { if (path.includes(locale)) { const pathWithoutLocale = path.slice(0, -1); const value = get(record, path); return set(acc, pathWithoutLocale, value); } return acc; }, cloneDeep(record)); return recordForLocale; }; // Return all the possible paths of the record as an array of arrays // For example, given the record // { // title: { en: 'title_en', fr: 'title_fr' }, // items: [ // { description: { en: 'item1_en', fr: 'item1_fr' } }, // { description: { en: 'item2_en', fr: 'item2_fr' } } // ] // }, // the paths will be // [ // ['title'], // ['title', 'en'], // ['title', 'fr'], // ['items'], // ['items', '0'], // ['items', '0', 'description'], // ['items', '0', 'description', 'en'], // ['items', '0', 'description', 'fr'], // ['items', '1'], // ['items', '1', 'description'], // ['items', '1', 'description', 'en'], // ['items', '1', 'description', 'fr']] const getRecordPaths = ( record: any = {}, path: Array<string> = [] ): Array<Array<string>> => { return Object.entries(record).reduce((acc, [key, value]) => { if (value !== null && typeof value === 'object') { return [ ...acc, [...path, key], ...getRecordPaths(value, [...path, key]), ]; } if (Array.isArray(value)) { return value.reduce( (acc, item, index) => [ ...acc, ...getRecordPaths(item, [...path, key, `${index}`]), ], acc ); } return [...acc, [...path, key]]; }, []); };