npm package: @interdan/react-localized-texts
Description: Make static texts editable, supports multi languages
Author: Denis Oborskiy
Latest version: 1.0.11

# React localized texts

Library allows to achieve two main goals for your React application:

* add localization: your app can have two or more languages
* ability to edit your text content right inside your application and observe the results instantly (for one, two, three or more languages)

The library supports the nexts features:

* pure text or html format
* variables (substitutions) inside your text values
* intelligent code completion of the text keys (tested in VS code)
* mobile view mode
* __Type__Script

## Installation: texts are just pure strings
    npm install @interdan/react-localized-texts

## Installation: texts can be pure strings or html content as well
    npm install @interdan/react-localized-texts
    npm install @tinymce/tinymce-react
    npm install react-html-parser

In general "react-html-parser" not required, you can use any function that build react element from the html string.

## "texts.js" file (or "texts.ts" for TypeScript)

To store all text keys it's necessary to create a "texts" object. The keys of this object are keys of all possible texts that should be accessible in your source code. But you can request additional keys from your API along with their values.
The values of this "texts" object are collections of all possible parameters for a text template. If the text isn't expected to have parameters - that object must be empty. There is only one exception - special value for that object "pureString: true". It used along with HTML content. "pureString: true" - means that this particular text value shouldn't contain HTML content since it used as text input placeholder, select's option value, etc. somewhere where ONLY strings are allowed. Here is an example of "texts" file:

```javascript
import { transformTextsIfProxyNotSupported, tryCreateTextsES6Proxy } from '@interdan/react-localized-texts';

const x = null;
const texts = {
  hi_there: { pureString: true },
  empty_text_example: { },
  current_time_is: { time: x },
  multiline_text_example: {},
  enter_your_name_click_to_edit: { pureString: true },
};

const resultTexts = Object.assign(tryCreateTextsES6Proxy(), texts);
transformTextsIfProxyNotSupported(resultTexts);

export default resultTexts;
```

The value of the "x" variable could be anything, you can use 0, 1 instead of variable "x" at all. "x" used just for better readability.
Use this "tryCreateTextsES6Proxy" and "transformTextsIfProxyNotSupported" as in this example - that methods just convert the original texts object values to react elements or functions that expect parameter values and also return react elements.

## Using texts in your code

To reference texts in the app just import this "texts" object:

```javascript
import texts from '<path to file>/texts';
```
and use it inside your .jsx (or .tsx)

```jsx
return (
    <div>
      <p>{texts.hi_there}</p>
      <p>{texts.empty_text_example}</p>
      <p>
        {texts.current_time_is({ time: <b>{time}</b> })}
      </p>
      <p>
        Value can be a string or number: {texts.current_time_is({ time: "12:00" })}
      </p>
      {texts.multiline_text_example}
      {texts["Dynamic key. If no value provided doesn't work in IE (shows empty string instead)"]}
      <div className="input-example">
        <TextSourceProvider text={texts.enter_your_name_click_to_edit} propertyToSet="placeholder">
          <input type="text" />
        </TextSourceProvider>
      </div>
    </div>
  );
```

As you can see from example, if your text has parameter (or parameters) you must use it as a function with a single object parameter. That object should contain values for the same keys you have declared in the "texts" file. Look one more time on this "current_time_is" example:

in "texts" file we have

```javascript
  current_time_is: { time: x },
```
in JSX we have

```jsx
  <p>
    {texts.current_time_is({ time: <b>{time}</b> })}
  </p>
```

Text parameter value type can be number, string any valid react element.

If the key stored in some variable (they are delivered from your API) and not specified in the "texts" file you have to use square brackets (just a standard way to access JavaScript object value):

```jsx
  <p>
    {texts[myCompanyNameTextKey]}
  </p>
```

If you for some reason (testing, scaffolding, etc.) don't want to declare text key in the "texts" file your code will be still workable (thanks to ES6 proxy, but not supported by IE):

```jsx
  <h3>This is allowed:</h3>
  <p>
    {texts['any key I have in my mind']}
    {texts.justTestKey}
  </p>
```

## Texts values initialization

To set texts values (that can be stored locally or be retrieved from your API) you should use the next JSON format:

```javascript
const myTextsValues = {
  texts: [
    {
      key: "keyOne",
      pureString: false,
      values: [
        {
          language: "en",
          value: "Hi there",
        },
        {
          language: "fr",
          value: "Salut",
        },
      ],
    },
    {
      key: "current_time_is",
      values: [
        {
          language: "en",
          value: "Current time is: {{time}}",
        },
        {
          language: "fr",
          value: "[ Not translated yet ]",
        },
      ],
    },
    ...
  ]
};
```

For texts with parameters use the curly braces to refer the parameter by name:
```javascript
value: "Current time is: {{time}}"
```

To set texts values use method "loadTexts":
```javascript
import { loadTexts } from '@interdan/react-localized-texts';

loadTexts(myTextsValues);
```

## Text editor initialization

