1

I have a web application built with ReactJS, react-scripts (webpack), Monaco Editor, and react-monaco-editor. By default, the context menu is in English:

enter image description here

In the hover window, View Problem, Quick Fix, etc. are also in English.

enter image description here

Now, I would like to localize these keywords to Chinese, does anyone know how to do it?

PS 0: I have searched similar issues such as https://github.com/microsoft/monaco-editor/blob/main/samples/browser-amd-localized/index.html, but I don't know how to configure (e.g., require.config) in a project with webpack and react-monaco-editor.

PS 1: Here is a working sample with react-monaco-editor: https://codesandbox.io/p/sandbox/goofy-rgb-q525mq?file=%2Fsrc%2FApp.js%3A16%2C20. The code calling react-monaco-editor is as follows, which is similar to the way of calling react-monaco-editor in my code.

    <MonacoEditor
        width="800"
        height="600"
        language="javascript"
        value={code}
        options={options}
        onChange={this.onChange}
        editorDidMount={this.editorDidMount}
    />

Edit 1: Thanks to @blutorange and @VonC, the demo of blutorange in codesandbox does work. I tried to make it work in my code. I put import AAA in my files before the import of monaco-editor and react-monaco-editor. I can see that After setLocaleData is printed in the console, but the keywords of the editor are still in English. Additionally, I use dva in the project. The follows is src/index.tsx:

import './AAA';
import 'react-app-polyfill/ie11';
import 'react-app-polyfill/stable';

import dva from 'dva';
import './index.css';
import router from './router';
import AuthModel from './models/auth';
import SubscribtionModel from './models/subscription';
import AppModel from './models/app';
import SpreadsheetModel from './models/spreadsheet';
import UsageModel from './models/usage';
import AiModel from './models/ai';
import SettingsModel from './models/settings';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import zh from './i18n/locales/zh.json';
import en from './i18n/locales/en.json';

function patchOfficeExtension() {
    // https://stackoverflow.com/questions/53327711/how-to-add-a-polyfill-to-support-finally-in-edge
    //@ts-ignore
    OfficeExtension.Promise.prototype.finally = /* Promise.prototype.finally || */ {
        finally(fn: any): any {
            const onFinally = (cb: any) => OfficeExtension.Promise.resolve(fn()).then(cb);
            // @ts-ignore
            return this.then(
                (result: any) => onFinally(() => result),
                (reason: any) => onFinally(() => OfficeExtension.Promise.reject(reason)),
            );
        },
    }.finally;
    // tslint:enable
}

// https://chat.openai.com/c/06d8c247-6107-463f-80a9-6b571ee23f88
i18n.use(initReactI18next).init({
    lng: 'zh',
    // lng: 'en',
    resources: {
        en: { translation: en },
        zh: { translation: zh },
    },
    fallbackLng: 'en',
    // interpolation: { escapeValue: false }
});

//@ts-ignore
//import createLoading from 'dva-loading';

initializeIcons();

// 1. Initialize
const app = dva();
//app.use(createLoading());

// 2. Plugins
// app.use({});

// 3. Model
//@ts-ignore
app.model(AuthModel);
app.model(SubscribtionModel)
app.model(AppModel);
app.model(SpreadsheetModel);
app.model(UsageModel);
app.model(AiModel);
app.model(SettingsModel);
// 4. Router
//@ts-ignore
app.router(router);

// 5. Start
app.start('#root');

Here is the updated webpack.config.js:

const WEBPACK = require('webpack');
const PATH = require('path');
const MonacoWebpackPlugin = require('monaco-editor-esm-webpack-plugin');

module.exports = {
resolve: {
    extensions: ['.js', '.jsx']
},
context: __dirname,
entry: {
    app: ['./src/index.jsx'] // app: ['./MY_FOLDER_INPUT/MY_FILE_INDEX.jsx']
},
output: {
    path: PATH.join(__dirname, '/MY_FOLDER_OUTPUT'),
    filename: 'index.js'
},
module: {
    rules: [
        {
            test: /\.js/,
            enforce: 'pre',
            include: /node_modules[\\\/]monaco-editor[\\\/]esm/,
            use: MonacoWebpackPlugin.loader
        },
        {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
                loader: "babel-loader"
            }
        }
    ]
},
plugins: [
    new MonacoWebpackPlugin()
]
};

