Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(graphic_walker): Support i18n. #116

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/graphic-walker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,50 @@ export default YourEmbeddingTableauStyleApp;
# packages/graphic-walker
npm run dev
```


## I18n Support

Graphic Walker now support _English_ (as `"en"` or `"en-US"`) and _Chinese_ (as `"zh"` or `"zh-CN"`) with built-in locale resources. You can simply provide a valid string value (enumerated above) as `props.i18nLang` to set a language or synchronize your global i18n language with the component like the example given as follow.

```typescript
const YourApp = props => {
// ...

const curLang = /* get your i18n language */;

return <GraphicWalker
dataSource={dataSource}
rawFields={fields}
i18nLang={curLang}
/>
}
```

### Customize I18n

If you need i18n support to cover languages not supported currently, or to totally rewrite the content of any built-in resource(s), you can also provide your resource(s) as `props.i18nResources` to Graphic Walker like this.

```typescript
const yourResources = {
'de-DE': {
...
},
'fr-FE': {
...
},
};

const YourApp = props => {
// ...

const curLang = /* get your i18n language */;

return <GraphicWalker
dataSource={dataSource}
rawFields={fields}
i18nLang={curLang}
i18nResources={yourResources}
/>
}
```
7 changes: 6 additions & 1 deletion packages/graphic-walker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"name": "@kanaries/graphic-walker",
"version": "0.1.0",
"scripts": {
"dev": "vite --host",
"dev:front_end": "vite --host",
"dev:data_service": "npx serve ../rath-client/public -l 8080",
"dev": "concurrently \"npm run dev:front_end\" \"npm run dev:data_service\"",
"build": "tsc && vite build",
"serve": "vite preview",
"type": "tsc src/lib.ts --declaration --emitDeclarationOnly --jsx react --esModuleInterop --outDir dist"
Expand All @@ -27,13 +29,16 @@
"@heroicons/react": "^2.0.8",
"@kanaries/web-data-loader": "0.1.5",
"autoprefixer": "^10.3.5",
"i18next": "^21.9.1",
"i18next-browser-languagedetector": "^6.1.5",
"mobx": "^6.3.3",
"mobx-react-lite": "^3.2.1",
"postcss": "^8.3.7",
"re-resizable": "^6.9.8",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2",
"react-i18next": "^11.18.6",
"react-json-view": "^1.21.3",
"rxjs": "^7.3.0",
"styled-components": "^5.3.0",
Expand Down
39 changes: 31 additions & 8 deletions packages/graphic-walker/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Record, IMutField } from './interfaces';
import { IMutField, IRow } from './interfaces';
import VisualSettings from './visualSettings';
import { Container, NestContainer } from './components/container';
import ClickMenu from './components/clickMenu';
Expand All @@ -17,22 +17,42 @@ import { toJS } from 'mobx';
import "tailwindcss/tailwind.css"
import './index.css'
import { Specification } from 'visual-insights';
import PureTabs from './components/tabs/pureTab';
// import PureTabs from './components/tabs/pureTab';
import VisNav from './segments/visNav';
import { useTranslation } from 'react-i18next';
import { mergeLocaleRes, setLocaleLanguage } from './locales/i18n';


export interface EditorProps {
dataSource?: Record[];
dataSource?: IRow[];
rawFields?: IMutField[];
spec?: Specification
spec?: Specification;
i18nLang?: string;
i18nResources?: { [lang: string]: Record<string, string | any> };
}

const App: React.FC<EditorProps> = props => {
const { dataSource = [], rawFields = [], spec } = props;
const { dataSource = [], rawFields = [], spec, i18nLang = 'en-US', i18nResources } = props;
const { commonStore, vizStore } = useGlobalStore();
const [insightReady, setInsightReady] = useState<boolean>(true);

const { currentDataset, datasets, vizEmbededMenu } = commonStore;

const { t, i18n } = useTranslation();
const curLang = i18n.language;

useEffect(() => {
if (i18nResources) {
mergeLocaleRes(i18nResources);
}
}, [i18nResources]);

useEffect(() => {
if (i18nLang !== curLang) {
setLocaleLanguage(i18nLang);
}
}, [i18nLang, curLang]);

// use as an embeding module, use outside datasource from props.
useEffect(() => {
if (dataSource.length > 0) {
Expand Down Expand Up @@ -75,7 +95,7 @@ const App: React.FC<EditorProps> = props => {
{/* <PureTabs tabs={[{label: 'a', key: 'a'}, {label: 'b', key: 'b'}]} selectedKey='a' onSelected={() => {}} /> */}
</div>
<Container style={{ marginTop: '0em', borderTop: 'none' }}>
<VisualSettings />
<VisualSettings />
<div className="grid grid-cols-12 xl:grid-cols-6">
<div className="col-span-3 xl:col-span-1">
<DatasetFields />
Expand All @@ -94,13 +114,16 @@ const App: React.FC<EditorProps> = props => {
<InsightBoard />
{vizEmbededMenu.show && (
<ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
<div className="flex items-center"
<div className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100"
onClick={() => {
commonStore.closeEmbededMenu();
commonStore.setShowInsightBoard(true)
}}
>
数据解读<LightBulbIcon className="ml-1 w-3 inline-block" />
<span className="flex-1 pr-2">
{t('App.labels.data_interpretation')}
</span>
<LightBulbIcon className="ml-1 w-3 flex-grow-0 flex-shrink-0" />
</div>
</ClickMenu>
)}
Expand Down
2 changes: 1 addition & 1 deletion packages/graphic-walker/src/components/clickMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import styled from 'styled-components';

const MenuContainer = styled.div`
width: 100px;
min-width: 100px;
background-color: #fff;
border: 1px solid #f0f0f0;
position: absolute;
Expand Down
1 change: 1 addition & 0 deletions packages/graphic-walker/src/components/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const Container = styled.div`
export const NestContainer = styled.div`
border: 1px solid #d9d9d9;
padding: 0.4em;
font-size: 12px;
margin: 0.2em;
background-color: #fff;
`
54 changes: 39 additions & 15 deletions packages/graphic-walker/src/components/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import React from 'react';
import styled from 'styled-components';
import { XCircleIcon } from '@heroicons/react/24/outline';


const Background = styled.div({
position: 'fixed',
left: 0,
top: 0,
width: '100vw',
height: '100vh',
backdropFilter: 'blur(2px)',
zIndex: 25535,
});

const Container = styled.div`
width: 880px;
max-height: 800px;
Expand All @@ -25,23 +37,35 @@ const Container = styled.div`
z-index: 999;
`;
interface ModalProps {
onClose?: () => void
title?: string;
onClose?: () => void
title?: string;
}
const Modal: React.FC<ModalProps> = props => {
const { onClose, title } = props;
return (
<Container className="shadow-lg">
<div className="header relative h-9">
{title}
<XCircleIcon
className="text-red-600 absolute right-2 w-6 cursor-pointer"
onClick={onClose}
/>
</div>
<div className="container">{props.children}</div>
</Container>
)
const { onClose, title } = props;

return (
<Background onClick={onClose}>
<Container
role="dialog"
className="shadow-lg"
onClick={e => e.stopPropagation()}
>
<div className="header relative h-9">
<header className="font-bold">
{title}
</header>
<XCircleIcon
className="text-red-600 absolute right-2 w-6 cursor-pointer"
role="button"
tabIndex={0}
aria-label="close dialog"
onClick={onClose}
/>
</div>
<div className="container">{props.children}</div>
</Container>
</Background>
);
}

