Bladeren bron

[frontend] add modern i18n support

Björn Alm 3 jaren geleden
bovenliggende
commit
9539e73255
25 gewijzigde bestanden met toevoegingen van 1893 en 51 verwijderingen
  1. 1 1
      desktop/core/src/desktop/js/apps/editor/components/result/reactExample/ReactExample.test.tsx
  2. 50 34
      desktop/core/src/desktop/js/apps/editor/components/result/reactExample/ReactExample.tsx
  3. 2 0
      desktop/core/src/desktop/js/jest/jest.init.js
  4. 4 4
      desktop/core/src/desktop/js/reactComponents/ReactExampleGlobal/ReactExampleGlobal.test.tsx
  5. 9 10
      desktop/core/src/desktop/js/reactComponents/ReactExampleGlobal/ReactExampleGlobal.tsx
  6. 1 0
      desktop/core/src/desktop/js/types/types.ts
  7. 17 0
      desktop/core/src/desktop/js/utils/__mocks__/i18nReact.ts
  8. 76 0
      desktop/core/src/desktop/js/utils/i18nReact.test.ts
  9. 66 0
      desktop/core/src/desktop/js/utils/i18nReact.ts
  10. 0 0
      desktop/core/src/desktop/static/desktop/css/hue.css
  11. 1 0
      desktop/core/src/desktop/static/desktop/less/root-wrapped-antd.less
  12. 121 0
      desktop/core/src/desktop/static/desktop/locales/de/translation.json
  13. 558 0
      desktop/core/src/desktop/static/desktop/locales/en/translation.json
  14. 121 0
      desktop/core/src/desktop/static/desktop/locales/es/translation.json
  15. 121 0
      desktop/core/src/desktop/static/desktop/locales/fr/translation.json
  16. 121 0
      desktop/core/src/desktop/static/desktop/locales/ja/translation.json
  17. 121 0
      desktop/core/src/desktop/static/desktop/locales/ko/translation.json
  18. 32 0
      desktop/core/src/desktop/static/desktop/locales/pt-BR/translation.json
  19. 32 0
      desktop/core/src/desktop/static/desktop/locales/pt/translation.json
  20. 121 0
      desktop/core/src/desktop/static/desktop/locales/zh-CN/translation.json
  21. 121 0
      desktop/core/src/desktop/static/desktop/locales/zh/translation.json
  22. 2 0
      desktop/core/src/desktop/templates/global_js_constants.mako
  23. 1 2
      desktop/libs/notebook/src/notebook/templates/editor2.mako
  24. 191 0
      package-lock.json
  25. 3 0
      package.json

+ 1 - 1
desktop/core/src/desktop/js/apps/editor/components/result/reactExample/ReactExample.test.tsx

@@ -1,7 +1,7 @@
+import React from 'react';
 import { render, screen } from '@testing-library/react';
 import '@testing-library/jest-dom';
 