Could you tell how I could detect precisely the order of imports? I tried to use source-map-explorer, it gives a map as follows, but i cannot even find AAA:

enter image description here

SoftTimur
  • 5,630
  • 38
  • 140
  • 292

1 Answers1

2

Check first if a webpack's plugin for monaco editor like monaco-editor-nls would be enough for your use case.

You would need modify the editorDidMount function in your React component to also set the locale data.

Following its "Using" section example, that would be for you:

import React, { useEffect } from 'react';
import MonacoEditor from 'react-monaco-editor';
import { setLocaleData } from 'monaco-editor-nls';
import zh_CN from 'monaco-editor-nls/locale/zh-hans';

const App = () => {
  const code = "// Add your code here";
  const options = {
    selectOnLineNumbers: true
  };

  const editorDidMount = (editor, monaco) => {
    console.log('editorDidMount', editor);
    editor.focus();

    // After mounting the editor, set the locale data
    setLocaleData(zh_CN);

    // You may then use the `monaco` instance as necessary
    // Note that you already have an editor instance,
    // so there's no need to call `monaco.editor.create` unless you're creating another editor instance
  };

  const onChange = (newValue, e) => {
    console.log('onChange', newValue, e);
  };

  return (
    <MonacoEditor
      width="800"
      height="600"
      language="javascript"
      theme="vs-dark"
      value={code}
      options={options}
      onChange={onChange}
      editorDidMount={editorDidMount}
    />
  );
};

export default App;

The setLocaleData is called in the editorDidMount function to set the locale data after the Monaco Editor instance has been created. In your case, as you are using react-monaco-editor, the editor instance is created automatically by the MonacoEditor component, and you get that instance in the editorDidMount function.


As commented, it is not enough on its own.
Try adding monaco-editor-esm-webpack-plugin (npm install monaco-editor-esm-webpack-plugin)

And update your webpack.config.js:

const MonacoWebpackPlugin = require('monaco-editor-esm-webpack-plugin');
const PATH = require('path');

