jcquintas / ni18n Goto Github PK
View Code? Open in Web Editor NEWSimple and powerful i18next integration for next.js
Home Page: https://jcquintas.gitbook.io/ni18n/
License: MIT License
Simple and powerful i18next integration for next.js
Home Page: https://jcquintas.gitbook.io/ni18n/
License: MIT License
When trying to load translations on the server side from within getServerSideProps, the props are loaded into the store, but fail to be applied to the html. When the client takes over, the translations are loaded. This also causes a mismatch error when fallbackLng is set to supportedLngs as in the example.
The issue is present in a fresh vanilla NextJS installation with files as follows:
pages/_app.tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { appWithI18Next, useSyncLanguage } from 'ni18n'
import { ni18nConfig } from '../ni18n.config'
const App = ({ Component, pageProps }: AppProps) => {
useSyncLanguage('en')
return <Component {...pageProps} />
}
export default appWithI18Next(App, ni18nConfig)
ni18n.config.ts
import type { Ni18nOptions } from 'ni18n'
const supportedLngs = ['es', 'en']
export const ni18nConfig: Ni18nOptions = {
fallbackLng: supportedLngs,
supportedLngs,
ns: ['translation'],
react: {
useSuspense: false,
},
}
pages/index.tsx
import Head from 'next/head'
import { useTranslation } from 'react-i18next'
import { loadTranslations } from 'ni18n'
import { ni18nConfig } from '../ni18n.config'
import { GetServerSideProps } from 'next'
export default function Home() {
const { t } = useTranslation()
return (
<>
<Head>
<title>Test nI18n</title>
</Head>
<main>
<div>
<p>Hello!</p>
<p>{t('key')}</p>
</div>
</main>
</>
)
}
export const getServerSideProps: GetServerSideProps = async (props) => {
return {
props: {
...(await loadTranslations(ni18nConfig, props.locale, ['translation']))
}
}
}
It's worth noting that the prop ni18n_server is being loaded with the translations, but whatever I try, I've not been able to get SSR to pick them up.
__ni18n_server__: { resources: { es: [Object], en: [Object] }, ns: [ 'translation' ] }
As can be seen here, the SSR response shows 'key' instead of the text 'Translation in English'. Once the client takes over, the translation is corrected in the view and the error is displayed.
package.json as follows:
{
"name": "n18n",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@next/font": "13.0.7",
"@types/node": "18.11.17",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"i18next": "^22.4.6",
"next": "13.0.7",
"ni18n": "^1.0.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-i18next": "^12.1.1",
"typescript": "4.9.4"
}
}
Hello,
I'm trying to use multiple NS for one component but it only use first NS added.
i tried with:
const { t } = useTranslation("races", "pets");
Or with:
const { t } = useTranslation(["races", "pets"]);
(according https://react.i18next.com/guides/multiple-translation-files)
But only first one is used.
Ni18n.config.js:
import { Ni18nOptions } from "ni18n";
export const ni18nConfig = {
supportedLngs: ['fr', 'en'],
ns: ["website", "pets", "races"],
};
export const languages = [
{ code: "en", flag: "gb", translateKey: "English" },
{ code: "fr", flag: "fr", translateKey: "Français" },
];
/en/pets.json:
{
"Battle pit": "Battle pit",
"Bind to equipment": "Bind to equipment",
"Bind to pickup": "Bind to pickup",
"Bound": "Bound",
"Cumulative": "Cumulative",
"Description of pets": "English description of pets.",
"List of pets": "List of pets",
"No": "No",
"Offensive": "Offensif",
"Race": "Race"
}
/fr/pets.json:
{
"Battle pit": "Fosse de combat",
"Bind to equipment": "Lié à l'équipement",
"Bind to pickup": "Lié au ramassage",
"Bound": "Lié au ramassage",
"Cumulative": "Cumulable",
"Description of pets": "Description des familiers en Français.",
"List of pets": "Liste des familiers",
"No": "Non",
"Offensive": "Offensif",
"Race": "Race"
}
/en/races.json:
{
"Aquilonian female": "Aquilonian",
"Aquilonian male": "Aquilonian",
"Cimmerian female": "Cimmerian",
"Cimmerian male": "Cimmerian",
"Khitan female": "Khitan",
"Khitan male": "Khitan",
"Stygian female": "Stygian",
"Stygian male": "Stygian"
}
/fr/races.json:
{
"Aquilonian female": "Aquilonienne",
"Aquilonian male": "Aquilonien",
"Cimmerian female": "Cimmerienne",
"Cimmerian male": "Cimmerien",
"Khitan female": "Khitane",
"Khitan male": "Khitan",
"Stygian female": "Stygienne",
"Stygian male": "Stygien"
}
Hi Jose,
Currently using this package to help translate a pretty big Next.js project. It's working really well so far, and it allows for client-side toggling between locale strings which was something that was difficult to accomplish with other solutions, without having to mutate the url string (something that was somewhat of a deal-breaker).
However, this Next.js project needs to support IE11 😩 . Specifically, the appWithI18Next
function seems to only compile down to ES6 syntax (with arrow functions), which causes the app to break when running in IE:
We are currently looking into solutions for doing some custom transipling down to ES5 with in the next.js config using https://www.npmjs.com/package/next-transpile-modules, or potentially even patching the library locally to allow for IE11 support. Unsure how this will end up but figured I should open up an issue anyway.
Wondering how you feel about this? Would you be willing/have time to help us with this, or if you would maybe be open to us creating a PR?
Issue description:
The default namespace is not available in a SSR page when it has been set to something other than "translation".
Why this is a problem:
The browser requests from the server the file "translation.json", which does not exist; client receives 404 error.
Reasoning:
The developer should not be forced to use a specific filename for the default namespace, or if this is a "feature", then at least this should be mentioned in the documentation.
Inside "ni18n.config.js":
const ni18nConfig = {
supportedLngs: ["en", "et"],
ns: ["common"],
}
export default ni18nConfig
Inside "index.tsx" using SSR:
export const getStaticProps: GetStaticProps = async (context) => {
const result = await loadTranslations(ni18nConfig, context.locale)
console.log(result);
return {
props: {...result}
}
}
Expected Console.log output:
{
__ni18n_server__: { resources: { en: [Object] }, ns: [ 'common' ] }
}
Actual Console.log output:
{
__ni18n_server__: { resources: { en: [Object] }, ns: [ 'translation' ] }
}
I just want to know if this lib support app-router in nextjs 13 and how to configure
export const getStaticProps: GetStaticProps = async (props: any) => {
return {
props: {
...(await loadTranslations(ni18nConfig, props.locale, [
'common',
]))
},
}
}
obtains the "translation" namespace instead even though I do not have "translation" defined as a namespace
I have started using ni18n. I had the following ni18nConffig:
import type { Ni18nOptions } from 'ni18n'
export const ni18nConfig: Ni18nOptions = {
supportedLngs: ['en', 'es'],
ns: ['layout-default','specific-page'],
}
I understood that the first item is the default namespace and always gets loaded, and maybe this is were I was wrong.
In fact it worked ok until I used loadTranslations on getServerSideProps to load the specific-page namespace for a specific page. It loaded specific-page correctly, but not default-layout.
My getServerSideProps:
export const getServerSideProps = async (props) => {
return {
props: {
...(await loadTranslations(ni18nConfig, props.locale, ['specific-page'])),
},
}
}
I have solved it by including the defaultNS entry in ni18nConfig:
import type { Ni18nOptions } from 'ni18n'
export const ni18nConfig: Ni18nOptions = {
supportedLngs: ['en', 'es'],
ns: ['layout-default','specific-page'],
defaultNS: 'layout-default'
}
Now it works correctly. default-layout gets loaded on every page and specific-page only on specific page.
Also question: when I use loadTranslations, is the default namespace loaded on server side rendering? I'm assuming it does, but I'm not sure as the default behavior is to load client-side.
I'm using ni18n with next-auth and after I added session to my props
interface MyAppProps extends AppProps<{ session: Session }> {
emotionCache?: EmotionCache;
}
const MyApp: React.FunctionComponent<MyAppProps> = (props) => {
const { Component, emotionCache = clientSideEmotionCache, pageProps: { session, ...pageProps } } = props;
I started getting this type error in appWithI18Next
export default appWithI18Next/*<{ session: Session }>*/(MyApp, ni18nConfig);
TS2345: Argument of type 'FunctionComponent' is not assignable to parameter of type 'ElementType<AppInitialProps<{}> & { Component: NextComponentType<NextPageContext, any, any>; router: Router; __N_SSG?: boolean | undefined; __N_SSP?: boolean | undefined; __N_RSC?: boolean | undefined; } & { ...; }>'. Type 'FunctionComponent' is not assignable to type 'FunctionComponent<AppInitialProps<{}> & { Component: NextComponentType<NextPageContext, any, any>; router: Router; __N_SSG?: boolean | undefined; __N_SSP?: boolean | undefined; __N_RSC?: boolean | undefined; } & { ...; }>'. Types of parameters 'props' and 'props' are incompatible. Type 'AppInitialProps<{}> & { Component: NextComponentType<NextPageContext, any, any>; router: Router; __N_SSG?: boolean | undefined; __N_SSP?: boolean | undefined; __N_RSC?: boolean | undefined; } & { ...; }' is not assignable to type 'MyAppProps'.
When I extended it with generic like this
declare const appWithI18Next: <T>(WrappedComponent: ElementType<AppProps<T> & {
children?: ReactNode;
}>, options: Ni18nOptions) => ElementType;
error disappeared
I'm not sure if it's working, still finishing adding next-auth
I'm having some issues making the namespaces codesplitting for pages. I'm not using the NextJS internationalized routing.
I tried first to use the clientNamespaces
like this:
export const getStaticProps = async (props) => {
return {
props: {
...clientNamespaces(ni18nConfig, ['common', 'first-page']),
},
}
}
However it just load correctly the namespaces on the first load, when I change to another page is not loading the new namespaces correctly
After I tried the loadTranslations
like this:
export async function getStaticProps(props) {
return {
props: {
...(await loadTranslations(ni18nConfig, props.locale, [
"common",
"first-page",
])),
},
};
}
In this scenario the namespaces are loaded correctly without problem but then the changeLanguage stop working correctly (I guess because of the props.locale) whenever I change to another page with a different language than the default one, the language change to the default one
@JCQuintas
Can you release please?
I need this - #168
export const ni18nConfig = {
supportedLngs,
ns: [
"menu",
],
backend: {
loadPath: `${process.env.NEXT_BASE_PATH}/${localePath}`,
},
};
Env variables are not getting substituted at build time while building the project. Every other instance of NEXT_BASE_PATH
in the project is substituted with the value of NEXT_BASE_PATH
in next.js
While the setup works fine running locally, the deployed to vercel app returns empty translations by loadTranslations in getServerSideProps
GET | https://app.vercel.app/_next/data/HZ7wajlS0AuNQGjuW_9so/en/app/dashboard.json
{"pageProps":{"__ni18n_server__":{"resources":{"en":{"dashboard":{},"common":{}}},"ns":["dashboard","common"]}},"__N_SSP":true}
ni18n.config.ts
import ChainedBackend from 'i18next-chained-backend'
import HttpBackend from 'i18next-http-backend'
import LocalStorageBackend from 'i18next-localstorage-backend'
const isBrowser = typeof window !== 'undefined'
const isRunningOnLocalhost = !!process.env['NEXT_PUBLIC_IS_RUNNING_ON_LOCALHOST']
const localePath = '{{lng}}/{{ns}}.json'
export const ni18nConfig = {
supportedLngs: ['en'],
ns: ['common', 'auth', 'dashboard'],
use: isBrowser ? [ChainedBackend] : undefined,
defaultNS: 'common',
fallbackLng: 'en',
backend: isBrowser
? {
backends: [LocalStorageBackend, HttpBackend],
backendOptions: [{ expirationTime: 24 * 60 * 60 * 1000 }, { loadPath: `locales/${localePath}` }]
}
: isRunningOnLocalhost
? { loadPath: `apps/ui-web-app/public/locales/${localePath}` }
: { loadPath: `locales/${localePath}` },
interpolation: {
format: (value, format, lng, options) => {
...
}
}
}
next.config.js
// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require('@nrwl/next/plugins/with-nx')
"i18next": "^21.6.5",
"i18next-chained-backend": "^3.0.2",
"i18next-http-backend": "^1.3.1",
"i18next-localstorage-backend": "^3.1.3",
"next": "12.0.7",
"ni18n": "^1.0.3-rc.0",
/**
* @type {import('@nrwl/next/plugins/with-nx').WithNxOptions}
**/
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
const nextConfig = {
nx: {
// Set this to true if you would like to to use SVGR
// See: https://github.com/gregberge/svgr
svgr: false
},
images: {
domains: ['cdn.nordigen.com']
},
i18n: {
defaultLocale: 'en',
locales: ['en', 'es']
}
}
module.exports = withBundleAnalyzer(withNx(nextConfig))
const Dashboard: NextPage = () => (
<AuthGuard>
<RoutePermissionsGuard>
<SubscriptionGuard>
<DashboardLayout>
<DashboardPage />
</DashboardLayout>
</SubscriptionGuard>
</RoutePermissionsGuard>
</AuthGuard>
)
export const getServerSideProps = async ({ locale }) => {
const something = {
...(await loadTranslations(ni18nConfig, locale, ['dashboard']))
}
console.log('file: index.tsx ~ line 25 ~ getServerSideProps ~ something', JSON.stringify(something))
return {
props: something
}
}
export default Dashboard
{"i18next": "^21.6.5",
"i18next-chained-backend": "^3.0.2",
"i18next-http-backend": "^1.3.1",
"i18next-localstorage-backend": "^3.1.3",
"next": "12.0.7",
"ni18n": "^1.0.3-rc.0"}
Is there some way to tell Next.JS to reload the JSON files in public/locales/[lng]
when they change?
On browser page load (initial load before next-clientside-loading takes over or pressing F5), the translation-keys will appear for a short time until translation files are loaded.
I am using the basic public/locales/<lang>/foo.json
structure and no NextJS routing (no /de-DE/bla)
Hi, thanks for this lib!
I’m trying to use ni18n according to the example customer-language-selection and using the LanguageDetector
plugin (https://github.com/i18next/i18next-browser-languageDetector/).
Everything seems to work expect the langue detection, fallback lang is always used and stored instead the expected fr
(browser lang is fr_FR
) :
//ni18n.config.ts
import type { Ni18nOptions } from 'ni18n'
import LanguageDetector from 'i18next-browser-languagedetector'
export const ni18nConfig: Ni18nOptions = {
fallbackLng: 'en',
supportedLngs:['en', 'fr'],
use: [LanguageDetector], //load plugin here
ns: ['translation'],
react: {
useSuspense: false,
},
}
//_app.ts
function App({ Component, pageProps }: Props) {
//...
const locale = typeof window !== 'undefined'
&& window.localStorage.getItem('i18nextLng') //same key used by the plugin
useSyncLanguage(locale)
//...
}
If I register the plugin and init i18n before, it’s seems to work:
//ni18n.config.ts
import i18n from 'i18next'
import type { Ni18nOptions } from 'ni18n'
import LanguageDetector from 'i18next-browser-languagedetector'
const languageDetector = new LanguageDetector(null,{})
i18n.use(languageDetector).init() //register and init
export const ni18nConfig: Ni18nOptions = {
fallbackLng: 'en',
//...
//don't load plugin here
}
What I’am wrong or what’s the best way to handle this?
In my new project, I've opted to use Next.js 14 as the development framework, and for handling multilingual support, I chose ni18n because it doesn't require the cumbersome configuration that I truly dislike, which is associated with the multilingual libraries officially recommended by Next.js.
Getting back to the main issue, after installing ni18n, I encountered a persistent warning in the web terminal about inconsistencies between server-side and client-side rendering (this might not be the exact wording, but it conveys the gist). My initial thought was that there might be a bug in the new version of Next.js, leading me into a frustrating cycle of downgrading and encountering errors, then downgrading further and facing more issues.
I proceeded to search on Google for a solution but didn't find any useful information. However, I noticed that many similar issues pointed towards the same library - react-i18next, which I mentioned earlier. I decided to try downgrading this library, and after rolling back to version 11, the web terminal stopped displaying any rendering errors, and the pages were clear. It's hard to express my relief.
I wanted to share my experience with everyone, hoping that if you face similar issues, you can resolve them quickly.
Hi! 👋
Firstly, thanks for your work on this project! 🙂
Today I used patch-package to patch [email protected]
for the project I'm working on.
I need to set the locale upon i18next
instance init on Next.js during server-side rendering to avoid hydration issues. To do this, I tried using different i18next
plugins to get the locale from the cookies, but none worked. Next.js's native locale management didn't help either as they have very aggressive defaults that will take over the lcoale or assign its own config defaults.
What I did to solve this was to get the locale value from the cookies in a middleware, pass the locale to the pageProps and this is then read in the init step using the HOC provided by ni18n
.
Here is the diff that solved my problem. I know it should be made more configurable, right now the property for language in pageProps is hardcoded.
diff --git a/node_modules/ni18n/dist/cjs/app-with-i18next/app-with-i18next.js b/node_modules/ni18n/dist/cjs/app-with-i18next/app-with-i18next.js
index 6b5e8ee..e4ea304 100644
--- a/node_modules/ni18n/dist/cjs/app-with-i18next/app-with-i18next.js
+++ b/node_modules/ni18n/dist/cjs/app-with-i18next/app-with-i18next.js
@@ -100,7 +100,7 @@ var appWithI18Next = function (WrappedComponent, options) {
var ns = __ni18n_server__ || __ni18n_client__
? (0, common_1.uniqueArray)(__spreadArray(__spreadArray([], __read(((__ni18n_server__ === null || __ni18n_server__ === void 0 ? void 0 : __ni18n_server__.ns) || [])), false), __read(((__ni18n_client__ === null || __ni18n_client__ === void 0 ? void 0 : __ni18n_client__.ns) || [])), false))
: i18nextOptions.ns;
- var instance = (0, create_i18n_instance_1.createI18nInstance)(__assign(__assign(__assign(__assign({}, i18nextOptions), { lng: locale }), __ni18n_server__), { ns: ns }), plugins).instance;
+ var instance = (0, create_i18n_instance_1.createI18nInstance)(__assign(__assign(__assign(__assign({}, i18nextOptions), { lng: props.pageProps.language || locale }), __ni18n_server__), { ns: ns }), plugins).instance;
return instance;
}, [options, __ni18n_server__, locale]);
return (react_1.default.createElement(react_i18next_1.I18nextProvider, { i18n: i18nInstance },
diff --git a/node_modules/ni18n/dist/esm/app-with-i18next/app-with-i18next.js b/node_modules/ni18n/dist/esm/app-with-i18next/app-with-i18next.js
index 9ef7d7b..c1feab9 100644
--- a/node_modules/ni18n/dist/esm/app-with-i18next/app-with-i18next.js
+++ b/node_modules/ni18n/dist/esm/app-with-i18next/app-with-i18next.js
@@ -74,7 +74,7 @@ export var appWithI18Next = function (WrappedComponent, options) {
var ns = __ni18n_server__ || __ni18n_client__
? uniqueArray(__spreadArray(__spreadArray([], __read(((__ni18n_server__ === null || __ni18n_server__ === void 0 ? void 0 : __ni18n_server__.ns) || [])), false), __read(((__ni18n_client__ === null || __ni18n_client__ === void 0 ? void 0 : __ni18n_client__.ns) || [])), false))
: i18nextOptions.ns;
- var instance = createI18nInstance(__assign(__assign(__assign(__assign({}, i18nextOptions), { lng: locale }), __ni18n_server__), { ns: ns }), plugins).instance;
+ var instance = createI18nInstance(__assign(__assign(__assign(__assign({}, i18nextOptions), { lng: props.pageProps.language || locale }), __ni18n_server__), { ns: ns }), plugins).instance;
return instance;
}, [options, __ni18n_server__, locale]);
return (React.createElement(I18nextProvider, { i18n: i18nInstance },
This issue body was partially generated by patch-package.
Hi, using ni18n in Next.js.
I want locale code hide to url.
So, I made it according to the example (customer-language-selection)
Then the desired function was realized but, SSR not working.
When the page is delivered on the server side, it looks like English and changes to the language you set on the client after it is loaded.
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = nextConfig;
_app.tsx
import { appWithI18Next, useSyncLanguage } from "ni18n";
import { ni18nConfig } from "../../ni18n.config";
const MyApp = ({ Component, pageProps }: any) => {
const locale =
typeof window !== "undefined"
? (window.localStorage.getItem("MY_LANGUAGE") as string)
: "";
useSyncLanguage(locale);
return <Component {...pageProps} />;
};
export default appWithI18Next(MyApp, ni18nConfig);
ni18n.config.js
const supportedLngs = ["en-US", "ko-KR"];
export const ni18nConfig = {
/**
* Set `fallbackLng` to the `supportedLngs` array in order for them all to be loaded
*/
fallbackLng: supportedLngs,
supportedLngs,
ns: ["translation"],
react: {
useSuspense: false,
},
};
index.tsx
import { loadTranslations } from "ni18n";
import { useTranslation } from "react-i18next";
import { ni18nConfig } from "../../ni18n.config";
/**
* Manually change the language and store the selected on localStorage
*/
const changeLanguage = (i18n: any, language: any) => {
window.localStorage.setItem("MY_LANGUAGE", language);
i18n.changeLanguage(language);
};
const languages = [
{ code: "en-US", translateKey: "english" },
{ code: "ko-KR", translateKey: "korean" },
];
function Home() {
const { t, i18n } = useTranslation();
return (
<div>
<h1>{t("test")}</h1>
{i18n.language}
{languages.map((language) => (
<button
data-id={`${language.code}-button`}
className={i18n.language === language.code ? "active" : undefined}
onClick={() => changeLanguage(i18n, language.code)}
key={language.code}
>
{t(language.translateKey)}
</button>
))}
</div>
);
}
export const getServerSideProps = async (props) => {
return {
props: {
...(await loadTranslations(ni18nConfig, props.locale, ["translation"])),
},
};
};
export default Home;
What is the hook import {useSyncLanguage} from 'ni18n'
used for?
First of all, thank you for creating this project! It's working great for me except for one small problem. I'd like to completely decouple initial locale from the Next router, and use a cookie/manually set the locale instead (I am not using domain or subpath internationalized routing).
In my page's getServerSideProps
, I call loadTranslations
with a locale from a cookie. If this locale (in this case, Spanish), does not match the defaultLocale
from the config (English), then the server will load the Spanish translations, but appWithi18next
will init i18n with English, causing the client to crash because the English translations weren't loaded. If I include useSyncLanguage
near my app root it changes the language to Spanish and not crash, but the browser will still fetch the English translations and then re-change the i18n language back to English asynchronously once they are loaded. This causes a flicker of Spanish (desired) -> English content (not desired).
In app-with-i18next.tsx, the initial locale is pulled from Next's props.router.locale
, which a read-only field set by Next. From what I can tell from reading the Next source code, the only way to consistently force this value to be based on a cookie is with a kind of hacky redirect from middleware. I'm wondering if it would be possible to add either an optional check to props.pageProps.locale
or allow some optional function to set the initial locale? I'd be happy to work on a PR for this feature if you think it's worth adding to this project. Or if maybe there is some existing way to solve this issue with configuration, please let me know. Thank you!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.