-import React from 'react';
 import ReactExample from './ReactExample';
 
 describe('ReactExample', () => {

+ 50 - 34
desktop/core/src/desktop/js/apps/editor/components/result/reactExample/ReactExample.tsx

@@ -1,7 +1,10 @@
 'use strict';
 
 import React, { FunctionComponent, useState } from 'react';
-import { Button, Modal } from 'antd';
+import { Button, Modal, Skeleton } from 'antd';
+
+// Provides a i18n translation hook
+import { i18nReact } from '../../../../../utils/i18nReact';
 
 // tsx-files don't have the same baseUrl as other js files so
 // we are using a relative path when importing
@@ -15,7 +18,7 @@ import SqlExecutable from '../../../execution/sqlExecutable';
 import './ReactExample.scss';
 
 export interface ReactExampleProps {
-  title: string;
+  title?: string;
   // This example component recieves the "activeExecutable" used in the result page but
   // the props in general can of course be of any type
   activeExecutable?: SqlExecutable;
@@ -27,18 +30,27 @@ interface EditorCursor {
   position: Ace.Position;
 }
 
-const defaultProps = { title: 'Default result title' };
-
 // Using the FunctionComponent generic is optional. Alternatively you can explicitly
 // define the children prop like in the ReactExampleGlobal component.
-const ReactExample: FunctionComponent<ReactExampleProps> = ({ title, activeExecutable }) => {
+const ReactExample: FunctionComponent<ReactExampleProps> = ({ activeExecutable, ...i18n }) => {
+  // We use the translation hook WITHOUT the built in suspence, meaning that our
+  // component should handle the rendering of the loading state. This gives us the
+  // possibilty to render a loader or placeholder while waiting.
+  const { t, ready: i18nReady } = i18nReact.useTranslation(undefined, { useSuspense: false });
+
+  // This is one way we can do i18n on default values for strings passed as props.
+  // Use the prop value if present, if not we use a default value/key passed through i18n
+  const { title = t('Schedule') } = i18n;
+
   // Example of having the react component rerender based on changes from useHuePubSub.
   // Use with caution and preferrably only at the top level component in your component tree.
   const editorCursor = useHuePubSub<EditorCursor>({ topic: CURSOR_POSITION_CHANGED_EVENT });
 
   const id = activeExecutable?.id;
   const position =
-    editorCursor?.position !== undefined ? JSON.stringify(editorCursor.position) : 'not available';
+    editorCursor?.position !== undefined
+      ? JSON.stringify(editorCursor.position)
+      : t('Not available');
 
   const [isModalOpen, setIsModalOpen] = useState(false);
 
@@ -54,41 +66,45 @@ const ReactExample: FunctionComponent<ReactExampleProps> = ({ title, activeExecu
     setIsModalOpen(false);
   };
 
-  return (
+  return !i18nReady ? (
+    <div className="antd">
+      <Skeleton />
+    </div>
+  ) : (
     // The 'antd' class is added to the root element since we want it to apply the correct
     // "global" styling to its antd sub components, e.g. the antd Button.
     // Also make sure that the component specific Antd style is imported in the file
     // 'root-wrapped-antd.less'.
-    <div className="react-example antd">
-      <h1 className="react-example__title">{title}</h1>
-      <Button type="primary" onClick={showModal}>
-        Open Modal
-      </Button>
-
-      <p className="react-example__description">
-        I'm an Editor specific react component containing subcomponents. The dynamic id that I'm
-        getting from a Knockout observable is {id}.
-      </p>
-      <p className="react-example__description">
-        {`I'm also geting a cursor position from hue huePubSub using the hook useHuePubSub which is
+    <React.StrictMode>
+      <div className="react-example antd">
+        <h1 className="react-example__title">{title}</h1>
+        <Button type="primary" onClick={showModal}>
+          {t('Open')}
+        </Button>
+
+        <p className="react-example__description">
+          I'm an Editor specific react component containing subcomponents. The dynamic id that I'm
+          getting from a Knockout observable is {id}.
+        </p>
+        <p className="react-example__description">
+          {`I'm also geting a cursor position from hue huePubSub using the hook useHuePubSub which is
         updated on each 'editor.cursor.position.changed'. Cursor position is
         ${position}`}
-      </p>
-      <ReactExampleGlobal className="react-example__react-example-global-component">
-        I'm a button from the application global component set
-      </ReactExampleGlobal>
-      <Button type="primary" onClick={() => console.info('clicked')}>
-        I'm an Antd button
-      </Button>
-      <Modal title="Basic Modal" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
-        <p>Some contents...</p>
-        <p>Some contents...</p>
-        <p>Some contents...</p>
-      </Modal>
-    </div>
+        </p>
+        <ReactExampleGlobal className="react-example__react-example-global-component">
+          I'm a button from the application global component set
+        </ReactExampleGlobal>
+        <Button type="primary" onClick={() => console.info('clicked')}>
+          I'm an Antd button
+        </Button>
+
+        {/* The title and content of the Modal is internationlized using the t-function */}
+        <Modal title={t('Modify')} open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
+          <p>{t('Settings')}.</p>
+        </Modal>
+      </div>
+    </React.StrictMode>
   );
 };
 
-ReactExample.defaultProps = defaultProps;
-
 export default ReactExample;

+ 2 - 0
desktop/core/src/desktop/js/jest/jest.init.js

@@ -120,3 +120,5 @@ axios.interceptors.request.use(config => {
 process.on('unhandledRejection', err => {
   fail(err);
 });
+
+jest.mock('../utils/i18nReact');

+ 4 - 4
desktop/core/src/desktop/js/reactComponents/ReactExampleGlobal/ReactExampleGlobal.test.tsx

@@ -1,8 +1,8 @@
+import React from 'react';
 import { render, screen } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import '@testing-library/jest-dom';
 
-import React from 'react';
 import ReactExampleGlobal from './ReactExampleGlobal';
 
 describe('ReactExampleGlobal', () => {
@@ -15,7 +15,7 @@ describe('ReactExampleGlobal', () => {
     // It is recommended to call userEvent.setup per test
     const user = userEvent.setup();
     render(<ReactExampleGlobal />);
-    const btn = screen.getByRole('button', { name: 'ReactExampleGlobal - Like me' });
+    const btn = screen.getByRole('button', { name: 'ReactExampleGlobal - Yes' });
     expect(btn).not.toBeDisabled();
 
     await user.click(btn);
@@ -26,7 +26,7 @@ describe('ReactExampleGlobal', () => {
     const user = userEvent.setup();
     const clickCallback = jest.fn();
     render(<ReactExampleGlobal onClick={clickCallback} />);
-    const btn = screen.getByRole('button', { name: 'ReactExampleGlobal - Like me' });
+    const btn = screen.getByRole('button', { name: 'ReactExampleGlobal - Yes' });
     await user.click(btn);
     expect(clickCallback).toHaveBeenCalled();
   });
@@ -34,7 +34,7 @@ describe('ReactExampleGlobal', () => {
   test('prints to console.info on click', async () => {
     const user = userEvent.setup();
     render(<ReactExampleGlobal version="1" myObj={{ id: 'a' }} />);
-    const btn = screen.getByRole('button', { name: 'ReactExampleGlobal - Like me' });
+    const btn = screen.getByRole('button', { name: 'ReactExampleGlobal - Yes' });
     await user.click(btn);
     expect(consoleSpy).toHaveBeenCalledWith('ReactExampleGlobal clicked  1 a');
   });

+ 9 - 10
desktop/core/src/desktop/js/reactComponents/ReactExampleGlobal/ReactExampleGlobal.tsx

@@ -2,6 +2,8 @@
 
 import React, { useState } from 'react';
 
+import { i18nReact } from '../../utils/i18nReact';
+
 import './ReactExampleGlobal.scss';
 
 export interface ReactExampleGlobalProps {
@@ -12,20 +14,19 @@ export interface ReactExampleGlobalProps {
   children?: React.ReactNode | React.ReactNode[];
 }
 
-const defaultProps = {
-  // eslint-disable-next-line @typescript-eslint/no-empty-function
-  onClick: () => {},
-  version: 'xxx'
-};
-
 const ReactExampleGlobal = ({
   onClick,
   children,
-  version,
+  version = '1',
   myObj
 }: ReactExampleGlobalProps): JSX.Element => {
   const [isClicked, setIsClicked] = useState(false);
 
+  // We use the translation hook with the built in suspence,
+  // meaning that this component won't render until the language file
+  // have been loaded.
+  const { t } = i18nReact.useTranslation();
+
   return (
     <button
       className="react-example-global"
@@ -36,11 +37,9 @@ const ReactExampleGlobal = ({
         console.info(`ReactExampleGlobal clicked  ${version} ${myObj?.id}`);
       }}
     >
-      ReactExampleGlobal - {children ?? 'Like me'}
+      ReactExampleGlobal - {children ?? t('Yes')}
     </button>
   );
 };
 
-ReactExampleGlobal.defaultProps = defaultProps;
-
 export default ReactExampleGlobal;

+ 1 - 0
desktop/core/src/desktop/js/types/types.ts

@@ -55,6 +55,7 @@ export interface hueWindow {
   HAS_CONNECTORS?: boolean;
   HAS_SQL_ANALYZER?: boolean;
   HUE_BASE_URL?: string;
+  HUE_LANG?: string;
   HUE_VERSION?: string;
   LOGGED_USERNAME?: string;
   SHOW_ADD_MORE_EDITORS?: boolean;

+ 17 - 0
desktop/core/src/desktop/js/utils/__mocks__/i18nReact.ts

@@ -0,0 +1,17 @@
+import { UseTranslationOptions } from 'react-i18next';
+interface useTranslationReturn {
+  t(str: string): string;
+  ready: boolean;
+}
+interface i18nReactMock {
+  useTranslation(string, options?: UseTranslationOptions): useTranslationReturn;
+}
+
+export const i18nReact: i18nReactMock = {
+  useTranslation: () => {
+    return {
+      t: str => str,
+      ready: true
+    };
+  }
+};

+ 76 - 0
desktop/core/src/desktop/js/utils/i18nReact.test.ts

@@ -0,0 +1,76 @@
+import { renderHook } from '@testing-library/react';
+
+import i18next from 'i18next';
+import HttpApi from 'i18next-http-backend';
+import { initReactI18next } from 'react-i18next';
+
+// Since the our i18nReact is globally mocked in jest.init.js
+// we need to use requireActual when testing the actual i18nReact module
+const { i18nReact } = jest.requireActual('./i18nReact');
+
+describe('i18nReact', () => {
+  const originalHueLang = global.HUE_LANG;
+  jest.mock('i18next');
+
+  // Setup mocks used to initialize i18next
+  const initMock = jest.fn();
+  const useInitReactI18nextMock = jest.fn().mockReturnValue({ init: initMock });
+  const useHttpApiMock = jest
+    .spyOn(i18next, 'use')
+    .mockReturnValue({ ...i18next, use: useInitReactI18nextMock });
+
+  const renderUseTranslationHook = () => {
+    // Since we mock the i18next init calls we have to pass an instance to
+    // i18nReact.useTranslation here in order to supress a console warning.
+    return renderHook(() => i18nReact.useTranslation('translations', { i18n: i18next }));
+  };
+
+  afterEach(() => {
+    global.HUE_LANG = originalHueLang;
+    initMock.mockClear();
+  });
+
+  it('initializes i18next with HttpApi and initReactI18next once the useTranslation hook is used', () => {
+    expect(useHttpApiMock).not.toHaveBeenCalled();
+    expect(useInitReactI18nextMock).not.toHaveBeenCalled();
+
+    renderUseTranslationHook();
+    expect(useHttpApiMock).toHaveBeenCalledWith(HttpApi);
+    expect(useInitReactI18nextMock).toHaveBeenCalledWith(initReactI18next);
+  });
+
+  it('initializes i18next with language defined in global var HUE_LANG', () => {
+    global.HUE_LANG = 'en-us';
+    renderUseTranslationHook();
+    expect(initMock).toHaveBeenCalledWith(expect.objectContaining({ lng: global.HUE_LANG }));
+  });
+
+  it('initializes i18next with fallback language "en"', () => {
+    global.HUE_LANG = undefined;
+    renderUseTranslationHook();
+    expect(initMock).toHaveBeenCalledWith(expect.objectContaining({ fallbackLng: 'en' }));
+  });
+
+  it('initializes i18next with load strategy "languageOnly" if full locale is NOT supported', () => {
+    global.HUE_LANG = 'en-us';
+    const testConfig = { ...i18nReact.getI18nConfig(), supportedLngs: ['en'] };
+    const getI18nConfigMock = jest.spyOn(i18nReact, 'getI18nConfig').mockReturnValue(testConfig);
+
+    renderUseTranslationHook();
+    expect(initMock).toHaveBeenCalledWith(expect.objectContaining({ lng: 'en-us' }));
+    expect(initMock).toHaveBeenCalledWith(expect.objectContaining({ load: 'languageOnly' }));
+
+    getI18nConfigMock.mockRestore();
+  });
+
+  it('initializes i18next with load strategy "currentOnly" if full locale is supported', () => {
+    global.HUE_LANG = 'pt-BR';
+    const testConfig = { ...i18nReact.getI18nConfig(), supportedLngs: ['pt-BR'] };
+    const getI18nConfigMock = jest.spyOn(i18nReact, 'getI18nConfig').mockReturnValue(testConfig);
+
+    renderUseTranslationHook();
+    expect(initMock).toHaveBeenCalledWith(expect.objectContaining({ lng: 'pt-BR' }));
+    expect(initMock).toHaveBeenCalledWith(expect.objectContaining({ load: 'currentOnly' }));
+    getI18nConfigMock.mockRestore();
+  });
+});

+ 66 - 0
desktop/core/src/desktop/js/utils/i18nReact.ts

@@ -0,0 +1,66 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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 i18next, { InitOptions } from 'i18next';
+import HttpApi from 'i18next-http-backend';
+import {
+  initReactI18next,
+  useTranslation,
+  UseTranslationOptions,
+  UseTranslationResponse
+} from 'react-i18next';
+import { hueWindow } from 'types/types';
+
+// eslint-disable-next-line @typescript-eslint/ban-types
+let i18nextLoad: Promise<Function>;
+
+const determineLoadStrategy = (supportedLanguages: Array<string>, currentLang: string) => {
+  const supportedFullLocales = supportedLanguages
+    .filter(lang => lang.includes('-'))
+    .map(lang => lang.toLowerCase());
+  return supportedFullLocales.includes(currentLang?.toLowerCase()) ? 'currentOnly' : 'languageOnly';
+};
+
+export const i18nReact = {
+  getI18nConfig: (): InitOptions => {
+    const supportedLngs = ['de', 'en', 'es', 'fr', 'ja', 'ko', 'pt', 'pt-BR', 'zh', 'zh-CN'];
+    const currentLang = (window as hueWindow).HUE_LANG ?? '';
+    return {
+      backend: { loadPath: '/static/desktop/locales/{{lng}}/{{ns}}.json' },
+      lng: currentLang,
+      fallbackLng: 'en',
+      supportedLngs: supportedLngs,
+      nonExplicitSupportedLngs: true,
+      load: determineLoadStrategy(supportedLngs, currentLang),
+      debug: false,
+      interpolation: {
+        escapeValue: false // React strings are safe from XSS
+      }
+    };
+  },
+
+  useTranslation: (
+    nameSpace?: string | undefined,
+    options?: UseTranslationOptions
+  ): UseTranslationResponse<string, undefined> => {
+    const config = i18nReact.getI18nConfig();
+    // We need to call initialize on i18next once before we can use useTranslation hook
+    if (!i18nextLoad) {
+      i18nextLoad = i18next.use(HttpApi).use(initReactI18next).init(config);
+    }
+    return useTranslation(nameSpace, options || {});
+  }
+};

File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue.css


+ 1 - 0
desktop/core/src/desktop/static/desktop/less/root-wrapped-antd.less

@@ -18,6 +18,7 @@
   // Import the styles for the actual components used
   @import 'node_modules/antd/es/button/style/index.less';
   @import 'node_modules/antd/es/modal/style/index.less';
+  @import 'node_modules/antd/es/skeleton/style/index.less';
   /* stylelint-enable no-invalid-position-at-import-rule */
 
   // Required global overrides:

+ 121 - 0
desktop/core/src/desktop/static/desktop/locales/de/translation.json

@@ -0,0 +1,121 @@
+{
+  "Add": "Hinzufügen",
+  "aggregate": "Aggregat",
+  "Aggregate": "Sammlung",
+  "alias": "Alias",
+  "All": "Alle",
+  "and": "und",
+  "Apps": "Apps",
+  "Assist": "Unterstützung",
+  "Assistant": "Assistent",
+  "Back": "Zurück",
+  "Browsers": "Browser",
+  "Bundle": "Bundle",
+  "Cancel": "Abbrechen",
+  "Change": "Ändern",
+  "Clipboard": "Zwischenablage",
+  "Close": "Schließen",
+  "Clusters": "Cluster",
+  "Columns": "Spalten",
+  "condition": "Bedingung",
+  "Create": "Erstellen",
+  "Created": "Erstellt",
+  "CSV": "CSV",
+  "cte": "CTE",
+  "CTEs": "CTEs",
+  "Dashboard": "Dashboard",
+  "database": "Datenbank",
+  "Databases": "Datenbanken",
+  "Date": "Datum",
+  "default": "Standard:",
+  "Delete": "Löschen",
+  "Description": "Beschreibung",
+  "Details": "Details",
+  "Directory": "Verzeichnis",
+  "Documentation": "Dokumentation",
+  "Documents": "Dokumente",
+  "Editor": "Editor",
+  "Excel": "Excel",
+  "Export": "Exportieren",
+  "Failed": "Fehlgeschlagen",
+  "Field": "Feld",
+  "File": "Datei",
+  "Files": "Dateien",
+  "filter": "Filter",
+  "Filter": "Filter",
+  "Format": "Format",
+  "Functions": "Funktionen",
+  "Group": "Gruppe",
+  "HBase": "HBase",
+  "Help": "Hilfe",
+  "Hive": "Hive",
+  "Identifiers": "Bezeichner",
+  "Impala": "Impala",
+  "Import": "Importieren",
+  "Indexes": "Indizes",
+  "Insert": "Einfügen einer",
+  "Jobs": "Jobs",
+  "join": "Zusammenführung",
+  "Key": "Schlüssel",
+  "keyword": "Schlüsselwort",
+  "Keywords": "Schlüsselwörter",
+  "Metrics": "Kennzahlen",
+  "Modify": "Ändern",
+  "More": "Mehr",
+  "Name": "Name",
+  "Next": "Weiter",
+  "No": "Nein",
+  "Notebook": "Notizbuch",
+  "of": "von",
+  "Open": "Öffnen",
+  "Options": "Optionen",
+  "Order": "Reihenfolge",
+  "other": "andere",
+  "Output": "Ausgabe",
+  "Owner": "Eigentümer",
+  "Partitions": "Partitionen",
+  "Password": "Kennwort",
+  "Permissions": "Berechtigungen",
+  "Popular": "Häufig verwendet",
+  "Popularity": "Beliebtheit",
+  "Project": "Projekt",
+  "Queries": "Abfragen",
+  "Read": "Lesen",
+  "Refresh": "Aktualisieren",
+  "Remove": "Entfernen",
+  "Report": "Bericht",
+  "Running": "Aktiv",
+  "Sample": "Beispiel",
+  "sample": "Beispiel",
+  "Samples": "Beispiele",
+  "Schedule": "Planung",
+  "Search": "Suche",
+  "Settings": "Einstellungen",
+  "Share": "Freigeben",
+  "Sharing": "Wird freigegeben",
+  "Size": "Größe",
+  "Sources": "Quellen",
+  "SQL": "SQL",
+  "Start": "Start",
+  "Statement": "Anweisung",
+  "Stats": "Statistiken",
+  "Stopped": "Angehalten",
+  "table": "Tabelle",
+  "Table": "Tabelle",
+  "Tables": "Tabellen",
+  "Terms": "Bedingungen",
+  "Type": "Typ",
+  "UDFs": "UDFs",
+  "Undo": "Rückgängig",
+  "Username": "Benutzername",
+  "Value": "Wert",
+  "Values": "Werte",
+  "variable": "Variable",
+  "Variables": "Variablen",
+  "View": "Ansicht",
+  "view": "Ansicht",
+  "Views": "Ansichten",
+  "virtual": "virtuell",
+  "Workflow": "Workflow",
+  "Yes": "Ja"
+}

+ 558 - 0
desktop/core/src/desktop/static/desktop/locales/en/translation.json

@@ -0,0 +1,558 @@
+{
+  "(Optional)": "(Optional)",
+  "1 hour": "1 hour",
+  "1 week": "1 week",
+  "8 hours": "8 hours",
+  "Active cluster": "Active cluster",
+  "Active compute": "Active compute",
+  "Active database": "Active database",
+  "Active namespace": "Active namespace",
+  "Add": "Add",
+  "Add a description...": "Add a description...",
+  "Add filter": "Add filter",
+  "Add one ?": "Add one ?",
+  "Add privilege": "Add privilege",
+  "Add properties...": "Add properties...",
+  "Add table": "Add table",
+  "Add tags...": "Add tags...",
+  "Admin": "Admin",
+  "Administer Server": "Administer Server",
+  "Administer Users": "Administer Users",
+  "Administration": "Administration",
+  "aggregate": "aggregate",
+  "Aggregate": "Aggregate",
+  "alias": "alias",
+  "All": "All",
+  "An error occurred refreshing the table stats. Please try again.": "An error occurred refreshing the table stats. Please try again.",
+  "Analysis was not possible for the executed query.": "Analysis was not possible for the executed query.",
+  "Analytic": "Analytic",
+  "and": "and",
+  "Any logged in user with the link can": "Any logged in user with the link can",
+  "Apps": "Apps",
+  "Are you sure you want to clear the task history?": "Are you sure you want to clear the task history?",
+  "Assist": "Assist",
+  "Assistant": "Assistant",
+  "at cursor": "at cursor",
+  "Australia": "Australia",
+  "Back": "Back",
+  "Bar Chart": "Bar Chart",
+  "Bars": "Bars",
+  "Brazil": "Brazil",
+  "Browse column privileges": "Browse column privileges",
+  "Browse db privileges": "Browse db privileges",
+  "Browse DB privileges": "Browse DB privileges",
+  "Browse table privileges": "Browse table privileges",
+  "Browsers": "Browsers",
+  "Bundle": "Bundle",
+  "Canada": "Canada",
+  "Cancel": "Cancel",
+  "Cancel Download": "Cancel Download",
+  "Cancel upload": "Cancel upload",
+  "Category": "Category",
+  "Change": "Change",
+  "Channel": "Channel",
+  "Chart": "Chart",
+  "Check compatibility": "Check compatibility",
+  "China": "China",
+  "Choose a column to pivot...": "Choose a column to pivot...",
+  "Choose a column...": "Choose a column...",
+  "Choose a scope...": "Choose a scope...",
+  "Choose a type...": "Choose a type...",
+  "Choose...": "Choose...",
+  "Clear": "Clear",
+  "Clear cache": "Clear cache",
+  "Clear the current editor": "Clear the current editor",
+  "Clear the query history": "Clear the query history",
+  "Click for more details": "Click for more details",
+  "Clipboard": "Clipboard",
+  "Close": "Close",
+  "Close session": "Close session",
+  "Cluster": "Cluster",
+  "Clusters": "Clusters",
+  "CodeGen": "CodeGen",
+  "Collapse": "Collapse",
+  "Collection": "Collection",
+  "Column": "Column",
+  "column name...": "column name...",
+  "Columns": "Columns",
+  "columns": "columns",
+  "Compilation": "Compilation",
+  "Complex Type": "Complex Type",
+  "Compute": "Compute",
+  "condition": "condition",
+  "Conditional": "Conditional",
+  "Configuration files located in": "Configuration files located in",
+  "Confirm History Clearing": "Confirm History Clearing",
+  "Confirm the deletion?": "Confirm the deletion?",
+  "Connect": "Connect",
+  "Connect to the data source": "Connect to the data source",
+  "Connectors": "Connectors",
+  "Copy the displayed results to your clipboard": "Copy the displayed results to your clipboard",
+  "Could not find": "Could not find",
+  "Could not find details for the function": "Could not find details for the function",
+  "CPU": "CPU",
+  "Create": "Create",
+  "Create database": "Create database",
+  "Create Directory": "Create Directory",
+  "Create folder": "Create folder",
+  "Create index": "Create index",
+  "Create table": "Create table",
+  "Created": "Created",
+  "Created: ": "Created: ",
+  "Creating session": "Creating session",
+  "CSV": "CSV",
+  "cte": "cte",
+  "CTEs": "CTEs",
+  "Dashboard": "Dashboard",
+  "Data Masking": "Data Masking",
+  "Data Science": "Data Science",
+  "Data Warehouse": "Data Warehouse",
+  "Database": "Database",
+  "database": "database",
+  "Databases": "Databases",
+  "Date": "Date",
+  "days ago": "days ago",
+  "Deactivate link": "Deactivate link",
+  "Deactivate the link sharing": "Deactivate the link sharing",
+  "default": "default",
+  "default section": "default section",
+  "Default": "Default",
+  "Delete": "Delete",
+  "Delete document": "Delete document",
+  "Delete this privilege": "Delete this privilege",
+  "Deleting...": "Deleting...",
+  "Description": "Description",
+  "Details": "Details",
+  "Did you mean": "Did you mean",
+  "dir": "dir",
+  "Directory": "Directory",
+  "Directory name": "Directory name",
+  "DistCp Job": "DistCp Job",
+  "distinct": "distinct",
+  "Do you really want to delete the following document(s)?": "Do you really want to delete the following document(s)?",
+  "Document type": "Document type",
+  "Documentation": "Documentation",
+  "Documents": "Documents",
+  "Done.": "Done.",
+  "Done. 0 results.": "Done. 0 results.",
+  "Drop a SQL file here": "Drop a SQL file here",
+  "Drop iPython/Zeppelin notebooks here": "Drop iPython/Zeppelin notebooks here",
+  "Edit": "Edit",
+  "Edit list...": "Edit list...",
+  "Edit Profile": "Edit Profile",
+  "Edit tags": "Edit tags",
+  "Edit this privilege": "Edit this privilege",
+  "Editor": "Editor",
+  "Empty configuration section": "Empty configuration section",
+  "Empty directory": "Empty directory",
+  "Empty file...": "Empty file...",
+  "Empty result.": "Empty result.",
+  "Error loading columns.": "Error loading columns.",
+  "Error loading contents.": "Error loading contents.",
+  "Error loading databases.": "Error loading databases.",
+  "Error loading entries": "Error loading entries",
+  "Error loading fields.": "Error loading fields.",
+  "Error loading index details.": "Error loading index details.",
+  "Error loading namespaces.": "Error loading namespaces.",
+  "Error loading samples": "Error loading samples",
+  "Error loading table details.": "Error loading table details.",
+  "Error loading tables.": "Error loading tables.",
+  "Error loading the queries.": "Error loading the queries.",
+  "Error while copying results.": "Error while copying results.",
+  "Europe": "Europe",
+  "Example: SELECT * FROM tablename, or press CTRL + space": "Example: SELECT * FROM tablename, or press CTRL + space",
+  "Excel": "Excel",
+  "Execute": "Execute",
+  "Execute a query to get query execution analysis.": "Execute a query to get query execution analysis.",
+  "Executing...": "Executing...",
+  "Execution": "Execution",
+  "Expand": "Expand",
+  "Expand to all columns": "Expand to all columns",
+  "Expand to selected columns": "Expand to selected columns",
+  "Expected end of statement": "Expected end of statement",
+  "Expired": "Expired",
+  "Explain": "Explain",
+  "Explain the current SQL query": "Explain the current SQL query",
+  "Export": "Export",
+  "Export the result into a file, an index, a new table...": "Export the result into a file, an index, a new table...",
+  "Failed": "Failed",
+  "Field": "Field",
+  "Fields": "Fields",
+  "File": "File",
+  "File Browser": "File Browser",
+  "Files": "Files",
+  "filter": "filter",
+  "Filter": "Filter",
+  "Filter columns...": "Filter columns...",
+  "Filter databases...": "Filter databases...",
+  "Filter sources...": "Filter sources...",
+  "Filter...": "Filter...",
+  "Fix": "Fix",
+  "Fix me": "Fix me",
+  "Folder name": "Folder name",
+  "Foreign key": "Foreign key",
+  "Foreign Key": "Foreign Key",
+  "Foreign keys": "Foreign keys",
+  "Format": "Format",
+  "Format the current SQL query": "Format the current SQL query",
+  "France": "France",
+  "Functions": "Functions",
+  "Germany": "Germany",
+  "Get hints on how to port SQL from other databases": "Get hints on how to port SQL from other databases",
+  "Get link": "Get link",
+  "Go Home": "Go Home",
+  "Go to column:": "Go to column:",
+  "Gradient Map": "Gradient Map",
+  "Grid": "Grid",
+  "Group": "Group",
+  "group": "group",
+  "group by": "group by",
+  "HBase": "HBase",
+  "Heatmap": "Heatmap",
+  "Help": "Help",
+  "Hide advanced": "Hide advanced",
+  "Hide Details": "Hide Details",
+  "Hive": "Hive",
+  "Hive Query": "Hive Query",
+  "Iceberg table": "Iceberg table",
+  "Identifiers": "Identifiers",
+  "Ignore this type of error": "Ignore this type of error",
+  "Impala": "Impala",
+  "Impala Query": "Impala Query",
+  "Import": "Import",
+  "import": "import",
+  "Import complete!": "Import complete!",
+  "Import failed!": "Import failed!",
+  "Import Hue Documents": "Import Hue Documents",
+  "Import Job": "Import Job",
+  "Import Query History": "Import Query History",
+  "Imported: ": "Imported: ",
+  "Importing...": "Importing...",
+  "Improve Analysis": "Improve Analysis",
+  "Indexes": "Indexes",
+  "Insert": "Insert",
+  "Insert ": "Insert ",
+  "Insert at cursor": "Insert at cursor",
+  "Insert in the editor": "Insert in the editor",
+  "Insert value here": "Insert value here",
+  "Installed Applications": "Installed Applications",
+  "Instances": "Instances",
+  "intensity": "intensity",
+  "Invalidate all metadata and rebuild index.": "Invalidate all metadata and rebuild index.",
+  "IO": "IO",
+  "It looks like you are offline or an unknown error happened. Please refresh the page.": "It looks like you are offline or an unknown error happened. Please refresh the page.",
+  "Italy": "Italy",
+  "Japan": "Japan",
+  "Java Job": "Java Job",
+  "Job browser": "Job browser",
+  "Job Design": "Job Design",
+  "Jobs": "Jobs",
+  "Jobs preview": "Jobs preview",
+  "join": "join",
+  "Key": "Key",
+  "keyword": "keyword",
+  "Keywords": "Keywords",
+  "label": "label",
+  "Language Reference": "Language Reference",
+  "Last Modified": "Last Modified",
+  "latitude": "latitude",
+  "legend": "legend",
+  "limit": "limit",
+  "Limit": "Limit",
+  "Limit the number of results to...": "Limit the number of results to...",
+  "Lines": "Lines",
+  "Link": "Link",
+  "Load recent queries in order to improve recommendations": "Load recent queries in order to improve recommendations",
+  "Loading": "Loading",
+  "Loading metrics...": "Loading metrics...",
+  "Loading...": "Loading...",
+  "Lock this row": "Lock this row",
+  "Log Out": "Log Out",
+  "longitude": "longitude",
+  "Manage Users": "Manage Users",
+  "Manual refresh": "Manual refresh",
+  "MapReduce Job": "MapReduce Job",
+  "Marker Map": "Marker Map",
+  "Markers": "Markers",
+  "Mathematical": "Mathematical",
+  "max": "max",
+  "Memory": "Memory",
+  "Message": "Message",
+  "Metrics": "Metrics",
+  "min": "min",
+  "Misc": "Misc",
+  "Missing label configuration.": "Missing label configuration.",
+  "Missing latitude configuration.": "Missing latitude configuration.",
+  "Missing legend configuration.": "Missing legend configuration.",
+  "Missing longitude configuration.": "Missing longitude configuration.",
+  "Missing region configuration.": "Missing region configuration.",
+  "Missing value configuration.": "Missing value configuration.",
+  "Missing x axis configuration.": "Missing x axis configuration.",
+  "Missing y axis configuration.": "Missing y axis configuration.",
+  "Modified": "Modified",
+  "Modify": "Modify",
+  "More": "More",
+  "My Profile": "My Profile",
+  "Name": "Name",
+  "Namespace": "Namespace",
+  "Namespaces": "Namespaces",
+  "New document": "New document",
+  "New folder": "New folder",
+  "Next": "Next",
+  "No": "No",
+  "No clusters available": "No clusters available",
+  "No clusters available.": "No clusters available.",
+  "No clusters found": "No clusters found",
+  "No columns found": "No columns found",
+  "No computes found": "No computes found",
+  "No connectors": "No connectors",
+  "No data available": "No data available",
+  "No Data Available.": "No Data Available.",
+  "No databases found": "No databases found",
+  "No databases found.": "No databases found.",
+  "No description available.": "No description available.",
+  "No documents found": "No documents found",
+  "No entries found": "No entries found",
+  "No help available.": "No help available.",
+  "No indexes selected.": "No indexes selected.",
+  "No logs available at this moment.": "No logs available at this moment.",
+  "No match found": "No match found",
+  "No matching records": "No matching records",
+  "No namespaces found": "No namespaces found",
+  "No namespaces found.": "No namespaces found.",
+  "No optimizations identified.": "No optimizations identified.",
+  "No privileges found for the selected object.": "No privileges found for the selected object.",
+  "No queries found for": "No queries found for",
+  "No related computes": "No related computes",
+  "No results found": "No results found",
+  "No results found.": "No results found.",
+  "No sorting": "No sorting",
+  "No tables available.": "No tables available.",
+  "No tables found": "No tables found",
+  "No tables identified.": "No tables identified.",
+  "No task history.": "Not task history.",
+  "Not available": "Not available",
+  "Notebook": "Notebook",
+  "of": "of",
+  "Oozie Bundle": "Oozie Bundle",
+  "Oozie Schedule": "Oozie Schedule",
+  "Oozie Workflow": "Oozie Workflow",
+  "Open": "Open",
+  "Open cluster": "Open cluster",
+  "Open document": "Open document",
+  "Open folder": "Open folder",
+  "Open in Browser": "Open in Browser",
+  "Open in Dashboard": "Open in Dashboard",
+  "Open in Dashboard...": "Open in Dashboard...",
+  "Open in Editor": "Open in Editor",
+  "Open in File Browser...": "Open in File Browser...",
+  "Open in HBase": "Open in HBase",
+  "Open in Importer": "Open in Importer",
+  "Open in Table Browser...": "Open in Table Browser...",
+  "Options": "Options",
+  "Order": "Order",
+  "order by": "order by",
+  "other": "other",
+  "Output": "Output",
+  "Overview": "Overview",
+  "Owner": "Owner",
+  "Partition key": "Partition key",
+  "Partition Key": "Partition Key",
+  "Partitions": "Partitions",
+  "Password": "Password",
+  "Permissions": "Permissions",
+  "Pie Chart": "Pie Chart",
+  "Pig Design": "Pig Design",
+  "Pig Script": "Pig Script",
+  "Pin": "Pin",
+  "Planning": "Planning",
+  "Please wait, it might take a while...": "Please wait, it might take a while...",
+  "Popular": "Popular",
+  "Popularity": "Popularity",
+  "Prev": "Prev",
+  "Preview": "Preview",
+  "Primary key": "Primary key",
+  "Primary Key": "Primary Key",
+  "Project": "Project",
+  "Queries": "Queries",
+  "Query Analysis": "Query Analysis",
+  "Query browser": "Query browser",
+  "Query failed": "Query failed",
+  "Query needs to be saved.": "Query needs to be saved.",
+  "Query requires a select or aggregate.": "Query requires a select or aggregate.",
+  "Query running": "Query running",
+  "Query settings": "Query settings",
+  "queued": "queued",
+  "Quick Query": "Quick Query",
+  "Re-create": "Re-create",
+  "Re-create session": "Re-create session",
+  "Read": "Read",
+  "read": "read",
+  "Read-only": "Read-only",
+  "Refresh": "Refresh",
+  "region": "region",
+  "Remove": "Remove",
+  "Replace the editor content...": "Replace the editor content...",
+  "Report": "Report",
+  "Result available": "Result available",
+  "Result expired": "Result expired",
+  "Result image": "Result image",
+  "result(s) copied to the clipboard": "result(s) copied to the clipboard",
+  "Results have expired, rerun the query if needed.": "Results have expired, rerun the query if needed.",
+  "Right-click for details": "Right-click for details",
+  "Right-click to expand with columns": "Right-click to expand with columns",
+  "Risks": "Risks",
+  "rows.": "rows.",
+  "Running": "Running",
+  "Sample": "Sample",
+  "sample": "sample",
+  "sample query": "sample query",
+  "Samples": "Samples",
+  "Save": "Save",
+  "Save changes": "Save changes",
+  "Save session settings as default": "Save session settings as default",
+  "scatter group": "scatter group",
+  "Scatter Plot": "Scatter Plot",
+  "scatter size": "scatter size",
+  "Schedule": "Schedule",
+  "scope": "scope",
+  "Search": "Search",
+  "Search Dashboard": "Search Dashboard",
+  "Search data and saved documents...": "Search data and saved documents...",
+  "Search saved documents...": "Search saved documents...",
+  "Search...": "Search...",
+  "Sections": "Sections",
+  "Select a query or start typing to get optimization hints.": "Select a query or start typing to get optimization hints",
+  "Select and execute a query to see the analysis.": "Select and execute a query to see the analysis.",
+  "Select and execute a query to see the result.": "Select and execute a query to see the result.",
+  "Select json file": "Select json file",
+  "Select the chart parameters on the left": "Select the chart parameters on the left",
+  "Select this folder": "Select this folder",
+  "Selected dialect": "Selected dialect",
+  "Selected entry": "Selected entry",
+  "Selected type": "Selected type",
+  "Send": "Send",
+  "Sentry will recursively delete the SERVER or DATABASE privileges you marked for deletion.": "Sentry will recursively delete the SERVER or DATABASE privileges you marked for deletion.",
+  "Server": "Server",
+  "Sessions": "Sessions",
+  "Set as default application": "Set as default application",
+  "Set as default settings": "Set as default settings",
+  "Settings": "Settings",
+  "Share": "Share",
+  "Share as a gist": "Share as a gist",
+  "Share link": "Share link",
+  "Share the query selection via a link": "Share the query selection via a link",
+  "Share the query via a link": "Share the query via a link",
+  "Share to Slack": "Share to Slack",
+  "Shareable link": "Shareable link",
+  "Sharing": "Sharing",
+  "Shell Script": "Shell Script",
+  "Show 50 more...": "Show 50 more...",
+  "Show advanced": "Show advanced",
+  "Show columns": "Show columns",
+  "Show Details": "Show Details",
+  "Show details": "Show details",
+  "Show in Assist...": "Show in Assist...",
+  "Show more...": "Show more...",
+  "Show row details": "Show row details",
+  "Show sample": "Show sample",
+  "Show view SQL": "Show view SQL",
+  "Sign out": "Sign out",
+  "Size": "Size",
+  "Solr Search": "Solr Search",
+  "Sort ascending": "Sort ascending",
+  "Sort descending": "Sort descending",
+  "sorting": "sorting",
+  "Sources": "Sources",
+  "Spark Job": "Spark Job",
+  "SQL": "SQL",
+  "Start": "Start",
+  "Starting": "Starting",
+  "Statement": "Statement",
+  "Stats": "Stats",
+  "Stop": "Stop",
+  "Stop batch": "Stop batch",
+  "Stopped": "Stopped",
+  "Stopping": "Stopping",
+  "Streams": "Streams",
+  "String": "String",
+  "Success.": "Success.",
+  "Summary": "Summary",
+  "table": "table",
+  "Table": "Table",
+  "Table Browser": "Table Browser",
+  "Table Generating": "Table Generating",
+  "Tables": "Tables",
+  "Tags can only contain 1 to 50 alphanumeric characters, _ or -.": "Tags can only contain 1 to 50 alphanumeric characters, _ or -.",
+  "Tags could not be loaded.": "Tags could not be loaded.",
+  "Task History": "Task History",
+  "Terms": "Terms",
+  "Test connection": "Test connection",
+  "The document is not shared for modify.": "The document is not shared for modify.",
+  "The document is not shared for read.": "The document is not shared for read.",
+  "The file has not been found": "The file has not been found",
+  "The number of resulting rows was too big to be downloaded and the resulting file has been truncated to": "The number of resulting rows was too big to be downloaded and the resulting file has been truncated to",
+  "The table has no columns": "The table has no columns",
+  "The trash is empty": "The trash is empty",
+  "The upload has been canceled": "The upload has been canceled",
+  "There are currently no information about the sessions.": "There are currently no information about the sessions.",
+  "There are no stats to be shown": "There are no stats to be shown",
+  "There are no terms to be shown": "There are no terms to be shown",
+  "There is currently no information about the sessions.": "There is currently no information about the sessions.",
+  "There was a problem loading the databases": "There was a problem loading the databases",
+  "There was a problem loading the index preview": "There was a problem loading the index preview",
+  "There was a problem loading the indexes": "There was a problem loading the indexes",
+  "There was a problem loading the table preview": "There was a problem loading the table preview",
+  "There was an error loading the document.": "There was an error loading the document.",
+  "This app does not have a UI": "This app does not have a UI",
+  "This field does not support stats": "This field does not support stats",
+  "This will sync missing tables.": "This will sync missing tables.",
+  "Timeline": "Timeline",
+  "Timeline Chart": "Timeline Chart",
+  "Top down analysis": "Top down analysis",
+  "Top Nodes": "Top Nodes",
+  "Topics": "Topics",
+  "Type": "Type",
+  "type": "type",
+  "Type a username or a group name": "Type a username or a group name",
+  "Type Conversion": "Type Conversion",
+  "UDFs": "UDFs",
+  "UK": "UK",
+  "Undo": "Undo",
+  "Unlock this row": "Unlock this row",
+  "Unset from default application": "Unset from default application",
+  "Update": "Update",
+  "Updated: ": "Updated: ",
+  "Upload a file": "Upload a file",
+  "Upload file": "Upload file",
+  "Upload SQL Analyzer history": "Upload SQL Analyzer history",
+  "uploaded successfully": "uploaded successfully",
+  "USA": "USA",
+  "used by": "used by",
+  "Username": "Username",
+  "Value": "Value",
+  "value": "value",
+  "Values": "Values",
+  "variable": "variable",
+  "Variables": "Variables",
+  "View": "View",
+  "view": "view",
+  "View Profile": "View Profile",
+  "Views": "Views",
+  "virtual": "virtual",
+  "Visually explore the result": "Visually explore the result",
+  "Waiting for streaming data...": "Waiting for streaming data...",
+  "Welcome Tour": "Welcome Tour",
+  "With grant": "With grant",
+  "With grant option": "With grant option",
+  "Workflow": "Workflow",
+  "World": "World",
+  "write": "write",
+  "x-axis": "x-axis",
+  "y-axis": "y-axis",
+  "Yes": "Yes",
+  "Yes, delete": "Yes, delete",
+  "You don't have any saved queries.": "You don't have any saved queries.",
+  "Your download is being prepared": "Your download is being prepared",
+  "Your downloaded results have been truncated": "Your downloaded results have been truncated"
+}

+ 121 - 0
desktop/core/src/desktop/static/desktop/locales/es/translation.json

@@ -0,0 +1,121 @@
+{
+  "Add": "Agregar",
+  "aggregate": "agregado",
+  "Aggregate": "Agregar",
+  "alias": "alias",
+  "All": "Todos",
+  "and": "y",
+  "Apps": "Aplicaciones",
+  "Assist": "Asistir",
+  "Assistant": "Asistente",
+  "Back": "Volver",
+  "Browsers": "Exploradores",
+  "Bundle": "Bundle",
+  "Cancel": "Cancelar",
+  "Change": "Cambiar",
+  "Clipboard": "Portapapeles",
+  "Close": "Cerrar",
+  "Clusters": "Clústeres",
+  "Columns": "Columnas",
+  "condition": "condición",
+  "Create": "Crear",
+  "Created": "Creado",
+  "CSV": "CSV",
+  "cte": "CTE",
+  "CTEs": "CTE",
+  "Dashboard": "Panel",
+  "database": "base de datos",
+  "Databases": "Bases de datos",
+  "Date": "Fecha",
+  "default": "Predeterminado:",
+  "Delete": "Eliminar",
+  "Description": "Descripción",
+  "Details": "Detalles",
+  "Directory": "Directorio",
+  "Documentation": "Documentación",
+  "Documents": "Documentos",
+  "Editor": "Editor",
+  "Excel": "Excel",
+  "Export": "Exportar",
+  "Failed": "Erróneos",
+  "Field": "Campo",
+  "File": "Archivo",
+  "Files": "Archivos",
+  "filter": "filtrar",
+  "Filter": "Filtro",
+  "Format": "Dar formato",
+  "Functions": "Funciones",
+  "Group": "Grupo",
+  "HBase": "HBase",
+  "Help": "Ayuda",
+  "Hive": "Hive",
+  "Identifiers": "Identificadores",
+  "Impala": "Impala",
+  "Import": "Importar",
+  "Indexes": "Índices",
+  "Insert": "Insertar ",
+  "Jobs": "Jobs",
+  "join": "unión",
+  "Key": "Clave",
+  "keyword": "palabra clave",
+  "Keywords": "Palabras clave",
+  "Metrics": "Métricas",
+  "Modify": "Modificar",
+  "More": "Más",
+  "Name": "Nombre",
+  "Next": "Siguiente",
+  "No": "No",
+  "Notebook": "Bloc de notas",
+  "of": "de",
+  "Open": "Abrir",
+  "Options": "Opciones",
+  "Order": "Orden",
+  "other": "otro",
+  "Output": "Resultado",
+  "Owner": "Propietario",
+  "Partitions": "Particiones",
+  "Password": "Contraseña",
+  "Permissions": "Permisos",
+  "Popular": "Popular",
+  "Popularity": "Popularidad",
+  "Project": "Proyecto",
+  "Queries": "Consultas",
+  "Read": "Leer",
+  "Refresh": "Actualizar",
+  "Remove": "Quitar",
+  "Report": "Informe",
+  "Running": "En ejecución",
+  "Sample": "Ejemplo",
+  "sample": "ejemplo",
+  "Samples": "Ejemplos",
+  "Schedule": "Programa",
+  "Search": "Buscar",
+  "Settings": "Ajustes",
+  "Share": "Compartir",
+  "Sharing": "Compartir",
+  "Size": "Tamaño",
+  "Sources": "Orígenes",
+  "SQL": "SQL",
+  "Start": "Iniciar",
+  "Statement": "Instrucción",
+  "Stats": "Estadísticas",
+  "Stopped": "Detenido",
+  "table": "tabla",
+  "Table": "Tabla",
+  "Tables": "Tablas",
+  "Terms": "Términos",
+  "Type": "Tipo",
+  "UDFs": "UDF",
+  "Undo": "Deshacer",
+  "Username": "Nombre de usuario",
+  "Value": "Valor",
+  "Values": "Valores",
+  "variable": "variable",
+  "Variables": "Variables",
+  "View": "Ver",
+  "view": "vista",
+  "Views": "Vistas",
+  "virtual": "virtual",
+  "Workflow": "Workflow",
+  "Yes": "Sí"
+}

+ 121 - 0
desktop/core/src/desktop/static/desktop/locales/fr/translation.json

@@ -0,0 +1,121 @@
+{
+  "Add": "Ajouter",
+  "aggregate": "agrégat",
+  "Aggregate": "Agréger",
+  "alias": "alias",
+  "All": "Tout",
+  "and": "et",
+  "Apps": "Applications",
+  "Assist": "Aide",
+  "Assistant": "Assistant",
+  "Back": "Précédent",
+  "Browsers": "Navigateurs",
+  "Bundle": "Bundle",
+  "Cancel": "Annuler",
+  "Change": "Modifier",
+  "Clipboard": "Presse-papiers",
+  "Close": "Fermer",
+  "Clusters": "Clusters",
+  "Columns": "Colonnes",
+  "condition": "condition",
+  "Create": "Créer",
+  "Created": "Créé",
+  "CSV": "CSV",
+  "cte": "cte",
+  "CTEs": "CTE",
+  "Dashboard": "Tableau de bord",
+  "database": "base de données",
+  "Databases": "Bases de données",
+  "Date": "Date",
+  "default": "Par défaut :",
+  "Delete": "Supprimer",
+  "Description": "Description",
+  "Details": "Détails",
+  "Directory": "Répertoire",
+  "Documentation": "Documentation",
+  "Documents": "Documents",
+  "Editor": "Editeur",
+  "Excel": "Excel",
+  "Export": "Exporter",
+  "Failed": "Echoué",
+  "Field": "Champ",
+  "File": "Fichier",
+  "Files": "Fichiers",
+  "filter": "filtre",
+  "Filter": "Filtrer",
+  "Format": "Format",
+  "Functions": "Fonctions",
+  "Group": "Groupe",
+  "HBase": "HBase",
+  "Help": "Aide",
+  "Hive": "Hive",
+  "Identifiers": "Identificateurs",
+  "Impala": "Impala",
+  "Import": "Importer",
+  "Indexes": "Index",
+  "Insert": "Insérer",
+  "Jobs": "Jobs",
+  "join": "jointure",
+  "Key": "Clé",
+  "keyword": "mot-clé",
+  "Keywords": "Mots-clés",
+  "Metrics": "Mesures",
+  "Modify": "Modifier",
+  "More": "Plus",
+  "Name": "Nom",
+  "Next": "Suivant",
+  "No": "Non",
+  "Notebook": "Carnet",
+  "of": "sur",
+  "Open": "Ouvrir",
+  "Options": "Options",
+  "Order": "Ordre",
+  "other": "autre",
+  "Output": "Sortie",
+  "Owner": "Propriétaire",
+  "Partitions": "Partitions",
+  "Password": "Mot de passe",
+  "Permissions": "Autorisations",
+  "Popular": "Populaire",
+  "Popularity": "Popularité",
+  "Project": "Projet",
+  "Queries": "Requêtes",
+  "Read": "Lire",
+  "Refresh": "Actualiser",
+  "Remove": "Supprimer",
+  "Report": "Rapports",
+  "Running": "En cours d'exécution",
+  "Sample": "Exemple",
+  "sample": "exemple",
+  "Samples": "Exemples",
+  "Schedule": "Planifier",
+  "Search": "Rechercher", 
+  "Settings": "Paramètres",
+  "Share": "Partager",
+  "Sharing": "Partage",
+  "Size": "Taille",
+  "Sources": "Sources",
+  "SQL": "SQL",
+  "Start": "Début",
+  "Statement": "Instruction",
+  "Stats": "Statistiques",
+  "Stopped": "Arrêté",
+  "table": "table",
+  "Table": "Table",
+  "Tables": "Tables",
+  "Terms": "Termes",
+  "Type": "Type",
+  "UDFs": "FDU",
+  "Undo": "Annuler",
+  "Username": "Nom d'utilisateur",
+  "Value": "Valeur",
+  "Values": "Valeurs",
+  "variable": "variable",
+  "Variables": "Variables",
+  "View": "Afficher",
+  "view": "afficher",
+  "Views": "Vues",
+  "virtual": "virtuel",
+  "Workflow": "Workflow",
+  "Yes": "Oui"
+}

+ 121 - 0
desktop/core/src/desktop/static/desktop/locales/ja/translation.json

@@ -0,0 +1,121 @@
+{
+  "Add": "追加",
+  "aggregate": "集計",
+  "Aggregate": "集計",
+  "alias": "エイリアス",
+  "All": "すべて",
+  "and": "および",
+  "Apps": "アプリ",
+  "Assist": "アシスト",
+  "Assistant": "アシスト",
+  "Back": "戻る",
+  "Browsers": "ブラウザ",
+  "Bundle": "Bundle",
+  "Cancel": "キャンセル",
+  "Change": "変更",
+  "Clipboard": "クリップボード",
+  "Close": "閉じる",
+  "Clusters": "クラスタ",
+  "Columns": "列",
+  "condition": "条件",
+  "Create": "作成",
+  "Created": "作成済み",
+  "CSV": "CSV",
+  "cte": "CTE",
+  "CTEs": "CTE",
+  "Dashboard": "ダッシュボード",
+  "database": "データベース",
+  "Databases": "データベース",
+  "Date": "日付",
+  "default": "デフォルト:",
+  "Delete": "削除",
+  "Description": "説明",
+  "Details": "詳細",
+  "Directory": "ディレクトリ",
+  "Documentation": "ドキュメント",
+  "Documents": "ドキュメント",
+  "Editor": "エディタ",
+  "Excel": "Excel",
+  "Export": "エクスポート",
+  "Failed": "失敗しました",
+  "Field": "フィールド",
+  "File": "ファイル",
+  "Files": "ファイル",
+  "filter": "フィルタ",
+  "Filter": "フィルター",
+  "Format": "フォーマット",
+  "Functions": "関数",
+  "Group": "グループ",
+  "HBase": "HBase",
+  "Help": "ヘルプ",
+  "Hive": "Hive",
+  "Identifiers": "識別子",
+  "Impala": "Impala",
+  "Import": "インポート",
+  "Indexes": "インデックス",
+  "Insert": "カーソルの位置に",
+  "Jobs": "ジョブ",
+  "join": "結合",
+  "Key": "キー",
+  "keyword": "キーワード",
+  "Keywords": "キーワード",
+  "Metrics": "測定値",
+  "Modify": "変更",
+  "More": "詳細",
+  "Name": "名前",
+  "Next": "次ヘ",
+  "No": "いいえ",
+  "Notebook": "ノートブック",
+  "of": "/",
+  "Open": "開く",
+  "Options": "オプション",
+  "Order": "順序",
+  "other": "その他",
+  "Output": "出力",
+  "Owner": "所有者",
+  "Partitions": "パーティション",
+  "Password": "パスワード",
+  "Permissions": "パーミッション",
+  "Popular": "人気順",
+  "Popularity": "使用率",
+  "Project": "プロジェクト",
+  "Queries": "クエリ",
+  "Read": "読み取り",
+  "Refresh": "更新",
+  "Remove": "削除",
+  "Report": "レポート",
+  "Running": "実行中",
+  "Sample": "サンプル",
+  "sample": "サンプル",
+  "Samples": "例",
+  "Schedule": "スケジュール",
+  "Search": "検索",
+  "Settings": "設定",
+  "Share": "共有",
+  "Sharing": "共有",
+  "Size": "サイズ",
+  "Sources": "ソース",
+  "SQL": "SQL",
+  "Start": "開始",
+  "Statement": "ステートメント",
+  "Stats": "統計",
+  "Stopped": "停止しました",
+  "table": "テーブル",
+  "Table": "テーブル",
+  "Tables": "テーブル",
+  "Terms": "単語",
+  "Type": "タイプ",
+  "UDFs": "UDF",
+  "Undo": "取り消し",
+  "Username": "ユーザー名",
+  "Value": "値",
+  "Values": "値",
+  "variable": "変数",
+  "Variables": "変数",
+  "View": "表示",
+  "view": "ビュー",
+  "Views": "ビュー",
+  "virtual": "仮想",
+  "Workflow": "Workflow",
+  "Yes": "はい"
+}

+ 121 - 0
desktop/core/src/desktop/static/desktop/locales/ko/translation.json

@@ -0,0 +1,121 @@
+{
+  "Add": "추가",
+  "aggregate": "집계",
+  "Aggregate": "집계",
+  "alias": "별칭",
+  "All": "모두",
+  "and": "및",
+  "Apps": "앱",
+  "Assist": "지원",
+  "Assistant": "도우미",
+  "Back": "뒤로",
+  "Browsers": "브라우저",
+  "Bundle": "Bundle",
+  "Cancel": "취소",
+  "Change": "변경",
+  "Clipboard": "클립보드",
+  "Close": "닫기",
+  "Clusters": "클러스터",
+  "Columns": "열",
+  "condition": "조건",
+  "Create": "생성",
+  "Created": "생성됨",
+  "CSV": "CSV",
+  "cte": "cte",
+  "CTEs": "CTE",
+  "Dashboard": "대시보드",
+  "database": "데이터베이스",
+  "Databases": "데이터베이스",
+  "Date": "날짜",
+  "default": "기본값:",
+  "Delete": "삭제",
+  "Description": "설명",
+  "Details": "세부 정보",
+  "Directory": "디렉토리",
+  "Documentation": "문서",
+  "Documents": "문서",
+  "Editor": "편집기",
+  "Excel": "Excel",
+  "Export": "내보내기",
+  "Failed": "실패함",
+  "Field": "필드",
+  "File": "파일",
+  "Files": "파일",
+  "filter": "필터",
+  "Filter": "필터",
+  "Format": "형식",
+  "Functions": "함수",
+  "Group": "그룹",
+  "HBase": "HBase",
+  "Help": "도움말",
+  "Hive": "Hive",
+  "Identifiers": "식별자",
+  "Impala": "Impala",
+  "Import": "가져오기",
+  "Indexes": "인덱스",
+  "Insert": "커서 위치에 ",
+  "Jobs": "Job",
+  "join": "가입",
+  "Key": "키",
+  "keyword": "키워드",
+  "Keywords": "키워드",
+  "Metrics": "메트릭",
+  "Modify": "수정",
+  "More": "더",
+  "Name": "이름",
+  "Next": "다음",
+  "No": "아니요",
+  "Notebook": "노트북",
+  "of": "/",
+  "Open": "열기",
+  "Options": "옵션",
+  "Order": "순서",
+  "other": "기타",
+  "Output": "출력",
+  "Owner": "소유자",
+  "Partitions": "파티션",
+  "Password": "암호",
+  "Permissions": "권한",
+  "Popular": "인기",
+  "Popularity": "인기도",
+  "Project": "프로젝트",
+  "Queries": "쿼리",
+  "Read": "읽기",
+  "Refresh": "새로 고침",
+  "Remove": "제거",
+  "Report": "보고서",
+  "Running": "실행 중",
+  "Sample": "샘플",
+  "sample": "샘플",
+  "Samples": "샘플",
+  "Schedule": "예약",
+  "Search": "검색",    
+  "Settings": "설정",
+  "Share": "공유",
+  "Sharing": "공유",
+  "Size": "크기",
+  "Sources": "소스",
+  "SQL": "SQL",
+  "Start": "시작",
+  "Statement": "명령문",
+  "Stats": "상태",
+  "Stopped": "중지됨",
+  "table": "테이블",
+  "Table": "테이블",
+  "Tables": "테이블",
+  "Terms": "용어",
+  "Type": "유형",
+  "UDFs": "UDF",
+  "Undo": "실행 취소",
+  "Username": "사용자 이름",
+  "Value": "값",
+  "Values": "값",
+  "variable": "변수",
+  "Variables": "변수",
+  "View": "보기",
+  "view": "보기",
+  "Views": "보기",
+  "virtual": "가상",
+  "Workflow": "Workflow",
+  "Yes": "예"
+}

+ 32 - 0
desktop/core/src/desktop/static/desktop/locales/pt-BR/translation.json

@@ -0,0 +1,32 @@
+{
+  "Add": "Adicionar",
+  "Administration": "Administração",
+  "and": "e",
+  "Back": "Voltar",
+  "Bundle": "Bundle",
+  "Cancel": "Cancelar",
+  "default": "Padrão:",
+  "Description": "Descrição",
+  "Documentation": "Documentação",
+  "Failed": "Falhou",
+  "Group": "Grupo",
+  "HBase": "HBase",
+  "Hive": "Hive",
+  "Impala": "Impala",
+  "Indexes": "Índices",
+  "Name": "Nome",
+  "Next": "Próximo",
+  "No": "Não",
+  "of": "de",
+  "Owner": "Proprietário",
+  "Password": "Senha",
+  "Project": "Projeto",
+  "Read": "Ler",
+  "Search": "Pesquisar",
+  "Share": "Compartilhar",
+  "Sharing": "Compartilhando",
+  "Start": "Iniciar",
+  "Username": "Nome de usuário",
+  "Workflow": "Workflow",
+  "Yes": "Sim"
+}

+ 32 - 0
desktop/core/src/desktop/static/desktop/locales/pt/translation.json

@@ -0,0 +1,32 @@
+{
+  "Add": "Adicionar",
+  "Administration": "Administração",
+  "and": "e",
+  "Back": "Voltar",
+  "Bundle": "Bundle",
+  "Cancel": "Cancelar",
+  "default": "Predefinição:",
+  "Description": "Descrição",
+  "Documentation": "Documentação",
+  "Failed": "Falhou",
+  "Group": "Grupo",
+  "HBase": "HBase",
+  "Hive": "Hive",
+  "Impala": "Impala",
+  "Indexes": "Índices",
+  "Name": "Nome",
+  "Next": "Seguinte",
+  "No": "Não",
+  "of": "de",
+  "Owner": "Proprietário",
+  "Password": "Palavra-passe",
+  "Project": "Projecto",
+  "Read": "Ler",
+  "Search": "Pesquisar",
+  "Share": "Partilhar",
+  "Sharing": "Partilha",
+  "Start": "Iniciar",
+  "Username": "Nome de utilizador",
+  "Workflow": "Workflow",
+  "Yes": "Sim"
+}

+ 121 - 0
desktop/core/src/desktop/static/desktop/locales/zh-CN/translation.json

@@ -0,0 +1,121 @@
+{
+  "Add": "添加",
+  "aggregate": "汇总",
+  "Aggregate": "汇总",
+  "alias": "别名",
+  "All": "所有",
+  "and": "并",
+  "Apps": "应用程序",
+  "Assist": "辅助",
+  "Assistant": "助手",
+  "Back": "返回",
+  "Browsers": "浏览器",
+  "Bundle": "Bundle",
+  "Cancel": "取消",
+  "Change": "更改",
+  "Clipboard": "剪贴板",
+  "Close": "关闭",
+  "Clusters": "集群",
+  "Columns": "列",
+  "condition": "条件",
+  "Create": "创建",
+  "Created": "已创建",
+  "CSV": "CSV",
+  "cte": "cte",
+  "CTEs": "CTE",
+  "Dashboard": "控制面板",
+  "database": "数据库",
+  "Databases": "数据库",
+  "Date": "日期",
+  "default": "默认值:",
+  "Delete": "删除",
+  "Description": "描述",
+  "Details": "详细信息",
+  "Directory": "目录",
+  "Documentation": "文档",
+  "Documents": "文档",
+  "Editor": "编辑器",
+  "Excel": "Excel",
+  "Export": "导出",
+  "Failed": "失败",
+  "Field": "字段",
+  "File": "文件",
+  "Files": "文件",
+  "filter": "筛选器",
+  "Filter": "筛选器",
+  "Format": "格式",
+  "Functions": "功能",
+  "Group": "组",
+  "HBase": "HBase",
+  "Help": "帮助",
+  "Hive": "Hive",
+  "Identifiers": "标识符",
+  "Impala": "Impala",
+  "Import": "导入",
+  "Indexes": "索引",
+  "Insert": "插入",
+  "Jobs": "作业",
+  "join": "连接",
+  "Key": "键",
+  "keyword": "关键字",
+  "Keywords": "关键字",
+  "Metrics": "指标",
+  "Modify": "修改",
+  "More": "更多",
+  "Name": "名称",
+  "Next": "下一页",
+  "No": "否",
+  "Notebook": "笔记本",
+  "of": "的",
+  "Open": "打开",
+  "Options": "选项",
+  "Order": "排序",
+  "other": "其他",
+  "Output": "输出",
+  "Owner": "所有者",
+  "Partitions": "分区",
+  "Password": "密码",
+  "Permissions": "权限",
+  "Popular": "热门",
+  "Popularity": "热门程度",
+  "Project": "项目",
+  "Queries": "查询",
+  "Read": "读取",
+  "Refresh": "刷新",
+  "Remove": "删除",
+  "Report": "报告",
+  "Running": "正在运行",
+  "Sample": "样本",
+  "sample": "示例",
+  "Samples": "示例",
+  "Schedule": "计划",
+  "Search": "搜索",
+  "Settings": "设置",
+  "Share": "共享",
+  "Sharing": "共享",
+  "Size": "大小",
+  "Sources": "源",
+  "SQL": "SQL",
+  "Start": "开始",
+  "Statement": "语句",
+  "Stats": "统计数据",
+  "Stopped": "已停止",
+  "table": "表",
+  "Table": "表",
+  "Tables": "表",
+  "Terms": "术语",
+  "Type": "类型",
+  "UDFs": "UDF",
+  "Undo": "撤消",
+  "Username": "用户名",
+  "Value": "值",
+  "Values": "值",
+  "variable": "变量",
+  "Variables": "变量",
+  "View": "查看",
+  "view": "视图",
+  "Views": "视图",
+  "virtual": "虚拟",
+  "Workflow": "Workflow",
+  "Yes": "是"
+}

+ 121 - 0
desktop/core/src/desktop/static/desktop/locales/zh/translation.json

@@ -0,0 +1,121 @@
+{
+  "Add": "添加",
+  "aggregate": "汇总",
+  "Aggregate": "汇总",
+  "alias": "别名",
+  "All": "所有",
+  "and": "并",
+  "Apps": "应用程序",
+  "Assist": "辅助",
+  "Assistant": "助手",
+  "Back": "返回",
+  "Browsers": "浏览器",
+  "Bundle": "Bundle",
+  "Cancel": "取消",
+  "Change": "更改",
+  "Clipboard": "剪贴板",
+  "Close": "关闭",
+  "Clusters": "集群",
+  "Columns": "列",
+  "condition": "条件",
+  "Create": "创建",
+  "Created": "已创建",
+  "CSV": "CSV",
+  "cte": "cte",
+  "CTEs": "CTE",
+  "Dashboard": "控制面板",
+  "database": "数据库",
+  "Databases": "数据库",
+  "Date": "日期",
+  "default": "默认值:",
+  "Delete": "删除",
+  "Description": "描述",
+  "Details": "详细信息",
+  "Directory": "目录",
+  "Documentation": "文档",
+  "Documents": "文档",
+  "Editor": "编辑器",
+  "Excel": "Excel",
+  "Export": "导出",
+  "Failed": "失败",
+  "Field": "字段",
+  "File": "文件",
+  "Files": "文件",
+  "filter": "筛选器",
+  "Filter": "筛选器",
+  "Format": "格式",
+  "Functions": "功能",
+  "Group": "组",
+  "HBase": "HBase",
+  "Help": "帮助",
+  "Hive": "Hive",
+  "Identifiers": "标识符",
+  "Impala": "Impala",
+  "Import": "导入",
+  "Indexes": "索引",
+  "Insert": "插入",
+  "Jobs": "作业",
+  "join": "连接",
+  "Key": "键",
+  "keyword": "关键字",
+  "Keywords": "关键字",
+  "Metrics": "指标",
+  "Modify": "修改",
+  "More": "更多",
+  "Name": "名称",
+  "Next": "下一页",
+  "No": "否",
+  "Notebook": "笔记本",
+  "of": "的",
+  "Open": "打开",
+  "Options": "选项",
+  "Order": "排序",
+  "other": "其他",
+  "Output": "输出",
+  "Owner": "所有者",
+  "Partitions": "分区",
+  "Password": "密码",
+  "Permissions": "权限",
+  "Popular": "热门",
+  "Popularity": "热门程度",
+  "Project": "项目",
+  "Queries": "查询",
+  "Read": "读取",
+  "Refresh": "刷新",
+  "Remove": "删除",
+  "Report": "报告",
+  "Running": "正在运行",
+  "Sample": "样本",
+  "sample": "示例",
+  "Samples": "示例",
+  "Schedule": "计划",
+  "Search": "搜索",
+  "Settings": "设置",
+  "Share": "共享",
+  "Sharing": "共享",
+  "Size": "大小",
+  "Sources": "源",
+  "SQL": "SQL",
+  "Start": "开始",
+  "Statement": "语句",
+  "Stats": "统计数据",
+  "Stopped": "已停止",
+  "table": "表",
+  "Table": "表",
+  "Tables": "表",
+  "Terms": "术语",
+  "Type": "类型",
+  "UDFs": "UDF",
+  "Undo": "撤消",
+  "Username": "用户名",
+  "Value": "值",
+  "Values": "值",
+  "variable": "变量",
+  "Variables": "变量",
+  "View": "查看",
+  "view": "视图",
+  "Views": "视图",
+  "virtual": "虚拟",
+  "Workflow": "Workflow",
+  "Yes": "是"
+}

+ 2 - 0
desktop/core/src/desktop/templates/global_js_constants.mako

@@ -36,6 +36,7 @@
   from metastore.views import has_write_access
   from notebook.conf import ENABLE_NOTEBOOK_2, ENABLE_QUERY_ANALYSIS, ENABLE_QUERY_BUILDER, ENABLE_QUERY_SCHEDULING, ENABLE_SQL_INDEXER, \
       get_ordered_interpreters, SHOW_NOTEBOOKS
+  from django.utils.translation import get_language
 
   if sys.version_info[0] > 2:
     from django.utils.translation import gettext as _
@@ -632,6 +633,7 @@
     'Yes': '${ _('Yes') }',
     'Yes, delete': '${ _('Yes, delete') }',
   };
+  window.HUE_LANG = '${get_language()}';
 
   window.STATIC_URLS = {
     'desktop/art/cloudera-data-warehouse-3.svg': '${ static('desktop/art/cloudera-data-warehouse-3.svg') }',

+ 1 - 2
desktop/libs/notebook/src/notebook/templates/editor2.mako

@@ -1013,9 +1013,8 @@ There is no bridge to KO for components using this integration. Example using in
               <ReactExampleGlobal data-bind="reactWrapper: 'ReactExampleGlobal', props: { children: 'KO binding used', myObj: activeExecutable }"></ReactExampleGlobal>
               
               Example component defined and used only within the "Editor app"
-              <ReactExample data-bind="reactWrapper: 'ReactExample', props: { title: 'Result title', activeExecutable: activeExecutable }"></ReactExample>
+              <ReactExample data-bind="reactWrapper: 'ReactExample', props: { activeExecutable: activeExecutable }"></ReactExample>
               !-->
-
               <result-table-ko-bridge class="table-results-bridge" data-bind="vueKoProps: {
                   'executable-observable': activeExecutable
                 }"></result-table-ko-bridge>

+ 191 - 0
package-lock.json

@@ -20,6 +20,8 @@
         "dropzone": "5.5.1",
         "file-saver": "^2.0.2",
         "filesize": "4.0.0",
+        "i18next": "^21.9.2",
+        "i18next-http-backend": "^1.4.4",
         "jquery": "3.5.1",
         "jquery-contextmenu": "2.9.2",
         "jquery-form": "4.3.0",
@@ -41,6 +43,7 @@
         "qs": "6.9.4",
         "react": "^18.1.0",
         "react-dom": "^18.1.0",
+        "react-i18next": "^11.18.6",
         "regenerator-runtime": "^0.13.3",
         "removeNPMAbsolutePaths": "^1.0.4",
         "sanitize-html": "^2.7.2",
@@ -6330,6 +6333,14 @@
         "node": ">=10"
       }
     },
+    "node_modules/cross-fetch": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
+      "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
+      "dependencies": {
+        "node-fetch": "2.6.7"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -9233,6 +9244,14 @@
       "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
       "dev": true
     },
+    "node_modules/html-parse-stringify": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+      "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+      "dependencies": {
+        "void-elements": "3.1.0"
+      }
+    },
     "node_modules/html-tags": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz",
@@ -9304,6 +9323,36 @@
         "node": "*"
       }
     },
+    "node_modules/i18next": {
+      "version": "21.9.2",
+      "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.9.2.tgz",
+      "integrity": "sha512-00fVrLQOwy45nm3OtC9l1WiLK3nJlIYSljgCt0qzTaAy65aciMdRy9GsuW+a2AtKtdg9/njUGfRH30LRupV7ZQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://locize.com"
+        },
+        {
+          "type": "individual",
+          "url": "https://locize.com/i18next.html"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+        }
+      ],
+      "dependencies": {
+        "@babel/runtime": "^7.17.2"
+      }
+    },
+    "node_modules/i18next-http-backend": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.4.tgz",
+      "integrity": "sha512-M4gLPe6JKZ2p1UmE6t4rzWV/sAxgrLThW7ztXAsTpFwFqXoyzhTzX8eYxVv9KjpCQh4K9nwxnEjEi+74C4Thbg==",
+      "dependencies": {
+        "cross-fetch": "3.1.5"
+      }
+    },
     "node_modules/iconv-lite": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -12890,6 +12939,44 @@
         "semver": "bin/semver"
       }
     },
+    "node_modules/node-fetch": {
+      "version": "2.6.7",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/node-fetch/node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+    },
+    "node_modules/node-fetch/node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+    },
+    "node_modules/node-fetch/node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    },
     "node_modules/node-int64": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -14537,6 +14624,27 @@
         "react": "^18.2.0"
       }
     },
+    "node_modules/react-i18next": {
+      "version": "11.18.6",
+      "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz",
+      "integrity": "sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==",
+      "dependencies": {
+        "@babel/runtime": "^7.14.5",
+        "html-parse-stringify": "^3.0.1"
+      },
+      "peerDependencies": {
+        "i18next": ">= 19.0.0",
+        "react": ">= 16.8.0"
+      },
+      "peerDependenciesMeta": {
+        "react-dom": {
+          "optional": true
+        },
+        "react-native": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/react-is": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -16940,6 +17048,14 @@
         "node": ">=10"
       }
     },
+    "node_modules/void-elements": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+      "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/vti": {
       "version": "0.0.24",
       "resolved": "https://registry.npmjs.org/vti/-/vti-0.0.24.tgz",
@@ -22687,6 +22803,14 @@
         "yaml": "^1.10.0"
       }
     },
+    "cross-fetch": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
+      "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
+      "requires": {
+        "node-fetch": "2.6.7"
+      }
+    },
     "cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -24905,6 +25029,14 @@
       "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
       "dev": true
     },
+    "html-parse-stringify": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+      "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+      "requires": {
+        "void-elements": "3.1.0"
+      }
+    },
     "html-tags": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz",
@@ -24961,6 +25093,22 @@
       "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz",
       "integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ="
     },
+    "i18next": {
+      "version": "21.9.2",
+      "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.9.2.tgz",
+      "integrity": "sha512-00fVrLQOwy45nm3OtC9l1WiLK3nJlIYSljgCt0qzTaAy65aciMdRy9GsuW+a2AtKtdg9/njUGfRH30LRupV7ZQ==",
+      "requires": {
+        "@babel/runtime": "^7.17.2"
+      }
+    },
+    "i18next-http-backend": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.4.tgz",
+      "integrity": "sha512-M4gLPe6JKZ2p1UmE6t4rzWV/sAxgrLThW7ztXAsTpFwFqXoyzhTzX8eYxVv9KjpCQh4K9nwxnEjEi+74C4Thbg==",
+      "requires": {
+        "cross-fetch": "3.1.5"
+      }
+    },
     "iconv-lite": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -27714,6 +27862,35 @@
         }
       }
     },
+    "node-fetch": {
+      "version": "2.6.7",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+      "requires": {
+        "whatwg-url": "^5.0.0"
+      },
+      "dependencies": {
+        "tr46": {
+          "version": "0.0.3",
+          "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+          "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+        },
+        "webidl-conversions": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+          "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+        },
+        "whatwg-url": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+          "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+          "requires": {
+            "tr46": "~0.0.3",
+            "webidl-conversions": "^3.0.0"
+          }
+        }
+      }
+    },
     "node-int64": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -28899,6 +29076,15 @@
         "scheduler": "^0.23.0"
       }
     },
+    "react-i18next": {
+      "version": "11.18.6",
+      "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz",
+      "integrity": "sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==",
+      "requires": {
+        "@babel/runtime": "^7.14.5",
+        "html-parse-stringify": "^3.0.1"
+      }
+    },
     "react-is": {
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -30748,6 +30934,11 @@
         "typescript": "^4.3.2"
       }
     },
+    "void-elements": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+      "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
+    },
     "vti": {
       "version": "0.0.24",
       "resolved": "https://registry.npmjs.org/vti/-/vti-0.0.24.tgz",

+ 3 - 0
package.json

@@ -41,6 +41,8 @@
     "dropzone": "5.5.1",
     "file-saver": "^2.0.2",
     "filesize": "4.0.0",
+    "i18next": "^21.9.2",
+    "i18next-http-backend": "^1.4.4",
     "jquery": "3.5.1",
     "jquery-contextmenu": "2.9.2",
     "jquery-form": "4.3.0",
@@ -62,6 +64,7 @@
     "qs": "6.9.4",
     "react": "^18.1.0",
     "react-dom": "^18.1.0",
+    "react-i18next": "^11.18.6",
     "regenerator-runtime": "^0.13.3",
     "removeNPMAbsolutePaths": "^1.0.4",
     "sanitize-html": "^2.7.2",

Some files were not shown because too many files changed in this diff