To work with library you need to have languages array (it's easy to delare as an object) and "save text values" method.
Here is an examples:

```javascript
const languages = {
  en: 'en',
  fr: 'fr',
  de: 'de',
  dk: 'dk',
};

const TEXTS_VAR_NAME = "texts JSON";
const saveTextValues = (textJson, successCallBack) => {
  localStorage.set(TEXTS_VAR_NAME, JSON.stringify(textJson));
  console.log('textJson: ', textJson);

  // mock of API request delay
  window.setTimeout(() => successCallBack({ success: true }), 2000);
};
```

On the top level of your app, you have to init library:

```javascript
import { initLocalizedTextsLibrary, loadTexts } from '@interdan/react-localized-texts';

initLocalizedTextsLibrary({
  saveTextValues,
  languagesList: Object.values(languages),
});

loadTexts(myTextsValues);
```

If you want HTML support for you text variables additional property required:

```javascript
import { initLocalizedTextsLibrary, loadTexts } from '@interdan/react-localized-texts';
import ReactHtmlParser from 'react-html-parser';
import { Editor } from '@tinymce/tinymce-react';

const TINY_API_KEY = 'your secred TINY API KEY';

initLocalizedTextsLibrary({
  saveTextValues,
  languagesList: Object.values(languages),

  htmlToReactBuilder: ReactHtmlParser,
  textValueEditor: buildTinyEditor(Editor, TINY_API_KEY),
});

loadTexts(myTextsValues);
```

The last step for setting up: wrapping your app tree with TextsEditor:

```jsx
import { TextsEditor } from '@interdan/react-localized-texts';

const lang = languages.en;

<TextsEditor language={lang}>
  <MyApp />
</TextsEditor>
```

With Redux it makes sence to connect TextEditor:

```jsx
import { TextsEditor } from '@interdan/react-localized-texts';
import { connect } from 'react-redux';

import { getCurrentLanguage } from '<my selectors path>';

const mapStateToProps = state => ({ language: getCurrentLanguage(state) });
const ConnectedTextsEditor = connect(mapStateToProps)(TextsEditor);

<ConnectedTextsEditor>
  <MyApp />
</ConnectedTextsEditor>
```

## Using texts with inputs

To use texts with inputs you need "TextSourceProvider", it is a React component that exported by default from this library. Here is a simple example of how you can use it to set placeholder for your text input:

```jsx
import TextSourceProvider from '@interdan/react-localized-texts';

<TextSourceProvider text={texts.enter_your_name} propertyToSet="placeholder">
  <input type="text" />
</TextSourceProvider>

```

or for select's option:
```jsx
import TextSourceProvider from '@interdan/react-localized-texts';

const StandardOption = ({ value, label }) => <option value={value}>{label}</option>;

const LocalizedOption = ({ value, label }) => (
  <TextSourceProvider text={label} propertyToSet="label">
    <StandardOption value={value} />
  </TextSourceProvider>
);

```

* Required `propertyToSet` property - just a child's property name you want to fullfill with string value.

* Required `text` property accepts the text you want to be set to child element as a string value (placeholder and option's content in examples).

`text` could be a text from "texts" object collection, or just a string key. This two lines of code produce the equivalent results:

```jsx
  <TextSourceProvider text={texts.years} propertyToSet="label">
```
and

```jsx
  <TextSourceProvider text="years" propertyToSet="label">
```

The only benefit for the first approach is intelligent code completion

`text` also could be a function. Sometime you need your string value to be formatted with some custom formatter. The formatter function should have the next form:

```javascript
import TextSourceProvider from '@interdan/react-localized-texts';
import texts from './multilang/texts';

export const formatWithYears = (toText, value) => {
  const isOne = parseInt(value, 10) === 1;
  return toText`${value} ${isOne ? texts.year : texts.years}`;
};
```

the first parameter if always ES6 Tag Function - `toText` that you have to apply to returned ES6 Template string as in example above. Other parameters are the same that you passed to TextSourceProvider with the optional `textParameters` property as an array.

The full example of using `text` as a function:

```jsx
// reusable formatter
const formatWithYears = (toText, value) => {
  const isOne = parseInt(value, 10) === 1;
  return toText`${value} ${isOne ? texts.year : texts.years}`;
};

// reusable component
const StandardOption = ({ value, label }) => <option value={value}>{label}</option>;

// reusable component
const SelectOption = ({ value, getText, textParameters }) => (
  <TextSourceProvider text={getText} textParameters={textParameters} propertyToSet="label">
    <StandardOption value={value} />
  </TextSourceProvider>
);

<select value={1} {...otherProps}>
  <SelectOption value={1} getText={formatWithYears} textParameters={[1]} />
  ...
</select>
```

## showTextEditor function

To show texts editor and make it possible to click on any text and edit them use function `showTextEditor`:

```javascript
import { showTextEditor } from '@interdan/react-localized-texts';

// some code
...

showTextEditor();
```

## Library helpers functions

`keyOf (textReactElement)` - extracts the key from the text: `keyOf(texts.years)` will returns `"years"`

`stringOf (textReactElement, language)` - gets the string value for provided texts and language.

`toReactElement` - ES6 Tag Function, allows to build a React element with a string template:
```jsx
import { toReactElement } from '@interdan/react-localized-texts';

const SomeComponent = ({ durationDays }) => {
  return toReactElement`${texts.it_will_take} ${<b>{durationDays}</b>} ${texts.days}`;
};

```
The result of `toReactElement` will be the React element or just a string (if all the parameters are primitives)