Skip to content
Permalink
Browse files

NEW: Translations layer (#1016)

  • Loading branch information...
tomashapl authored and vepor committed Apr 30, 2019
1 parent 10f17a9 commit bfa7a9052be4dd69ed3883a6911b64890e9c02d6
Showing with 369 additions and 30 deletions.
  1. +2 −1 .eslintrc
  2. +43 −0 .github/dictionary.md
  3. +3 −3 .github/theming.md
  4. +72 −0 config/fetchTranslations.js
  5. +4 −0 package.json
  6. +2 −1 readme.md
  7. +12 −0 src/Dictionary/index.js
  8. +17 −0 src/Dictionary/index.js.flow
  9. +0 −1 src/Popover/README.MD
  10. +5 −3 src/Popover/__tests__/__snapshots__/index.test.js.snap
  11. +6 −6 src/Popover/components/ContentWrapper.js
  12. +1 −2 src/Popover/index.js.flow
  13. +20 −0 src/ThemeProvider/README.md
  14. +19 −0 src/ThemeProvider/index.js
  15. +14 −0 src/ThemeProvider/index.js.flow
  16. +0 −1 src/Tooltip/README.md
  17. +3 −9 src/Tooltip/index.js
  18. +1 −2 src/Tooltip/index.js.flow
  19. +48 −0 src/Translate/README.md
  20. +30 −0 src/Translate/index.js
  21. +12 −0 src/Translate/index.js.flow
  22. +1 −0 src/data/dictionary/ar-AE.json
  23. +1 −0 src/data/dictionary/bg-BG.json
  24. +1 −0 src/data/dictionary/cs-CZ.json
  25. +1 −0 src/data/dictionary/da-DK.json
  26. +1 −0 src/data/dictionary/de-DE.json
  27. +1 −0 src/data/dictionary/el-GR.json
  28. +11 −0 src/data/dictionary/en-GB.json
  29. +1 −0 src/data/dictionary/en-US.json
  30. +1 −0 src/data/dictionary/es-AR.json
  31. +1 −0 src/data/dictionary/es-ES.json
  32. +1 −0 src/data/dictionary/es-MX.json
  33. +1 −0 src/data/dictionary/fi-FI.json
  34. +1 −0 src/data/dictionary/fr-FR.json
  35. +1 −0 src/data/dictionary/he-IL.json
  36. +1 −0 src/data/dictionary/hu-HU.json
  37. +1 −0 src/data/dictionary/id-ID.json
  38. +1 −0 src/data/dictionary/it-IT.json
  39. +1 −0 src/data/dictionary/ja-JP.json
  40. +1 −0 src/data/dictionary/ko-KR.json
  41. +1 −0 src/data/dictionary/lt-LT.json
  42. +1 −0 src/data/dictionary/nb-NO.json
  43. +1 −0 src/data/dictionary/nl-NL.json
  44. +1 −0 src/data/dictionary/pl-PL.json
  45. +1 −0 src/data/dictionary/pt-PT.json
  46. +1 −0 src/data/dictionary/ro-RO.json
  47. +2 −1 src/utils/rtl/RenderInRtl.js
  48. +18 −0 yarn.lock
@@ -55,6 +55,7 @@
"import/no-self-import": "off",
"react/destructuring-assignment": "off",
"react/no-access-state-in-setstate": "off",
"jsx-a11y/label-has-associated-control": "off"
"jsx-a11y/label-has-associated-control": "off",
"no-await-in-loop": "off"
}
}
@@ -0,0 +1,43 @@
# Dictionary
We added support to our own dictionary. It handles common translations in components like `Close`

You have available our dictionary in `@kiwicom/orbit-components/lib/data/dictionary/...`
There are files which contains our own translations.

**Example:**
```jsx
import en_GB from "@kiwicom/orbit-components/lib/data/dictionary/en-GB.json";
import ThemeProvider from "@kiwicom/orbit-components/lib/ThemeProvider";
import Tooltip from "@kiwicom/orbit-components/lib/Tooltip";
import Heading from "@kiwicom/orbit-components/lib/Heading";
const App = () =>
<ThemeProvider dictionary={en_GB}>
<Tooltip content="Write your text here. If it’s longer than three li…">
<Heading>
Orbit design system
</Heading>
</Tooltip>
</ThemeProvider>;
```

**Fallbacks**