export default Modal;
55 changes: 43 additions & 12 deletions packages/graphic-walker/src/components/sizeSetting.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,64 @@
import { ArrowsPointingOutIcon, XMarkIcon } from "@heroicons/react/24/outline";
import React from "react";
import { useState } from "react";
import React, { useState, useEffect } from "react";
import { useTranslation } from 'react-i18next';


interface SizeSettingProps {
onWidthChange: (val: number) => void;
onHeightChange: (val: number) => void;
width: number;
height: number;
}

const SizeSetting: React.FC<SizeSettingProps> = props => {
const { onWidthChange, onHeightChange, width, height } = props
const [show, setShow] = useState<boolean>(false);
const { t } = useTranslation('translation', { keyPrefix: 'main.tabpanel.settings.size_setting' });

useEffect(() => {
if (show) {
const closeDialog = () => {
setShow(false);
};

document.body.addEventListener('click', closeDialog);

return () => {
document.body.removeEventListener('click', closeDialog);
};
}
}, [show]);

return <div className="leading-none cursor-pointer">
<ArrowsPointingOutIcon
role="button"
id="button:size_setting"
aria-describedby="button:size_setting:label"
tabIndex={0}
aria-haspopup="dialog"
onClick={() => {
setShow(v => !v)
}}
className="w-4 h-4 inline-block mr-0.5 text-gray-900"
/>
{
show && <div className="absolute z-auto bg-white p-4 border border-gray-200 shadow">
show && <div role="dialog" className="absolute z-auto bg-white p-4 border border-gray-200 shadow cursor-default" onClick={e => e.stopPropagation()} style={{ zIndex: 25535 }}>
<div>
<XMarkIcon
className="text-gray-900 absolute right-2 top-2 w-4 cursor-pointer hover:bg-red-100"
onClick={(e) => {
setShow(false);
e.stopPropagation();
}}
/>
className="text-gray-900 absolute right-2 top-2 w-4 cursor-pointer hover:bg-red-100"
role="button"
tabIndex={0}
aria-label="close"
onClick={(e) => {
setShow(false);
e.stopPropagation();
}}
/>
</div>

<div className="mt-4">
<div className="mt-4 w-60">
<input className="w-full h-2 bg-blue-100 appearance-none"
style={{ cursor: 'ew-resize' }}
type="range"
name="width"
value={Math.sqrt(width / 1000)}
Expand All @@ -41,10 +67,13 @@ const SizeSetting: React.FC<SizeSettingProps> = props => {
onWidthChange(Math.round(Number(e.target.value) ** 2 * 1000))
}}
/>
<label className="text-sm ml-1" htmlFor="width">宽度{width}</label>
<output className="text-sm ml-1" htmlFor="width">
{`${t('width')}: ${width}`}
</output>
</div>
<div className=" mt-2">
<input className="w-full h-2 bg-blue-100 appearance-none"
style={{ cursor: 'ew-resize' }}
type="range"
name="height"
value={Math.sqrt(height / 1000)}
Expand All @@ -53,7 +82,9 @@ const SizeSetting: React.FC<SizeSettingProps> = props => {
onHeightChange(Math.round(Number(e.target.value) ** 2 * 1000))
}}
/>
<label className="text-sm ml-1" htmlFor="height">高度{height}</label>
<output className="text-sm ml-1" htmlFor="height">
{`${t('height')}: ${height}`}
</output>
</div>

</div>
Expand Down
Loading