Skip to content

Commit

Permalink
feat: improve i18n configuration (SoftwareBrothers#1444)
Browse files Browse the repository at this point in the history
  • Loading branch information
ariansobczak-rst authored Mar 14, 2023
1 parent 46f524f commit 1731eb2
Show file tree
Hide file tree
Showing 61 changed files with 1,405 additions and 1,143 deletions.
4 changes: 2 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"printWidth": 120,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5",
"trailingComma": "all",
"semi": false,
"overrides": [
{
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
"commander": "^5.1.0",
"flat": "5.0.1",
"i18next": "^21.9.2",
"i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.1.1",
"lodash": "^4.17.21",
"ora": "^5.4.1",
"prop-types": "^15.7.2",
Expand Down
4 changes: 2 additions & 2 deletions src/adminjs-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export interface AdminJSOptions {
* text: 'I am fetched from the backend',
* }
* },
* component: 'SomeStats',
* component: 'CustomPage',
* },
* anotherPage: {
* label: "TypeScript page",
Expand Down Expand Up @@ -240,7 +240,7 @@ export interface AdminJSOptions {
* Check out the [i18n tutorial]{@tutorial i18n} to see how
* internationalization in AdminJS works.
*/
locale?: Locale;
locale?: Locale | ((admin?: CurrentAdmin) => Locale | Promise<Locale>);

/**
* rollup bundle options;
Expand Down
45 changes: 6 additions & 39 deletions src/adminjs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import merge from 'lodash/merge'
import * as path from 'path'
import * as fs from 'fs'
import i18n, { i18n as I18n } from 'i18next'
import { FC } from 'react'
import type { FC } from 'react'

import { AdminJSOptionsWithDefault, AdminJSOptions } from './adminjs-options.interface'
import BaseResource from './backend/adapters/resource/base-resource'
Expand All @@ -16,9 +15,9 @@ import { ACTIONS } from './backend/actions'

import loginTemplate from './frontend/login-template'
import { ListActionResponse } from './backend/actions/list/list-action'
import { combineTranslations, Locale } from './locale/config'
import { locales } from './locale'
import { TranslateFunctions, createFunctions } from './utils/translate-functions.factory'
import { defaultLocale } from './locale'
import { Locale } from './locale/config'
import { TranslateFunctions } from './utils/translate-functions.factory'
import { relativeFilePathResolver } from './utils/file-resolver'
import { getComponentHtml } from './backend/utils'
import { ComponentLoader } from './backend/utils/component-loader'
Expand Down Expand Up @@ -72,8 +71,6 @@ class AdminJS {

public locale!: Locale

public i18n!: I18n

public translateFunctions!: TranslateFunctions

public componentLoader: ComponentLoader
Expand Down Expand Up @@ -117,7 +114,8 @@ class AdminJS {

this.resolveBabelConfigPath()

this.initI18n()
// To be removed when Login page will be renedered on client side
this.locale = defaultLocale

const { databases, resources } = this.options

Expand All @@ -127,37 +125,6 @@ class AdminJS {
this.resources = resourcesFactory.buildResources({ databases, resources })
}

initI18n(): void {
const language = this.options.locale?.language || locales.en.language
const defaultTranslations = locales[language]?.translations || locales.en.translations
const availableLanguages = this.options.locale?.availableLanguages || [language]
this.locale = {
translations: combineTranslations(defaultTranslations, this.options.locale?.translations),
language,
availableLanguages,
}
if (i18n.isInitialized) {
i18n.addResourceBundle(this.locale.language, 'translation', this.locale.translations)
} else {
i18n.init({
lng: this.locale.language,
initImmediate: false, // loads translations immediately
resources: {
[this.locale.language]: {
translation: this.locale.translations,
},
},
})
}

// mixin translate functions to AdminJS instance so users will be able to
// call AdminJS.translateMessage(...)
this.translateFunctions = createFunctions(i18n)
Object.getOwnPropertyNames(this.translateFunctions).forEach((translateFunctionName) => {
this[translateFunctionName] = this.translateFunctions[translateFunctionName]
})
}

/**
* Registers various database adapters written for AdminJS.
*
Expand Down
21 changes: 6 additions & 15 deletions src/backend/utils/custom-views/get-component-html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { ServerStyleSheet, StyleSheetManager, ThemeProvider } from 'styled-compo
import { Provider } from 'react-redux'
import { I18nextProvider } from 'react-i18next'
import { combineStyles } from '@adminjs/design-system'
import i18n from 'i18next'
import { Store } from 'redux'

import { getAssets, getBranding, getFaviconFromBranding } from '../../../backend/utils/options-parser/options-parser'
Expand All @@ -16,6 +15,7 @@ import createStore, {
ReduxState,
} from '../../../frontend/store/store'
import AdminJS from '../../../adminjs'
import initTranslations from '../../../frontend/utils/adminjs.i18n'

export async function getComponentHtml<T extends Record<string, unknown>>(
Component: React.FC<T>,
Expand All @@ -41,27 +41,18 @@ export async function getComponentHtml<T extends Record<string, unknown>>(

const theme = combineStyles((branding && branding.theme) || {})
const { locale } = store.getState()
i18n
.init({
resources: {
[locale.language]: {
translation: locale.translations,
},
},
lng: locale.language,
interpolation: { escapeValue: false },
})
const { i18n } = initTranslations(locale)

const sheet = new ServerStyleSheet()

const component = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
<I18nextProvider i18n={i18n}>
<Component {...props} />
</ThemeProvider>
</I18nextProvider>
</I18nextProvider>
</ThemeProvider>
</Provider>
</StyleSheetManager>,
)
Expand Down
13 changes: 13 additions & 0 deletions src/backend/utils/options-parser/options-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import merge from 'lodash/merge'
import AdminJS from '../../../adminjs'
import { AdminJSOptions, Assets, BrandingOptions } from '../../../adminjs-options.interface'
import { CurrentAdmin } from '../../../current-admin.interface'
import { defaultLocale, Locale } from '../../../locale'
import ViewHelpers from '../view-helpers/view-helpers'

const defaultBranding: AdminJSOptions['branding'] = {
Expand Down Expand Up @@ -45,6 +46,18 @@ export const getBranding = async (
return merged
}

export const getLocales = async (
admin: AdminJS,
currentAdmin?: CurrentAdmin,
): Promise<Locale> => {
const { locale } = admin.options || {}
const computed = typeof locale === 'function'
? await locale(currentAdmin)
: locale

return merge({}, defaultLocale, computed)
}

export const getFaviconFromBranding = (branding: BrandingOptions): string => {
if (branding.favicon) {
const { favicon } = branding
Expand Down
57 changes: 21 additions & 36 deletions src/frontend/bundle-entry.jsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,41 @@
import React from 'react'
import React, { Suspense } from 'react'
import { I18nextProvider } from 'react-i18next'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
import { ThemeProvider } from 'styled-components'
import { initReactI18next } from 'react-i18next'
import i18n from 'i18next'
import merge from 'lodash/merge'

import App from './components/application'
import BasePropertyComponent, { CleanPropertyComponent } from './components/property-type'
import createStore from './store/store'
import ViewHelpers from '../backend/utils/view-helpers/view-helpers'
import { flat } from '../utils/flat'
import * as AppComponents from './components/app'
import App from './components/application'
import BasePropertyComponent, { CleanPropertyComponent } from './components/property-type'
import withNotice from './hoc/with-notice'
import * as Hooks from './hooks'
import createStore from './store/store'
import ApiClient from './utils/api-client'
import withNotice from './hoc/with-notice'
import { flat } from '../utils/flat'
import { locales } from '../locale'
import initTranslations from './utils/adminJS.i18n'

const env = {
NODE_ENV: process.env.NODE_ENV || 'development',
}

const store = createStore(window.REDUX_STATE)
const theme = window.THEME
const defaultLocale = window.REDUX_STATE.locale
const currentLocale = JSON.parse(window.localStorage.getItem('locale')) || defaultLocale.language

i18n.use(initReactI18next).init({
resources: Object.keys(locales).reduce(
(memo, locale) => ({
...memo,
[locale]: {
translation: merge(
locales[locale].translations,
locale === defaultLocale.language ? defaultLocale.translations : {},
),
},
}),
{},
),
lng: currentLocale,
interpolation: { escapeValue: false },
})
const { locale } = store.getState()
const { i18n } = initTranslations(locale)

const Application = (
<Provider store={store}>
<ThemeProvider theme={theme}>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeProvider>
</Provider>
<Suspense fallback="...is loading">
<Provider store={store}>
<ThemeProvider theme={theme}>
<I18nextProvider i18n={i18n}>
<BrowserRouter>
<App />
</BrowserRouter>
</I18nextProvider>
</ThemeProvider>
</Provider>
</Suspense>
)

// eslint-disable-next-line no-undef
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { ButtonGroupProps } from '@adminjs/design-system'
import { expect } from 'chai'
import i18n from 'i18next'
import factory from 'factory-girl'
import { ActionJSON } from '../../../interfaces'
import { ActionJSON, ModalFunctions } from '../../../interfaces'
import { actionsToButtonGroup } from './actions-to-button-group'
import { createFunctions } from '../../../../utils/translate-functions.factory'
import '../../spec/action-json.factory'

const translateFunctions = createFunctions(i18n)

const modalFunctions = {
const modalFunctions: ModalFunctions = {
closeModal: () => { /* noop */ },
openModal: (data) => { /* noop */ },
openModal: () => { /* noop */ },
}

describe('actionsToButtonGroup', () => {
Expand Down
10 changes: 6 additions & 4 deletions src/frontend/components/app/breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { RecordJSON, ResourceJSON } from '../../interfaces'
import { getActionElementCss } from '../../utils'

export const BreadcrumbLink = styled(Link)`
color: ${({ theme }): string => theme.colors.grey100};
color: ${({ theme }): string => theme.colors.grey60};
font-family: ${({ theme }): string => theme.font};
line-height: ${({ theme }): string => theme.lineHeights.default};
font-size: ${({ theme }): string => theme.fontSizes.default};
Expand All @@ -26,13 +26,14 @@ export const BreadcrumbLink = styled(Link)`
}
&:last-child {
color: ${({ theme }): string => theme.colors.text};
&:after {
content: '';
}
}
`

export const BreadcrumbText = styled(Text)`
export const BreadcrumbText = styled<any>(Text)`
color: ${({ theme }): string => theme.colors.grey100};
font-family: ${({ theme }): string => theme.font};
font-weight: ${({ theme }): string => theme.fontWeights.normal.toString()};
Expand Down Expand Up @@ -81,8 +82,9 @@ const Breadcrumbs: React.FC<BreadcrumbProps> = (props) => {
const listAction = resource.resourceActions.find(({ name }) => name === 'list')
const action = resource.actions.find((a) => a.name === actionName)
const h = new ViewHelpers()
const { translateLabel: tl } = useTranslation()
const { tl, ta } = useTranslation()
const contentTag = getActionElementCss(resource.id, actionName, 'breadcrumbs')

return (
<Box flexGrow={1} className={cssClass('Breadcrumbs')} data-css={contentTag}>
<BreadcrumbLink to={h.dashboardUrl()}>{tl('dashboard')}</BreadcrumbLink>
Expand All @@ -93,7 +95,7 @@ const Breadcrumbs: React.FC<BreadcrumbProps> = (props) => {
) : (
<BreadcrumbText>{resource.name}</BreadcrumbText>
)}
{action && action.name !== 'list' && <BreadcrumbLink to="#">{action.label}</BreadcrumbLink>}
{action && action.name !== 'list' && <BreadcrumbLink to="#">{ta(action.label)}</BreadcrumbLink>}
</Box>
)
}
Expand Down
Loading

0 comments on commit 1731eb2

Please sign in to comment.