Translate.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import React, { useContext, useState } from 'react';
  2. import PropTypes from 'prop-types';
  3. const DEFAULT_LANGUAGE = 'en';
  4. const LANGUAGES_PATH = 'static/locales';
  5. const LANGUAGES_FILE = 'translations.json';
  6. const VARIABLE_REGEX = /\{([^}]+)\}/g;
  7. export const store = {
  8. /* dictionaries */
  9. };
  10. /**
  11. * Set a dictionary for a specific language
  12. * @param {String} lang language
  13. * @param {Object} data dictionary
  14. * @return {Object} dictionary added
  15. */
  16. export const setDict = (lang, data) => (store[lang] = data);
  17. /**
  18. * Get a dictionary for specific language
  19. * @param {String} lang language
  20. * @return {Object} dictionary
  21. */
  22. export const getDict = lang => store[lang];
  23. /**
  24. * Fetch a dictionary for specific language defined in JSON format
  25. * @param {Strong} lang language
  26. * @return {Object} dictionary loaded
  27. */
  28. export const fetchStore = async lang => {
  29. if (!store[lang]) {
  30. const res = await fetch(`/${LANGUAGES_PATH}/${lang}/${LANGUAGES_FILE}`);
  31. store[lang] = await res.json();
  32. }
  33. return store[lang];
  34. };
  35. /**
  36. * Interpolates values given in 'params'
  37. * @param {String} str text
  38. * @param {Object} params object to interpolate
  39. * @return {String}
  40. */
  41. const compile = (str = '', params) => {
  42. const matches = str.match(VARIABLE_REGEX);
  43. if (matches) {
  44. matches
  45. .map(v => v.replace(/\{|\}/g, ''))
  46. .forEach(v => (str = str.replace('{' + v + '}', params[v])));
  47. }
  48. return str;
  49. };
  50. /**
  51. * Reads an object value using dot notation for nested keys
  52. * @param {Object} obj object to parse
  53. * @param {String} skey key to search
  54. * @return {String|null}
  55. */
  56. export const accessKey = (obj, skey = '') => skey.split('.').reduce((a, b) => a && a[b], obj);
  57. /**
  58. * Returns the translated text for given key and interpolates values in 'params'
  59. * @param {String} key the key to search for
  60. * @param {String} lang language
  61. * @param {Object} params object with params to interpolate
  62. * @return {[type]} [description]
  63. */
  64. export const translateKey = (key, lang, params = {}) => compile(accessKey(getDict(lang), key));
  65. // =====================
  66. // REACT INTERFACE
  67. // =====================
  68. /**
  69. * Context used to handle translation in the component tree
  70. */
  71. const TranslateContext = React.createContext({
  72. language: DEFAULT_LANGUAGE,
  73. changeLanguage: () => {}
  74. });
  75. /**
  76. * Component provider to pass down context to child components
  77. * Must be used to wrap application
  78. */
  79. export class Provider extends React.Component {
  80. changeLanguage = language => {
  81. fetchStore(language).then(() => {
  82. this.setState(state => ({
  83. language
  84. }));
  85. });
  86. };
  87. state = {
  88. language: DEFAULT_LANGUAGE,
  89. changeLanguage: this.changeLanguage
  90. };
  91. constructor(props) {
  92. super(props);
  93. if (props.store) {
  94. Object.keys(props.store).forEach(l => setDict(l, props.store[l]));
  95. }
  96. }
  97. render() {
  98. return (
  99. <TranslateContext.Provider value={this.state}>
  100. {this.props.children}
  101. </TranslateContext.Provider>
  102. );
  103. }
  104. }
  105. /**
  106. * HOC to provide 'changeLanguage' and 't' method to WrappedComponent
  107. */
  108. export function withTranslation(WrappedComponent) {
  109. return function TranslatedComponent(props) {
  110. const { language, changeLanguage } = useContext(TranslateContext);
  111. const t = (k, l, p) => translateKey(k, l || language, p);
  112. return <WrappedComponent changeLanguage={changeLanguage} t={t} {...props} />;
  113. };
  114. }
  115. /**
  116. * Component to translate a given key
  117. * If key is missing, render children without changes
  118. */
  119. export const Trans = props => {
  120. const { language } = useContext(TranslateContext);
  121. const { i18nKey, lang, params, children } = props;
  122. return [translateKey(i18nKey, lang || language, params) || children];
  123. };
  124. Trans.proptypes = {
  125. i18nKey: PropTypes.string.isRequired,
  126. lang: PropTypes.string,
  127. params: PropTypes.object
  128. };