From 24f8d68255d15986328ad022d5559b741427fa38 Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Tue, 4 Aug 2020 13:28:43 -0700 Subject: [PATCH 1/5] feat: properly translate table chart --- packages/superset-ui-core/src/index.ts | 1 + .../superset-ui-core/src/utils/logging.ts | 24 +++++ packages/superset-ui-translation/package.json | 3 + .../superset-ui-translation/src/Translator.ts | 90 +++++++++++++---- .../src/TranslatorSingleton.ts | 20 +++- packages/superset-ui-translation/src/index.ts | 2 +- packages/superset-ui-translation/src/types.ts | 20 ---- .../src/types/index.ts | 59 +++++++++++ .../superset-ui-translation/src/types/jed.ts | 35 +++++++ .../test/Translator.test.ts | 97 ++++++++++++++++++- .../test/languagePacks/en.ts | 2 +- .../test/languagePacks/zh.ts | 4 +- .../src/DataTable/DataTable.tsx | 17 ++-- .../DataTable/components/SelectPageSize.tsx | 67 +++++++------ plugins/plugin-chart-table/src/TableChart.tsx | 44 +++++++-- .../plugin-chart-table/src/controlPanel.tsx | 16 ++- plugins/plugin-chart-table/src/i18n.ts | 41 ++++++++ 17 files changed, 444 insertions(+), 98 deletions(-) create mode 100644 packages/superset-ui-core/src/utils/logging.ts delete mode 100644 packages/superset-ui-translation/src/types.ts create mode 100644 packages/superset-ui-translation/src/types/index.ts create mode 100644 packages/superset-ui-translation/src/types/jed.ts create mode 100644 plugins/plugin-chart-table/src/i18n.ts diff --git a/packages/superset-ui-core/src/index.ts b/packages/superset-ui-core/src/index.ts index 8714574216..6918a8fb26 100644 --- a/packages/superset-ui-core/src/index.ts +++ b/packages/superset-ui-core/src/index.ts @@ -9,5 +9,6 @@ export { default as isDefined } from './utils/isDefined'; export { default as isRequired } from './utils/isRequired'; export { default as makeSingleton } from './utils/makeSingleton'; export { default as promiseTimeout } from './utils/promiseTimeout'; +export { default as logging } from './utils/logging'; export * from './types'; diff --git a/packages/superset-ui-core/src/utils/logging.ts b/packages/superset-ui-core/src/utils/logging.ts new file mode 100644 index 0000000000..95fcd20b8b --- /dev/null +++ b/packages/superset-ui-core/src/utils/logging.ts @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +const logger = console; + +/** + * Superset frontend logger, currently just dump to console directly. + */ +export default logger; diff --git a/packages/superset-ui-translation/package.json b/packages/superset-ui-translation/package.json index a215a6bc0e..bb460b5c5d 100644 --- a/packages/superset-ui-translation/package.json +++ b/packages/superset-ui-translation/package.json @@ -28,6 +28,9 @@ "dependencies": { "jed": "^1.1.1" }, + "peerDependencies": { + "@superset-ui/core": "^0.14.18" + }, "publishConfig": { "access": "public" } diff --git a/packages/superset-ui-translation/src/Translator.ts b/packages/superset-ui-translation/src/Translator.ts index 7a564d4148..d73789b3bd 100644 --- a/packages/superset-ui-translation/src/Translator.ts +++ b/packages/superset-ui-translation/src/Translator.ts @@ -1,20 +1,33 @@ -import UntypedJed from 'jed'; -import { TranslatorConfig } from './types'; +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import UntyppedJed from 'jed'; +import { logging } from '@superset-ui/core'; +import { Jed, TranslatorConfig, Locale, Translations, LocaleData, LanguagePack } from './types'; -interface Jed { - translate(input: string): Jed; - ifPlural(value: number, plural: string): Jed; - fetch(...args: unknown[]): string; -} - -const DEFAULT_LANGUAGE_PACK = { +const DEFAULT_LANGUAGE_PACK: LanguagePack = { domain: 'superset', locale_data: { superset: { '': { domain: 'superset', lang: 'en', - plural_forms: 'nplurals=1; plural=0', + plural_forms: 'nplurals=2; plural=(n != 1)', }, }, }, @@ -23,25 +36,62 @@ const DEFAULT_LANGUAGE_PACK = { export default class Translator { i18n: Jed; + locale: Locale; + constructor(config: TranslatorConfig = {}) { const { languagePack = DEFAULT_LANGUAGE_PACK } = config; // eslint-disable-next-line @typescript-eslint/no-unsafe-call - this.i18n = new UntypedJed(languagePack) as Jed; + this.i18n = new UntyppedJed(languagePack) as Jed; + this.locale = this.i18n.options.locale_data.superset[''].lang as Locale; + } + + /** + * Add additional translations on the fly, used by plugins. + */ + addTranslation(key: string, texts: ReadonlyArray) { + const translations = this.i18n.options.locale_data.superset; + if (key in translations) { + logging.warn(`Duplicate translation key "${key}", will override.`); + } + translations[key] = texts; + } + + /** + * Add a series of translations. + */ + addTranslations(translations: Translations) { + if (translations && !Array.isArray(translations)) { + Object.entries(translations).forEach(([key, vals]) => this.addTranslation(key, vals)); + } else { + logging.warn('Invalid translations'); + } + } + + addLocaleData(data: LocaleData) { + // always fallback to English + const translations = data?.[this.locale] || data?.en; + if (translations) { + this.addTranslations(translations); + } else { + logging.warn('Invalid locale data'); + } } translate(input: string, ...args: unknown[]): string { return this.i18n.translate(input).fetch(...args); } - translateWithNumber( - singular: string, - plural: string, - num: number = 0, - ...args: unknown[] - ): string { + translateWithNumber(key: string, ...args: unknown[]): string { + const [plural, num, ...rest] = args; + if (typeof plural === 'number') { + return this.i18n + .translate(key) + .ifPlural(plural, key) + .fetch(plural, num, ...args); + } return this.i18n - .translate(singular) - .ifPlural(num, plural) - .fetch(...args); + .translate(key) + .ifPlural(num as number, plural as string) + .fetch(...rest); } } diff --git a/packages/superset-ui-translation/src/TranslatorSingleton.ts b/packages/superset-ui-translation/src/TranslatorSingleton.ts index 50b134bfaa..bfc6731bc7 100644 --- a/packages/superset-ui-translation/src/TranslatorSingleton.ts +++ b/packages/superset-ui-translation/src/TranslatorSingleton.ts @@ -1,7 +1,7 @@ /* eslint no-console: 0 */ import Translator from './Translator'; -import { TranslatorConfig } from './types'; +import { TranslatorConfig, Translations, LocaleData } from './types'; let singleton: Translator | undefined; let isConfigured = false; @@ -25,12 +25,24 @@ function getInstance() { return singleton; } +function addTranslation(key: string, translations: string[]) { + return getInstance().addTranslation(key, translations); +} + +function addTranslations(translations: Translations) { + return getInstance().addTranslations(translations); +} + +function addLocaleData(data: LocaleData) { + return getInstance().addLocaleData(data); +} + function t(input: string, ...args: unknown[]) { return getInstance().translate(input, ...args); } -function tn(singular: string, plural: string, num?: number, ...args: unknown[]) { - return getInstance().translateWithNumber(singular, plural, num, ...args); +function tn(key: string, ...args: unknown[]) { + return getInstance().translateWithNumber(key, ...args); } -export { configure, t, tn }; +export { configure, addTranslation, addTranslations, addLocaleData, t, tn }; diff --git a/packages/superset-ui-translation/src/index.ts b/packages/superset-ui-translation/src/index.ts index 78eaa60106..e5dffc7e05 100644 --- a/packages/superset-ui-translation/src/index.ts +++ b/packages/superset-ui-translation/src/index.ts @@ -1,2 +1,2 @@ -export { configure, t, tn } from './TranslatorSingleton'; +export * from './TranslatorSingleton'; export * from './types'; diff --git a/packages/superset-ui-translation/src/types.ts b/packages/superset-ui-translation/src/types.ts deleted file mode 100644 index 038036a52b..0000000000 --- a/packages/superset-ui-translation/src/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface LanguagePack { - domain: string; - /* eslint-disable-next-line camelcase */ - locale_data: { - superset: { - [key: string]: - | string[] - | { - domain: string; - /* eslint-disable-next-line camelcase */ - plural_forms: string; - lang: string; - }; - }; - }; -} - -export interface TranslatorConfig { - languagePack?: LanguagePack; -} diff --git a/packages/superset-ui-translation/src/types/index.ts b/packages/superset-ui-translation/src/types/index.ts new file mode 100644 index 0000000000..f641ec8fab --- /dev/null +++ b/packages/superset-ui-translation/src/types/index.ts @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Jed as BaseJed, JedOptions, DomainData, Translations } from './jed'; + +export { Translations } from './jed'; + +/** + * Superset supported languages. + */ +export type Locale = 'de' | 'en' | 'es' | 'fr' | 'it' | 'ja' | 'ko' | 'pt' | 'pt_BR' | 'ru' | 'zh'; // supported locales in Superset + +/** + * Language pack provided to `jed`. + */ +export type LanguagePack = JedOptions & { + // eslint-disable-next-line camelcase + locale_data: { + superset: DomainData & { + '': { + domain: 'superset'; + lang: Locale; + // eslint-disable-next-line camelcase + plural_forms: string; + }; + }; + }; +}; + +export interface Jed extends BaseJed { + options: LanguagePack; +} + +/** + * Config options for Translator class. + */ +export interface TranslatorConfig { + languagePack?: LanguagePack; +} + +/** + * Key-value mapping of translation key and the translations. + */ +export type LocaleData = Partial>; diff --git a/packages/superset-ui-translation/src/types/jed.ts b/packages/superset-ui-translation/src/types/jed.ts new file mode 100644 index 0000000000..1353c403ae --- /dev/null +++ b/packages/superset-ui-translation/src/types/jed.ts @@ -0,0 +1,35 @@ +/** + * Translations for a language in the format of { key: [singular, plural, ...]}. + */ +export type Translations = { + [key: string]: ReadonlyArray; +}; + +export interface DomainConfig { + domain: string; + lang: string; + // eslint-disable-next-line camelcase + plural_forms: string; +} + +export type DomainData = { '': DomainConfig } & { + [key: string]: ReadonlyArray | DomainConfig; +}; + +export interface JedOptions { + domain: string; + // eslint-disable-next-line camelcase + locale_data: { + [domain: string]: DomainData; + }; +} + +export interface Jed { + translate(input: string): Jed; + + ifPlural(value: number, plural: string): Jed; + + fetch(...args: unknown[]): string; + + options: JedOptions; +} diff --git a/packages/superset-ui-translation/test/Translator.test.ts b/packages/superset-ui-translation/test/Translator.test.ts index 2a5be0975d..616520a6a7 100644 --- a/packages/superset-ui-translation/test/Translator.test.ts +++ b/packages/superset-ui-translation/test/Translator.test.ts @@ -1,10 +1,20 @@ import Translator from '../src/Translator'; +import { + configure, + t, + tn, + addLocaleData, + addTranslation, + addTranslations, +} from '../src/TranslatorSingleton'; import languagePackZh from './languagePacks/zh'; +import languagePackEn from './languagePacks/en'; + +configure({ + languagePack: languagePackEn, +}); describe('Translator', () => { - it('exists', () => { - expect(Translator).toBeDefined(); - }); describe('new Translator(config)', () => { it('initializes when config is not specified', () => { expect(new Translator()).toBeInstanceOf(Translator); @@ -32,6 +42,7 @@ describe('Translator', () => { }); it('translates template text with an argument', () => { expect(translator.translate('Copy of %s', 1)).toEqual('1 的副本'); + expect(translator.translate('Copy of %s', 2)).toEqual('2 的副本'); }); it('translates template text with multiple arguments', () => { expect(translator.translate('test %d %d', 1, 2)).toEqual('test 1 2'); @@ -52,7 +63,7 @@ describe('Translator', () => { }); it('translates template text with an argument', () => { expect(translator.translateWithNumber('Copy of %s', 'Copies of %s', 12, 12)).toEqual( - '12 的副本本本', + '12 的副本', ); }); it('translates template text with multiple arguments', () => { @@ -61,4 +72,82 @@ describe('Translator', () => { ); }); }); + describe('.translateWithNumber(key, num, ...args)', () => { + const translator = new Translator({ + languagePack: languagePackEn, + }); + it('translates template text with an argument', () => { + expect(translator.translateWithNumber('%s copies', 1)).toEqual('1 copy'); + expect(translator.translateWithNumber('%s copies', 2)).toEqual('2 copies'); + }); + }); + + // Extending language pack + describe('.addTranslation(...)', () => { + it('can add new translation', () => { + addTranslation('haha', ['Hahaha']); + expect(t('haha')).toEqual('Hahaha'); + }); + }); + + describe('.addTranslations(...)', () => { + it('can add new translations', () => { + addTranslations({ + foo: ['bar', '%s bars'], + bar: ['foo'], + }); + // previous translation still exists + expect(t('haha')).toEqual('Hahaha'); + // new translations work as expected + expect(tn('foo', 1)).toEqual('bar'); + expect(tn('foo', 2)).toEqual('2 bars'); + expect(tn('bar', 2)).toEqual('bar'); + }); + it('throw warning on invalid arguments', () => { + expect(() => addTranslations(undefined as never)).toThrow('Invalid translations'); + expect(tn('bar', '2 foo', 2)).toEqual('2 foo'); + }); + it('throw warning on duplicates', () => { + expect(() => { + addTranslations({ + haha: ['this is duplciate'], + }); + }).toThrow('Duplicate translation key "haha"'); + expect(t('haha')).toEqual('Hahaha'); + }); + }); + + describe('.addLocaleData(...)', () => { + it('can add new translations for language', () => { + addLocaleData({ + en: { + yes: ['ok'], + }, + }); + expect(t('yes')).toEqual('ok'); + }); + it('throw on unknown locale', () => { + expect(() => { + addLocaleData({ + zh: { + haha: ['yes'], + }, + }); + }).toThrow('Invalid locale data'); + }); + it('missing locale falls back to English', () => { + configure({ + languagePack: languagePackZh, + }); + // expect and error because zh is not current locale + expect(() => { + addLocaleData({ + en: { + yes: ['OK'], + }, + }); + }).not.toThrow(); + expect(t('yes')).toEqual('OK'); + }); + }); }); diff --git a/packages/superset-ui-translation/test/languagePacks/en.ts b/packages/superset-ui-translation/test/languagePacks/en.ts index cd4cdcef9e..80a777a410 100644 --- a/packages/superset-ui-translation/test/languagePacks/en.ts +++ b/packages/superset-ui-translation/test/languagePacks/en.ts @@ -10,7 +10,7 @@ const languagePack: LanguagePack = { lang: 'en', }, second: [''], - 'Copy of %s': [''], + '%s copies': ['%s copy', '%s copies'], }, }, }; diff --git a/packages/superset-ui-translation/test/languagePacks/zh.ts b/packages/superset-ui-translation/test/languagePacks/zh.ts index 53434ca2bb..f03a266d93 100644 --- a/packages/superset-ui-translation/test/languagePacks/zh.ts +++ b/packages/superset-ui-translation/test/languagePacks/zh.ts @@ -6,11 +6,11 @@ const languagePack: LanguagePack = { superset: { '': { domain: 'superset', - plural_forms: 'nplurals=2; plural=(n != 1)', + plural_forms: 'nplurals=1; plural=0;', lang: 'zh', }, second: ['秒'], - 'Copy of %s': ['%s 的副本', '%s 的副本本本'], + 'Copy of %s': ['%s 的副本'], }, }, }; diff --git a/plugins/plugin-chart-table/src/DataTable/DataTable.tsx b/plugins/plugin-chart-table/src/DataTable/DataTable.tsx index 248fcaf832..830c9520c3 100644 --- a/plugins/plugin-chart-table/src/DataTable/DataTable.tsx +++ b/plugins/plugin-chart-table/src/DataTable/DataTable.tsx @@ -30,20 +30,21 @@ import { } from 'react-table'; import matchSorter from 'match-sorter'; import GlobalFilter, { GlobalFilterProps } from './components/GlobalFilter'; -import SelectPageSize, { SizeOption } from './components/SelectPageSize'; +import SelectPageSize, { SelectPageSizeProps, SizeOption } from './components/SelectPageSize'; import SimplePagination from './components/Pagination'; import useSticky from './hooks/useSticky'; export interface DataTableProps extends TableOptions { tableClassName?: string; searchInput?: boolean | GlobalFilterProps['searchInput']; + selectPageSize?: boolean | SelectPageSizeProps['selectRenderer']; pageSizeOptions?: SizeOption[]; // available page size options maxPageItemCount?: number; hooks?: PluginHook[]; // any additional hooks width?: string | number; height?: string | number; pageSize?: number; - noResultsText?: string | ((filterString: string) => ReactNode); + noResults?: string | ((filterString: string) => ReactNode); sticky?: boolean; wrapperRef?: MutableRefObject; } @@ -65,7 +66,8 @@ export default function DataTable({ maxPageItemCount = 9, sticky: doSticky, searchInput = true, - noResultsText = 'No data found', + selectPageSize, + noResults = 'No data found', hooks, wrapperRef: userWrapperRef, ...moreUseTableOptions @@ -186,9 +188,7 @@ export default function DataTable({ ) : ( - {typeof noResultsText === 'function' - ? noResultsText(filterValue as string) - : noResultsText} + {typeof noResults === 'function' ? noResults(filterValue as string) : noResults} )} @@ -216,8 +216,9 @@ export default function DataTable({ {hasPagination ? ( ) : null} diff --git a/plugins/plugin-chart-table/src/DataTable/components/SelectPageSize.tsx b/plugins/plugin-chart-table/src/DataTable/components/SelectPageSize.tsx index 5d48e576f2..c098e2c5c9 100644 --- a/plugins/plugin-chart-table/src/DataTable/components/SelectPageSize.tsx +++ b/plugins/plugin-chart-table/src/DataTable/components/SelectPageSize.tsx @@ -20,10 +20,41 @@ import React from 'react'; export type SizeOption = number | [number, string]; -export interface SelectPageSizeProps { - sizeOptions: SizeOption[]; - currentSize?: number; +export interface SelectPageSizeRendererProps { + current: number; + options: SizeOption[]; + onChange: SelectPageSizeProps['onChange']; +} + +function DefaultSelectRenderer({ current, options, onChange }: SelectPageSizeRendererProps) { + return ( + + Show{' '} + {' '} + entries + + ); +} + +export interface SelectPageSizeProps extends SelectPageSizeRendererProps { total?: number; + selectRenderer?: typeof DefaultSelectRenderer; onChange: (pageSize: number) => void; } @@ -33,8 +64,9 @@ function getOptionValue(x: SizeOption) { export default React.memo(function SelectPageSize({ total, - sizeOptions, - currentSize, + options: sizeOptions, + current: currentSize, + selectRenderer, onChange, }: SelectPageSizeProps) { const sizeOptionValues = sizeOptions.map(getOptionValue); @@ -53,27 +85,6 @@ export default React.memo(function SelectPageSize({ ); } const current = currentSize === undefined ? sizeOptionValues[0] : currentSize; - return ( - - Show{' '} - {' '} - entries - - ); + const SelectRenderer = selectRenderer || DefaultSelectRenderer; + return ; }); diff --git a/plugins/plugin-chart-table/src/TableChart.tsx b/plugins/plugin-chart-table/src/TableChart.tsx index df407edd6e..7077085a53 100644 --- a/plugins/plugin-chart-table/src/TableChart.tsx +++ b/plugins/plugin-chart-table/src/TableChart.tsx @@ -20,11 +20,16 @@ import React, { useState, useMemo, useCallback } from 'react'; import { ColumnInstance, DefaultSortTypes, ColumnWithLooseAccessor } from 'react-table'; import { extent as d3Extent, max as d3Max } from 'd3-array'; import { FaSort, FaSortUp as FaSortAsc, FaSortDown as FaSortDesc } from 'react-icons/fa'; -import { t } from '@superset-ui/translation'; +import { t, tn } from '@superset-ui/translation'; import { DataRecordValue, DataRecord } from '@superset-ui/chart'; import { TableChartTransformedProps, DataType, DataColumnMeta } from './types'; -import DataTable, { DataTableProps, SearchInputProps, SizeOption } from './DataTable'; +import DataTable, { + DataTableProps, + SearchInputProps, + SelectPageSizeRendererProps, + SizeOption, +} from './DataTable'; import Styles from './Styles'; import formatValue from './utils/formatValue'; import { PAGE_SIZE_OPTIONS } from './controlPanel'; @@ -98,7 +103,7 @@ function SearchInput({ count, value, onChange }: SearchInputProps) { {t('Search')}{' '} @@ -106,6 +111,32 @@ function SearchInput({ count, value, onChange }: SearchInputProps) { ); } +function SelectPageSize({ options, current, onChange }: SelectPageSizeRendererProps) { + return ( + + {t('page_size.show')}{' '} + {' '} + {t('page_size.entries')} + + ); +} + export default function TableChart( props: TableChartTransformedProps & { sticky?: DataTableProps['sticky']; @@ -257,16 +288,15 @@ export default function TableChart( columns={columns} data={data} tableClassName="table table-striped table-condensed" - searchInput={includeSearch && SearchInput} pageSize={pageSize} pageSizeOptions={pageSizeOptions} width={width} height={height} // 9 page items in > 340px works well even for 100+ pages maxPageItemCount={width > 340 ? 9 : 7} - noResultsText={(filter: string) => - t(filter ? 'No matching records found' : 'No records found') - } + noResults={(filter: string) => t(filter ? 'No matching records found' : 'No records found')} + searchInput={includeSearch && SearchInput} + selectPageSize={pageSize !== null && SelectPageSize} // not in use in Superset, but needed for unit tests sticky={sticky} /> diff --git a/plugins/plugin-chart-table/src/controlPanel.tsx b/plugins/plugin-chart-table/src/controlPanel.tsx index 9d7516c3b9..e0a90c93df 100644 --- a/plugins/plugin-chart-table/src/controlPanel.tsx +++ b/plugins/plugin-chart-table/src/controlPanel.tsx @@ -18,7 +18,7 @@ * under the License. */ import React from 'react'; -import { t } from '@superset-ui/translation'; +import { t, addLocaleData } from '@superset-ui/translation'; import { formatSelectOptions, D3_TIME_FORMAT_OPTIONS, @@ -31,8 +31,18 @@ import { } from '@superset-ui/chart-controls'; import { validateNonEmpty } from '@superset-ui/validator'; import { smartDateFormatter } from '@superset-ui/time-format'; +import i18n from './i18n'; -export const PAGE_SIZE_OPTIONS = formatSelectOptions([[0, t('All')], 10, 20, 50, 100, 200]); +addLocaleData(i18n); + +export const PAGE_SIZE_OPTIONS = formatSelectOptions([ + [0, t('page_size.all')], + 10, + 20, + 50, + 100, + 200, +]); export enum QueryMode { aggregate = 'aggregate', @@ -264,7 +274,7 @@ const config: ControlPanelConfig = { label: t('Emit Filter Events'), renderTrigger: true, default: false, - description: t('Whether to apply filter when items are clicked'), + description: t('Whether to apply filter to dashboards when table cells are clicked'), }, }, ], diff --git a/plugins/plugin-chart-table/src/i18n.ts b/plugins/plugin-chart-table/src/i18n.ts new file mode 100644 index 0000000000..0fca5aa33a --- /dev/null +++ b/plugins/plugin-chart-table/src/i18n.ts @@ -0,0 +1,41 @@ +import { Locale } from '@superset-ui/translation'; + +const en = { + 'Query Mode': [''], + Aggregate: [''], + 'Raw Records': [''], + 'Emit Filter Events': [''], + 'Show Cell Bars': [''], + 'page_size.show': ['Show'], + 'page_size.all': ['All'], + 'page_size.entries': ['entries'], + 'search.num_records': ['%s record', '%s records...'], +}; + +const translations: Partial> = { + en, + fr: { + 'Query Mode': [''], + Aggregate: [''], + 'Raw Records': [''], + 'Emit Filter Events': [''], + 'Show Cell Bars': [''], + 'page_size.show': ['Afficher'], + 'page_size.all': ['tous'], + 'page_size.entries': ['entrées'], + 'search.num_records': ['%s enregistrement', '%s enregistrements...'], + }, + zh: { + 'Query Mode': ['查询模式'], + Aggregate: ['分组聚合'], + 'Raw Records': ['原始数据'], + 'Emit Filter Events': ['关联看板过滤器'], + 'Show Cell Bars': ['为指标添加条状图背景'], + 'page_size.show': ['每页显示'], + 'page_size.all': ['全部'], + 'page_size.entries': ['条'], + 'search.num_records': ['%s条记录...'], + }, +}; + +export default translations; From 20cb5d42a7472de9f51cb55df5967f5f3629c38d Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Tue, 4 Aug 2020 14:45:21 -0700 Subject: [PATCH 2/5] Add shims for window.console --- packages/superset-ui-core/src/utils/logging.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/superset-ui-core/src/utils/logging.ts b/packages/superset-ui-core/src/utils/logging.ts index 95fcd20b8b..eafd07fe76 100644 --- a/packages/superset-ui-core/src/utils/logging.ts +++ b/packages/superset-ui-core/src/utils/logging.ts @@ -16,9 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -const logger = console; +const logger = window.console || { + debug() {}, + log() {}, + warn() {}, + error() {}, + trace() {}, +}; /** - * Superset frontend logger, currently just dump to console directly. + * Superset frontend logger, currently just an alias to console. */ export default logger; From 89bb55f6c86214a55bd3c57f7c05444cd5e6fca2 Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Tue, 4 Aug 2020 18:49:01 -0700 Subject: [PATCH 3/5] Add tests for logging --- .../superset-ui-core/src/utils/logging.ts | 1 + .../test/utils/logging.test.ts | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 packages/superset-ui-core/test/utils/logging.test.ts diff --git a/packages/superset-ui-core/src/utils/logging.ts b/packages/superset-ui-core/src/utils/logging.ts index eafd07fe76..c4f2a23345 100644 --- a/packages/superset-ui-core/src/utils/logging.ts +++ b/packages/superset-ui-core/src/utils/logging.ts @@ -19,6 +19,7 @@ const logger = window.console || { debug() {}, log() {}, + info() {}, warn() {}, error() {}, trace() {}, diff --git a/packages/superset-ui-core/test/utils/logging.test.ts b/packages/superset-ui-core/test/utils/logging.test.ts new file mode 100644 index 0000000000..3452c25138 --- /dev/null +++ b/packages/superset-ui-core/test/utils/logging.test.ts @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { logging } from '../../src'; + +describe('logging', () => { + it('should have logging methods', () => { + expect(() => { + logging.debug(); + logging.log(); + logging.info(); + }).not.toThrow(); + expect(() => { + logging.warn('warn'); + }).toThrow('warn'); + expect(() => { + logging.error('error'); + }).toThrow('error'); + expect(() => { + logging.trace(); + }).toThrow('Trace:'); + }); +}); From f8655b7160613857d5907a09062ab06a5b347c84 Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Wed, 5 Aug 2020 10:16:57 -0700 Subject: [PATCH 4/5] Fix a typo; more robust testing --- .../test/utils/logging.test.ts | 26 +++++++++++++++++-- .../superset-ui-translation/src/Translator.ts | 4 +-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/superset-ui-core/test/utils/logging.test.ts b/packages/superset-ui-core/test/utils/logging.test.ts index 3452c25138..174e19ad83 100644 --- a/packages/superset-ui-core/test/utils/logging.test.ts +++ b/packages/superset-ui-core/test/utils/logging.test.ts @@ -16,10 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { logging } from '../../src'; describe('logging', () => { - it('should have logging methods', () => { + beforeEach(() => { + jest.resetModules(); + }); + it('should pipe to `console` methods', () => { + const { logging } = require('../../src'); + expect(() => { logging.debug(); logging.log(); @@ -35,4 +39,22 @@ describe('logging', () => { logging.trace(); }).toThrow('Trace:'); }); + it('should use noop functions when console unavailable', () => { + const console = window.console; + Object.assign(window, { console: undefined }); + const logging = require('../../src').logging; + + afterAll(() => { + Object.assign(window, { console }); + }); + + expect(() => { + logging.debug(); + logging.log(); + logging.info(); + logging.warn('warn'); + logging.error('error'); + logging.trace(); + }).not.toThrow(); + }); }); diff --git a/packages/superset-ui-translation/src/Translator.ts b/packages/superset-ui-translation/src/Translator.ts index d73789b3bd..ad35b92e20 100644 --- a/packages/superset-ui-translation/src/Translator.ts +++ b/packages/superset-ui-translation/src/Translator.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import UntyppedJed from 'jed'; +import UntypedJed from 'jed'; import { logging } from '@superset-ui/core'; import { Jed, TranslatorConfig, Locale, Translations, LocaleData, LanguagePack } from './types'; @@ -41,7 +41,7 @@ export default class Translator { constructor(config: TranslatorConfig = {}) { const { languagePack = DEFAULT_LANGUAGE_PACK } = config; // eslint-disable-next-line @typescript-eslint/no-unsafe-call - this.i18n = new UntyppedJed(languagePack) as Jed; + this.i18n = new UntypedJed(languagePack) as Jed; this.locale = this.i18n.options.locale_data.superset[''].lang as Locale; } From 71d79099255b92021b0962d94bd3600dec3dfd5e Mon Sep 17 00:00:00 2001 From: Jesse Yang Date: Wed, 5 Aug 2020 10:57:18 -0700 Subject: [PATCH 5/5] Lint fix --- package.json | 4 +++- packages/superset-ui-core/test/utils/logging.test.ts | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7544f49bca..84360638b4 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,9 @@ "promise/param-names": "off", "jest/require-to-throw-message": "off", "jest/no-test-return-statement": "off", - "jest/no-expect-resolves": "off" + "jest/no-expect-resolves": "off", + "@typescript-eslint/no-require-imports": "off", + "global-require": "off" } }, { diff --git a/packages/superset-ui-core/test/utils/logging.test.ts b/packages/superset-ui-core/test/utils/logging.test.ts index 174e19ad83..2d4ff781f9 100644 --- a/packages/superset-ui-core/test/utils/logging.test.ts +++ b/packages/superset-ui-core/test/utils/logging.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */ /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -16,7 +17,6 @@ * specific language governing permissions and limitations * under the License. */ - describe('logging', () => { beforeEach(() => { jest.resetModules(); @@ -40,9 +40,9 @@ describe('logging', () => { }).toThrow('Trace:'); }); it('should use noop functions when console unavailable', () => { - const console = window.console; + const { console } = window; Object.assign(window, { console: undefined }); - const logging = require('../../src').logging; + const { logging } = require('../../src'); afterAll(() => { Object.assign(window, { console });