* If translation key not exists in your language the fallback is `en_GB` which is our default lang
* If translation key not exists in both files (your language, default language), the translation key will be rendered e.g. `button_close`

**Your own dictionary**

There is option to add your own dictionary, just pass object containing keys and values.

```jsx
import ThemeProvider from "@kiwicom/orbit-components/lib/ThemeProvider";
const App = () =>
<ThemeProvider dictionary={{
"button_close": "My own translation"
}}>
<Button type="secondary" size="large" />
</ThemeProvider>;
```

@@ -1,12 +1,12 @@
# Theming
Our entire CSS styling for our components is based on [orbit-design-tokens](https://github.com/kiwicom/orbit-design-tokens), which contains variables with colors, sizings, spacings, etc. It also contains the functionality to create custom themes that can be used inside `orbit-components`.

All you need to do is pass colors into the `getTokens` function and then pass this object into `<ThemeProvider />`. The component from `styled-components` will do all the magic for you thanks to React's context API.
All you need to do is pass colors into the `getTokens` function and then pass this object into `<ThemeProvider />`. The component re-exports `ThemeProvider` from `styled-components` and will do all the magic for you thanks to React's context API.

**Example:**
```jsx
import { getTokens } from "@kiwicom/orbit-design-tokens";
import { ThemeProvider } from "styled-components";
import getTokens from "@kiwicom/orbit-components/lib/getTokens";
import ThemeProvider from "@kiwicom/orbit-components/lib/ThemeProvider";
const customTokens = getTokens({
palette: {
@@ -0,0 +1,72 @@
// @flow
import fetch from "isomorphic-unfetch";
import path from "path";
import dotenv from "dotenv";
import fs from "fs-extra";

dotenv.config();
const env = name => process.env[name] || "";

const PHRASE_APP_BASE_URL = "https://api.phraseapp.com/api/v2";
const PHRASE_APP_PROJECT_ID = env("PHRASE_APP_PROJECT_ID");
const PHRASE_APP_ACCESS_TOKEN = env("PHRASE_APP_ACCESS_TOKEN");

const LOCALES_URL = `${PHRASE_APP_BASE_URL}/projects/${PHRASE_APP_PROJECT_ID}/locales`;
const FILE_FORMAT = "nested_json";

const fetchJSON = async url => {
const options = {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `token ${PHRASE_APP_ACCESS_TOKEN}`,
},
};
return (await fetch(url, options)).json();
};

const writeJSON = (filename, obj) =>
new Promise((resolve, reject) => {
fs.outputFile(filename, JSON.stringify(obj, null, 2), "utf8", err => {
if (err) {
reject(err);
}

resolve();
});
});

const flatten = (obj = {}, keyPrefix = "") =>
Object.entries(obj).reduce((result, [key, value]) => {
if (value && typeof value === "object") {
return {
...result,
...flatten(value, `${keyPrefix}${key}.`),
};
}
return {
...result,
[keyPrefix + key]: value,
};
}, {});

(async () => {
try {
const allLocales = await fetchJSON(LOCALES_URL);
const LOCALES_DATA = path.join(__dirname, "..", "src", "data", "dictionary");

// PhraseApp has limits on parallel requests
// that's why we process requests in sequence
// eslint-disable-next-line no-restricted-syntax
for (const locale of allLocales) {
const translation = await fetchJSON(
`${LOCALES_URL}/${locale.id}/download?file_format=${FILE_FORMAT}&tags=orbit&encoding=UTF-8`,
);

await writeJSON(path.join(LOCALES_DATA, `${locale.code}.json`), flatten(translation.orbit));
}
} catch (error) {
console.error(error); // eslint-disable-line no-console
process.exit(1);
}
})();
@@ -18,6 +18,7 @@
"flow:check": "flow check",
"test": "jest --config=jest.json",
"test-ci": "yarn flow:check && yarn eslint && yarn test --ci --maxWorkers=2",
"fetch-translations": "babel-node config/fetchTranslations.js",
"deploy:storybook": "storybook-to-ghpages",
"deploy:surge": "yarn surge .out/ https://orbit-components-$(git rev-parse --abbrev-ref HEAD | sed -e 's/[^a-zA-Z0-9]/-/g').surge.sh",
"deploy:updateURL": "yarn babel-node config/deploymentUtils.js updateLiveURL"
@@ -107,6 +108,7 @@
"camelcase": "^5.0.0",
"capitalize": "^1.0.0",
"copyfiles": "^2.1.0",
"dotenv": "^7.0.0",
"enzyme": "^3.6.0",
"enzyme-adapter-react-16": "^1.6.0",
"enzyme-to-json": "^3.3.3",
@@ -120,7 +122,9 @@
"eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-react": "^7.7.0",
"flow-bin": "^0.89.0",
"fs-extra": "^7.0.1",
"glob": "^7.1.2",
"isomorphic-unfetch": "^3.0.0",
"jest": "^24.5.0",
"jest-styled-components": "^6.2.1",
"jsdom": "^12.0.0",
@@ -49,7 +49,7 @@ import Alert from "@kiwicom/orbit-components/lib/Alert"
<Alert>Hello World!</Alert>
```
If you want to use a custom theme inside your project, its necessary to generate a theme object from `orbit-design-tokens` and use it in `<ThemeProvider />` component from `styled-components`. See [this document](https://github.com/kiwicom/orbit-components/tree/master/.github/theming.md) for more information.
If you want to use custom theme or dictionary inside your project, it's necessary to wrap your app into `<ThemeProvider>`. See [this document](https://github.com/kiwicom/orbit-components/tree/master/src/ThemeProvider/README.md) for more information.
For live preview check out [Storybook](https://kiwicom.github.io/orbit-components/) or [orbit.kiwi](https://orbit.kiwi).
@@ -60,6 +60,7 @@ You can also try `orbit-components` live on [CodeSandbox](https://codesandbox.io
* [Icons](https://github.com/kiwicom/orbit-components/tree/master/src/Icon/README.md)
* [Right to left languages](https://github.com/kiwicom/orbit-components/tree/master/src/utils/rtl/README.md)
* [Theming](https://github.com/kiwicom/orbit-components/tree/master/.github/theming.md)
* [Dictionary](https://github.com/kiwicom/orbit-components/tree/master/.github/dictionary.md)
## Contributing
We are working on making this project fully open source. We appreciate any contributions you might make.
@@ -0,0 +1,12 @@
// @flow
import * as React from "react";

import type { DictionaryContextType, Props } from "./index";

export const DictionaryContext: DictionaryContextType = React.createContext({});

const Dictionary = ({ values, children }: Props) => (
<DictionaryContext.Provider value={values}>{children}</DictionaryContext.Provider>
);

export default Dictionary;
@@ -0,0 +1,17 @@
// @flow
import * as React from "react";

export type Translations = {
[key: string]: string,
};

export type DictionaryContextType = React.Context<Translations>;

export type Props = {
+values: Translations,
+children: React$Node,
};

declare export default React$ComponentType<Props>;

declare export var DictionaryContext: DictionaryContextType;
@@ -16,7 +16,6 @@ Table below contains all types of the props available in the Popover component.
| Name | Type | Default | Description |
| :---------------- | :--------------------- | :-------------- | :------------------------------- |
| **content** | `React.Node` | | The content to display in the Popover.
| closeText | `Translation` | `"Close"` | The text of the close button to display on mobile devices.
| **children** | `React.Node` | | The reference element where the Popover will appear.
| dataTest | `string` | | Optional prop for testing purposes.
| noPadding | `boolean` | `true` | Adds or removes padding around popover's content.
@@ -921,7 +921,7 @@ exports[`ContentWrapper it should match snapshot 1`] = `
<Button>
Content
</Button>
<ContentWrapper__StyledTooltipClose
<ContentWrapper__StyledPopoverClose
theme={
Object {
"orbit": Object {
@@ -1378,9 +1378,11 @@ exports[`ContentWrapper it should match snapshot 1`] = `
onClick={[MockFunction]}
type="secondary"
>
Close
<Translate
tKey="button_close"
/>
</Button>
</ContentWrapper__StyledTooltipClose>
</ContentWrapper__StyledPopoverClose>
</ContentWrapper__StyledPopoverContent>
</ContentWrapper__StyledPopoverParent>
</Fragment>
@@ -12,6 +12,7 @@ import type { Props } from "./ContentWrapper.js.flow";
import useDimensions from "../hooks/useDimensions";
import useVerticalPosition from "../hooks/useVerticalPosition";
import useHorizontalPosition from "../hooks/useHorizontalPosition";
import Translate from "../../Translate";

const showAnimation = keyframes`
from {
@@ -93,7 +94,7 @@ StyledOverlay.defaultProps = {
theme: defaultTheme,
};

const StyledTooltipClose = styled.div`
const StyledPopoverClose = styled.div`
padding: ${({ theme, noPadding }) => (noPadding ? theme.orbit.spaceSmall : 0)};
padding-top: ${({ theme }) => theme.orbit.spaceMedium};
@@ -103,13 +104,12 @@ const StyledTooltipClose = styled.div`
padding-bottom: 0;
`)}
`;
StyledTooltipClose.defaultProps = {
StyledPopoverClose.defaultProps = {
theme: defaultTheme,
};

const PopoverContentWrapper = ({
children,
closeText,
onClose,
width,
dataTest,
@@ -160,11 +160,11 @@ const PopoverContentWrapper = ({
>
<StyledPopoverContent ref={content}>
{children}
<StyledTooltipClose noPadding={noPadding}>
<StyledPopoverClose noPadding={noPadding}>
<Button type="secondary" block onClick={onClose}>
{closeText || "Close"}
<Translate tKey="button_close" />
</Button>
</StyledTooltipClose>
</StyledPopoverClose>
</StyledPopoverContent>
</StyledPopoverParent>
</React.Fragment>
@@ -1,5 +1,5 @@
// @flow
import type { Globals, Translation } from "../common/common.js.flow";
import type { Globals } from "../common/common.js.flow";

export type PositionsCore = "top" | "bottom";
export type AnchorsCore = "start" | "end";
@@ -28,7 +28,6 @@ export type Props = {|
...Globals,
+children: React$Node,
+content: React$Node,
+closeText?: Translation,
+preferredPosition?: PositionsCore,
+opened?: boolean,
+width?: string,
@@ -0,0 +1,20 @@
# ThemeProvider
orbit-components has theming support via our own `<ThemeProvider>` which adds you possibilities to add your own theme and dictionary.
```jsx
import ThemeProvider from "@kiwicom/orbit-components/lib/ThemeProvider";
```
After adding import please wrap your application into `ThemeProvider` and you can provide your own [`theme`](https://github.com/kiwicom/orbit-components/blob/master/.github/theming.md) and [`dictionary`](https://github.com/kiwicom/orbit-components/blob/master/.github/dictionary.md)

```jsx
<ThemeProvider theme={...} dictionary={...}>
<App />
</ThemeProvider>
```
## Props
Table below contains all types of the props available in the ThemeProvider component.

| Name | Type | Default | Description |
| :------------ | :------------------------ | :-------------- | :------------------------------- |
| **children** | `React.Node` | | Your app
| theme | `[Object]` | | See [`theming`](https://github.com/kiwicom/orbit-components/blob/master/.github/theming.md)
| dictionary | `[Object]` | | See [`dictionary`](https://github.com/kiwicom/orbit-components/blob/master/.github/dictionary.md)
@@ -0,0 +1,19 @@
// @flow
import React from "react";
import { ThemeProvider as StyledThemeProvider } from "styled-components";

import Dictionary from "../Dictionary";

import type { Props } from "./index";

const ThemeProvider = ({ theme, dictionary, children }: Props) => (
<StyledThemeProvider theme={theme}>
{dictionary ? (
<Dictionary values={dictionary}>{React.Children.only(children)}</Dictionary>
) : (
React.Children.only(children)
)}
</StyledThemeProvider>
);

export default ThemeProvider;
@@ -0,0 +1,14 @@
// @flow
/*
DOCUMENTATION: https://orbit.kiwi/components/themeprovider/
*/

import type { Translations } from "../Dictionary";

export type Props = {|
+theme: any,
+dictionary?: Translations,
+children: React$Node,
|};

declare export default React$ComponentType<Props>;
@@ -20,7 +20,6 @@ Table below contains all types of the props available in the Tooltip component.
| **content** | `React.Node` | | The content to display in the Tooltip.
| preferredPosition | [`enum`](#enum) | | The preferred position to choose [See Functional specs](#functional-specs)
| size | [`enum`](#enum) | | The maximum possible size of the Tooltip.
| closeText | `Translation` | | The text of the close button to display on mobile devices.
| tabIndex | `string` | `"0"` | Specifies the tab order of an element

## enum

0 comments on commit bfa7a90

Please sign in to comment.
You can’t perform that action at this time.