module.exports = {
  resolve: {
    extensions: ['.js', '.jsx']
  },
  context: __dirname,
  entry: {
    app: ['./src/index.jsx'] // app: ['./MY_FOLDER_INPUT/MY_FILE_INDEX.jsx']
  },
  output: {
    path: PATH.join(__dirname, '/MY_FOLDER_OUTPUT'),
    filename: 'index.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  },
  plugins: [
    new MonacoWebpackPlugin({
      // available options are documented at https://github.com/microsoft/monaco-editor-webpack-plugin#options
      languages: ['json'], 
      features: ['coreCommands', 'find']
    })
  ]
};

In the plugins array, you see now new MonacoWebpackPlugin() with some options. In the languages array, you should list the languages you want to use in your editor. In the features array, you can enable additional editor features.

In your main script file, you should be able to import the locale data from monaco-editor-nls and set the locale data using the setLocaleData function.

import { setLocaleData } from 'monaco-editor-nls';
import zh_CN from 'monaco-editor-nls/locale/zh-hans';

setLocaleData(zh_CN);

blutorange adds in the comments:

From what I remember working on monaco editor, it eagerly reads and caches translations, so I suspect that when you call setLocateData when the component is mounted, it might already be too late, as monaco editor will have already loaded the English translations.

There's also this note in the webpack plugin: You must import/require [monaco-editor] after setLocaleData.

That means, to ensure setLocaleData is called before the Monaco Editor loads, you might want to try calling it in a higher level component or as early as possible in your application's entry point file.

Your entry point file should include:

// index.jsx or index.js

import { setLocaleData } from 'monaco-editor-nls';
import zh_CN from 'monaco-editor-nls/locale/zh-hans';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';  // adjust this path to point to your App component

setLocaleData(zh_CN);

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

Here, setLocaleData would be called before your App component (which presumably contains the MonacoEditor component) is imported and used. The Monaco Editor is likely to be imported for the first time in your App component or in a component within App, ensuring setLocaleData is called before any Monaco related imports.

Do update your webpack configuration to use the monaco-editor-esm-webpack-plugin as outlined previously.


blutorange adds in the comments:

I can see 3 potential issues, though there might be more.

  1. First of all, the dependencies in that sandbox project are pretty old.
  2. Secondly, you're importing your App (which imports monaco-editor) before you call setLocaleData... but webpack does not care anyways.
  3. I changed the import order in the sandbox, but if you take a look at the generated bundle, you can see the following:
    imgur.com/a/SqcSfuh
    All imports are moved to the top, and the actual code is executed afterwards.
    And if you debug into the bundled code, it also does not seem to ever execute the replaced localize-nls code.

That means: make sure all your packages are up-to-date:

npm install react-monaco-editor@latest
npm install monaco-editor-nls@latest
npm install monaco-editor-esm-webpack-plugin@latest

Then try and modify how you import monaco-editor and react-monaco-editor and where you call setLocaleData. That is tricky because you cannot control how react-monaco-editor imports monaco-editor internally.

Instead of using setLocaleData in editorDidMount or the entry point file, try using it in a separate module that you import before you import monaco-editor. That would ensure setLocaleData is called before monaco-editor is imported.

Define a separate module (let's call it set-locale.js):

// set-locale.js

import { setLocaleData } from 'monaco-editor-nls';
import zh_CN from 'monaco-editor-nls/locale/zh-hans';

setLocaleData(zh_CN);

And use it in your component file:

import './set-locale';
import * as monaco from 'monaco-editor';

// rest of your component code

That approach attempts to ensure setLocaleData is called before monaco-editor is imported.


blutorange also points out in the comments:

  • a working setup
  • monaco-editor/react which completely sidesteps the issue by not bundling monaco editor whatsoever but just loads it from a CDN at runtime.
    npm install @monaco-editor/react

That is a good point. @monaco-editor/react is indeed another wrapper for Monaco Editor that might suit your needs better.

That means, on that last point, you would not have to deal with webpack configurations for bundling the editor yourself.

import Editor from "@monaco-editor/react";

function App() {
  const handleEditorDidMount = (editor, monaco) => {
    // here you can access the monaco instance
    // you could apply a language change here
  }

  return (
    <Editor
      height="90vh"
      defaultLanguage="javascript"
      defaultValue="// some comment"
      onMount={handleEditorDidMount}
    />
  );
}

To localize the editor with this package, you would have to find a way to load the locale data after the editor is loaded from the CDN.
See loader-config

import { loader } from '@monaco-editor/react';

// you can change the source of the monaco files
loader.config({ paths: { vs: '...' } });

// you can configure the locales
loader.config({ 'vs/nls': { availableLanguages: { '*': 'de' } } });

// or
loader.config({
  paths: {
    vs: '...',
  },
  'vs/nls': {
    availableLanguages: {
      '*': 'zh-cn',
    },
  },
});
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Thank you for the help. I added `monaco-editor-nls` to the sample project and added `setLocaleData(zh_CN)` to `editorDidMount`, the keywords are still in English: https://codesandbox.io/p/sandbox/unruffled-joana-c6tpqp?file=%2Fsrc%2FApp.js%3A8%2C23. – SoftTimur Jul 27 '23 at 05:14
  • @SoftTimur OK. You might need [`monaco-editor-esm-webpack-plugin`](https://github.com/wang12124468/monaco-editor-esm-webpack-plugin) as well, but I do not know how exactly to set it up. – VonC Jul 27 '23 at 05:58
  • Yes, i think the key is about setting up monaco-editor-esm-webpack-plugin. – SoftTimur Jul 27 '23 at 06:02
  • @SoftTimur OK, I have edited the answer to include that new setup (as well as I can understand it from the documentation) – VonC Jul 27 '23 at 06:09
  • thanks again. I added monaco-editor-esm-webpack-plugin and updated webpack.config.dev.js and webpack.config.prod.js, everything is still in english: https://codesandbox.io/p/sandbox/affectionate-yonath-nnlvl6?file=%2Fconfig%2Fwebpack.config.dev.js%3A202%2C13 – SoftTimur Jul 27 '23 at 12:07
  • @SoftTimur Then, It could be worthwhile to open issues on the respective repositories (`monaco-editor`, `react-monaco-editor`, `monaco-editor-nls`, `monaco-editor-esm-webpack-plugin`) describing the problem and the steps you have already taken. – VonC Jul 27 '23 at 12:25
  • Yes, I did open issues in these repositories. – SoftTimur Jul 27 '23 at 12:33
  • @SoftTimur [monaco-editor-esm-webpack-plugin issue 23](https://github.com/wang12124468/monaco-editor-esm-webpack-plugin/issues/23) seems to be closed. Maybe mentioning it on [microsoft/monaco-editor/](https://github.com/microsoft/monaco-editor/issues?q=is%3Aopen+is%3Aissue) would be better? – VonC Jul 27 '23 at 12:45
  • Thank you for the advice, I just mentioned it. – SoftTimur Jul 27 '23 at 13:08
  • From what I remember working on monaco editor, it eagerly reads and caches translations, so I suspect that when you call setLocateData when the component is mounted, it might already be too late, as monaco editor will have already loaded the English translations. There's also this note in the webpack plugin: `You must import/require [monaco-editor] after setLocaleData` – blutorange Jul 27 '23 at 14:01
  • @SoftTimur I have edited the answer to take into account [blutorange](https://stackoverflow.com/users/3925216/blutorange)'s [comment](https://stackoverflow.com/questions/76754247/localize-react-monaco-editor-and-set-up-monaco-editor-esm-webpack-plugin/76774921#comment135360406_76774921). – VonC Jul 27 '23 at 15:48
  • @blutorange Thank you for this feedback. I have edited the answer to suggest changes which would take into account your comment. – VonC Jul 27 '23 at 15:48
  • Thanks to @blutorange and @VonC, I have been thinking how to call `setLocaleData(zh_CN)` before importing "react-monaco-editor`. I tried VonC's code, but it still does not work: https://codesandbox.io/p/sandbox/awesome-cache-nlgchp?file=%2Fsrc%2Findex.js%3A10%2C36 – SoftTimur Jul 27 '23 at 16:01
  • I can see 3 potential issues, though there might be more. First of all, the dependencies in that sandbox project are pretty old. Secondly, you're importing your App (which imports monaco-editor) before you call setLocaleData... but webpack does not care anyways. I changed the import order in the sandbox, but if you take a look at the generated bundle, you can see the following: https://imgur.com/a/SqcSfuh All imports are moved to the top, and the actual code is executed afterwards. And if you debug into the bundled code, it also does not seem to ever execute the replaced localize-nls code. – blutorange Jul 27 '23 at 21:00
  • @SoftTimur I have included blutorange's comment in the answer, as well as some suggestion. – VonC Jul 27 '23 at 21:59
  • @blutorange Again, thank you for this detailed feedback. I have included it in the answer, with some suggestions. – VonC Jul 27 '23 at 22:00
  • 1
    You're welcome, I'm happy if this improves the answer for everybody. I'm just going to leave 3 last comments for now: (a) In that sandbox, it's also missing the rule section from the webpack-esm-plugin, which does part of the magic; (b) it definitely does work when setup correctly, here's a *simple* demo: https://codesandbox.io/p/sandbox/upbeat-sea-dgs6x9?welcome=true (c) There's also https://www.npmjs.com/package/@monaco-editor/react, which completely sidesteps the issue by *not* bundling monaco editor whatsoever but just loads it from a CDN at runtime. – blutorange Jul 27 '23 at 23:28
  • @blutorange That looks promising, thank you. I have updated the answer accordingly. – VonC Jul 28 '23 at 05:07
  • `To localize the editor with this package, you would have to find a way to load the locale data after the editor is loaded from the CDN.` --> https://github.com/suren-atoyan/monaco-react#loader-config `loader.config({'vs/nls': {availableLanguages:'*': 'zh-cn'}}}` That worked when I tried it. – blutorange Jul 28 '23 at 05:26
  • Thanks to @blutorange and @VonC, please see `Edit 1` in my OP. – SoftTimur Jul 28 '23 at 06:32
  • @SoftTimur JavaScript (and by extension, TypeScript) modules are not that predictable when it comes to execution order. Your `AAA.js` module should be run first because it's the first import in your `index.tsx` file. But the problem is that the monaco-editor might already be initialized before this code is run. – VonC Jul 28 '23 at 08:08
  • @SoftTimur You need to ensure that the initialization of monaco-editor happens only after you've set the locale data. One possible way is to use [dynamic imports (which return promises)](https://blog.logrocket.com/react-dynamic-imports-route-centric-code-splitting-guide/#dynamic-imports) to make sure your code runs in the order you want. You could first import and set the locale data, then dynamically import monaco-editor. However, it might not work with your current setup and might require some heavy refactoring. – VonC Jul 28 '23 at 08:09
  • @VonC, I'm aware of `Lazy` and do use it in some parts of the code. I think I have set the locale data before the import of monaco-editor, but the result did not work. So I would like to check the execution order like what blutorange did with https://imgur.com/a/SqcSfuh. Do you know how? – SoftTimur Jul 28 '23 at 08:58
  • @VonC Yeah I know, it's a bad hack. But it does illustrate what the issue is, namely the loading order. But you're right, this does need a proper solution though. – blutorange Jul 28 '23 at 09:43
  • @SoftTimur If you want to verify the execution order of your JavaScript or TypeScript code, one simple way would be to use console.log() statements. This is a rudimentary approach, but it can often be the quickest and easiest way to confirm the order in which your code is running. – VonC Jul 28 '23 at 09:44
  • @blutorange and @VonC I still cannot make this work on my local project. So I tried to make blutorange's code in Codesandbox work locally, I created a public project by copying all the main files. But `yarn build` raised an error: https://github.com/chengtie/localize-react-monaco-editor/issues/1, could you help? How did blutorange generate the files under `dist/`? – SoftTimur Jul 30 '23 at 07:15
  • @SoftTimur "under `dist/`"? It was likely done using a build tool such as [webpack](https://jsramblings.com/creating-a-react-app-with-webpack/) or [rollup](https://blog.bitsrc.io/trying-rollup-for-react-applications-d3c2304d16bf). These tools compile your code and bundle it into a set of output files that are placed in a `dist/` directory. – VonC Jul 30 '23 at 08:26
  • @SoftTimur Another potential idea (that I haven't tried) might be two create two separate webpack bundles. One with only monaco editor and a small script that sets the language and exposes the editor to the global scope (if not done so automatically). In your HTML page, you then make sure that script is loaded first. In the second main bundle, use web pack externals to exclude monaco editor from the bundle and load it from the window global instead. – blutorange Jul 31 '23 at 11:09
  • @SoftTimur Great, do not hesitate to post your own answer to illustrate your workaround. – VonC Aug 02 '23 at 16:53
  • Thanks to @VonC and @blutorange for the help here and in GitHub. The code in blutorange's CodeSandbox does work, though I have not fully made it work on my project yet. Currently, I use `@monaco-editor/react`, which bypasses this problem. Here is a working sample with `@monaco-editor/react`: https://codesandbox.io/s/tender-hypatia-zdjpx2 (the `DiagnosticsAdapter` part is not necessary if you don't want to set listeners for adding or removing models). – SoftTimur Aug 03 '23 at 06:53