From 05ae6dabba2197883b0f5e0eaa85b2d8ecffc71f Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 4 Nov 2019 09:02:12 +0100 Subject: [PATCH] Update I18n module doc --- docs/.vuepress/config.js | 1 + docs/.vuepress/public/margin-strings.jpg | Bin 0 -> 9109 bytes docs/.vuepress/public/sm-empty-state.jpg | Bin 0 -> 6890 bytes docs/modules/Components-TOREMOVE.md | 466 ----------------------- docs/modules/I18n.md | 142 +++++++ 5 files changed, 143 insertions(+), 466 deletions(-) create mode 100644 docs/.vuepress/public/margin-strings.jpg create mode 100644 docs/.vuepress/public/sm-empty-state.jpg delete mode 100644 docs/modules/Components-TOREMOVE.md create mode 100644 docs/modules/I18n.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index f5d1fc5bc..6a834d00e 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -89,6 +89,7 @@ module.exports = { ['/modules/Commands', 'Commands'], ['/modules/Components', 'Components'], ['/modules/Components-js', 'Components & JS'], + ['/modules/I18n', 'I18n'], ['/modules/Traits', 'Traits'], ['/modules/Style-manager', 'Style Manager'], ['/modules/Storage', 'Storage Manager'], diff --git a/docs/.vuepress/public/margin-strings.jpg b/docs/.vuepress/public/margin-strings.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27fb660a250f590a7755aa668862df83cc4cd56d GIT binary patch literal 9109 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 - - bar - -``` - -For each DOM element (`div`, `img`, `span`, etc.) the editor will create and store an object representation. Every future change to the template will be made on top of this structure, which will then reflect on the canvas. So each object, usually called *Model* (or state/store), will be the source of truth for the template, but what exactly does that mean? - -In more practical example, once the template is rendered on the canvas, if you try to remove one of its elements (eg. by using the browser inspector) and ask the editor to print the HTML (using `editor.getHtml()`) you'll see that the element will still be there. This is because the editor relies on Models and not on the DOM elements inside the canvas. This approach allows us to be extremely flexible on how we generate the final code (from the *Model*) and how to render it inside the canvas (from the *View*). - - - -# Manage Components - -## Component recognition - -But now, how does the editor recognize which Component to bind to the `img` element and what to do with the `span` one? -Each Component inherits, from the base one, a particular static method - -```js -/** - * @param {HTMLElement} el - * @return {Object} - */ -isComponent: function(el) { - ... -} -``` - -This method gives us the possibility to recognize and bind component types to each HTMLElement (div, img, iframe, etc.). Each **HTML string/element** introduced inside the canvas will be processed by `isComponent` of all available types and if it matches, the object represented the type should be returned. The method `isComponent` **is skipped** if you add the component object (`{ type: 'my-custom-type', tagName: 'div', attribute: {...}, ...}`) or declare the type explicitly on the element (`
...
`) - -For example, with the image component this method looks like: - -```js -// Image component -isComponent: function(el) { - if(el.tagName == 'IMG') - return {type: 'image'}; -} -``` - -Let's try with something that might look a little bit tricky. What about a Google Map?!? Google Maps are generally embedded as `iframe`s, but the template can be composed by a lot of different `iframe`s. How can I tell the editor that a particular iframe is actually a Google's Map? Well, you'll have to figure out the right pattern, you have the `HTMLElement` so you can make all the checks you want. In this particular case this pattern is used: - -```js -// Map component -isComponent: function(el) { - if(el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) { - return {type: 'map', src: el.src}; - } -}, -``` - -In addition to `tagName` check, we also used the `src` property, but you can actually override it with your own logic by extending the built-in component. - - - -## Define new Component - -Let's see an example with another HTML element that is not handled by default Component types. What about `input` elements? - -With the default GrapesJS configuration `input`s are treated like any other element; you can move it around, style it, etc. However, we'd like to handle this type of element more specifically. In this case, we have to create a new Component type. - -Let's define few specs for our new *Input* type: - -* Can be dropped only inside `form` elements -* Can't drop other elements inside it -* Can change the type of the input (text, password, email, etc.) -* Can make it required for the form - -To define a new Component type you need to choose from which built-in Component inherit its properties, in our case we just gonna choose the default one. Let's see a complete example of the new type definition - -```js -// Get DomComponents module -var comps = editor.DomComponents; - -// Get the model and the view from the default Component type -var defaultType = comps.getType('default'); -var defaultModel = defaultType.model; -var defaultView = defaultType.view; - -var inputTypes = [ - {value: 'text', name: 'Text'}, - {value: 'email', name: 'Email'}, - {value: 'password', name: 'Password'}, - {value: 'number', name: 'Number'}, -]; - -// The `input` will be the Component type ID -comps.addType('input', { - // Define the Model - model: defaultModel.extend({ - // Extend default properties - defaults: Object.assign({}, defaultModel.prototype.defaults, { - // Can be dropped only inside `form` elements - draggable: 'form, form *', - // Can't drop other elements inside it - droppable: false, - // Traits (Settings) - traits: ['name', 'placeholder', { - // Change the type of the input (text, password, email, etc.) - type: 'select', - label: 'Type', - name: 'type', - options: inputTypes, - },{ - // Can make it required for the form - type: 'checkbox', - label: 'Required', - name: 'required', - }], - }), - }, - // The second argument of .extend are static methods and we'll put inside our - // isComponent() method. As you're putting a new Component type on top of the stack, - // not declaring isComponent() might probably break stuff, especially if you extend - // the default one. - { - isComponent: function(el) { - if(el.tagName == 'INPUT'){ - return {type: 'input'}; - } - }, - }), - - // Define the View - view: defaultType.view, -}); -``` - -The code above is pretty much self-explanatory and as you see a lot of work is basically done on top of the Model properties. -The *View* is just extending the default one, so to cover also this part let's add some random behavior. - -```js -comps.addType('input', { - model: {...}, - view: defaultType.view.extend({ - // Bind events - events: { - // If you want to bind the event to children elements - // 'click .someChildrenClass': 'methodName', - click: 'handleClick', - dblclick: function(){ - alert('Hi!'); - } - }, - - // It doesn't make too much sense this method inside the component - // but it's ok as an example - randomHex: function() { - return '#' + Math.floor(Math.random()*16777216).toString(16); - }, - - handleClick: function(e) { - this.model.set('style', {color: this.randomHex()}); // <- Affects the final HTML code - this.el.style.backgroundColor = this.randomHex(); // <- Doesn't affect the final HTML code - // Tip: updating the model will reflect the changes to the view, so, in this case, - // if you put the model change after the DOM one this will override the backgroundColor - // change made before - }, - - // The render() should return 'this' - render: function () { - // Extend the original render method - defaultType.view.prototype.render.apply(this, arguments); - this.el.placeholder = 'Text here'; // <- Doesn't affect the final HTML code - return this; - }, - }), -}); -``` - -From the example above you can notice few interesting things: how to bind events, how to update directly the DOM and how to update the model. The difference between updating the DOM and the model is that the HTML code (the one you get with `editor.getHtml()`) is generated from the *Model* so updating directly the DOM will not affect it, it's just the change for the canvas. - - - -## Update Component type - -Here an example of how easily you can update/override the component - -```js -var originalMap = comps.getType('map'); - -comps.addType('map', { - model: originalMap.model.extend({ - // Override how the component is rendered to HTML - toHTML: function() { - return '
My Custom Map
'; - }, - }, { - isComponent: function(el) { - // ... new logic for isComponent - }, - }), - view: originalMap.view -}); -``` - -## Improvement over addType - -Now, with the [0.14.50](https://github.com/artf/grapesjs/releases/tag/v0.14.50) release, defining new components or extending them is a bit easier (without breaking the old process) - -* If you don't specify the type to extend, the `default` one will be used. In that case, you just -use objects for `model` and `view` -* The `defaults` property, in the `model`, will be merged automatically with defaults of the parent component -* If you use an object in `model` you can specify `isComponent` outside or omit it. In this case, -the `isComponent` is not mandatory but without it means the parser won't be able to identify the component -if not explicitly declared (eg. `
...
`) - -**Before** -```js -const defaultType = comps.getType('default'); - -comps.addType('new-component', { - model: defaultType.model.extend({ - defaults: { - ...defaultType.model.prototype.defaults, - someprop: 'somevalue', - }, - ... - }, { - // Even if it returns false, declaring isComponent is mandatory - isComponent(el) { - return false; - }, - }), - view: defaultType.view.extend({ ... }); -}); -``` - -**After** -```js -comps.addType('new-component', { - // We can even omit isComponent here, as `false` return will be the default behavior - isComponent: el => false, - model: { - defaults: { - someprop: 'somevalue', - }, - ... - }, - view: { ... }; -}); -``` -* If you need to extend some component, you can use `extend` and `extendView` property. -* You can now omit `view` property if you don't need to change it - -**Before** -```js -const originalMap = comps.getType('map'); - -comps.addType('map', { - model: originalMap.model.extend({ - ... - }, { - isComponent(el) { - // ... usually, you'd reuse the same logic - }, - }), - // Even if I do nothing in view, I have to specify it - view: originalMap.view -}); -``` -**After** - -The `map` type is already defined, so it will be used as a base for the model and view. -We can skip `isComponent` if the recognition logic is the same of the extended component. -```js -comps.addType('map', { - model: { ... }, -}); -``` -Extend the `model` and `view` with some other, already defined, components. -```js -comps.addType('map', { - extend: 'other-defined-component', - model: { ... }, // Will extend 'other-defined-component' - view: { ... }, // Will extend 'other-defined-component' - // `isComponent` will be taken from `map` -}); -``` -```js -comps.addType('map', { - extend: 'other-defined-component', - model: { ... }, // Will extend 'other-defined-component' - extendView: 'other-defined-component-2', - view: { ... }, // Will extend 'other-defined-component-2' - // `isComponent` will be taken from `map` -}); -``` - -### Extend parent functions - -When you need to reuse functions, of the parent you're extending, you can avoid writing something like this in any function: -```js -domc.getType('parent-type').model.prototype.init.apply(this, arguments); -``` -by using `extendFn` and `extendFnView` arrays: -```js -domc.addType('new-type', { - extend: 'parent-type', - extendFn: ['init'], // array of model functions to extend - model: { - init() { - // do something; - }, - } -}); -``` -The same would be for the view by using `extendFnView` - - - -## Lifecycle Hooks - -Each component triggers different lifecycle hooks, which allows you to add custom actions at their specific stages. -We can distinguish 2 different types of hooks: **global** and **local**. -You define **local** hooks when you create/extend a component type (usually via some `model`/`view` method) and the reason is to react to an event of that -particular component type. Instead, the **global** one, will be called indistinctly on any component (you listen to them via `editor.on`) and you can make -use of them for a more generic use case or also listen to them inside other components. - -Let's see below the flow of all hooks: - -* **Local hook**: `model.init()` method, executed once the model of the component is initiliazed -* **Global hook**: `component:create` event, called right after `model.init()`. The model is passed as an argument to the callback function. - Es. `editor.on('component:create', model => console.log('created', model))` -* **Local hook**: `view.init()` method, executed once the view of the component is initiliazed -* **Local hook**: `view.onRender()` method, executed once the component is rendered on the canvas -* **Global hook**: `component:mount` event, called right after `view.onRender()`. The model is passed as an argument to the callback function. -* **Local hook**: `model.updated()` method, executes when some property of the model is updated. -* **Global hook**: `component:update` event, called after `model.updated()`. The model is passed as an argument to the callback function. - You can also listen to specific property change via `component:update:{propertyName}` -* **Local hook**: `model.removed()` method, executed when the component is removed. -* **Global hook**: `component:remove` event, called after `model.removed()`. The model is passed as an argument to the callback function. - -Below you can find an example usage of all the hooks - -```js -editor.DomComponents.addType('test-component', { - model: { - defaults: { - testprop: 1, - }, - init() { - console.log('Local hook: model.init'); - this.listenTo(this, 'change:testprop', this.handlePropChange); - // Here we can listen global hooks with editor.on('...') - }, - updated(property, value, prevValue) { - console.log('Local hook: model.updated', - 'property', property, 'value', value, 'prevValue', prevValue); - }, - removed() { - console.log('Local hook: model.removed'); - }, - handlePropChange() { - console.log('The value of testprop', this.get('testprop')); - } - }, - view: { - init() { - console.log('Local hook: view.init'); - }, - onRender() { - console.log('Local hook: view.onRender'); - }, - }, -}); - -// A block for the custom component -editor.BlockManager.add('test-component', { - label: 'Test Component', - content: '
Test Component
', -}); - -// Global hooks -editor.on(`component:create`, model => console.log('Global hook: component:create', model.get('type'))); -editor.on(`component:mount`, model => console.log('Global hook: component:mount', model.get('type'))); -editor.on(`component:update:testprop`, model => console.log('Global hook: component:update:testprop', model.get('type'))); -editor.on(`component:remove`, model => console.log('Global hook: component:remove', model.get('type'))); -``` - - - - - -## Components & JS - -If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page -[Components & JS](Components-js.html) - - - - -## Hints - -```html -
- ... - - ... -
- - -``` - -In the example above the editor will not get the new type from the HTML because the content is already parsed and appended, so it'll get it only with new components (eg. from Blocks) - -Solution 1: turn off `autorender` - -```html - -``` -Solution 2: put all the stuff inside a plugin ([Creating plugins](Plugins.html)) - diff --git a/docs/modules/I18n.md b/docs/modules/I18n.md new file mode 100644 index 000000000..c1e56dfba --- /dev/null +++ b/docs/modules/I18n.md @@ -0,0 +1,142 @@ +--- +title: I18n (Internalization) +--- + +# Internalization + +The **I18n** module allows the internalization and updates of strings in the editor UI + +::: warning +This guide is referring to GrapesJS v0.15.9 or higher + +The module was added recently so we're open to receive help in translating strings in other languages. Your help will be much appreciated! +::: + +[[toc]] + + + +## Configuration + +By default, the editor includes only the English language, if you need other languages you have to import them manually. +**Note**: The language code is defined in the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) standard. + +```js +import grapesjs from 'grapesjs'; +import en from 'grapesjs/locale/en'; +import it from 'grapesjs/locale/it'; + +const editor = grapesjs.init({ + ... + i18n: { + // locale: 'en', // default locale + // detectLocale: true, // by default, the editor will detect the language + // localeFallback: 'en', // default fallback + messages: { en, it }, + } +}); +``` + +Now the editor will be translated in Italian for those browsers which default language is Italian (by default `detectLocale` option is enabled) + + + +## Update strings + +If you need to change some default language strings you can easily update them by using [I18n API](/api/i18n.html). +To find the correth path of the string you can check the [`en` locale file](https://github.com/artf/grapesjs/blob/master/src/i18n/locale/en.js) and follow its inner path inside the locale object. + +Let's say we want to update the default message of the empty state in Style Manager when no elements are selected. + + + +From the `en` locale file you can see it by following the path below + +```js +{ + ... + styleManager: { + empty: 'Select an element before using Style Manager', + ... + }, + ... +} +``` + +So now to update it you'll do this + +```js +editor.I18n.addMessages({ + en: { // indicate the locale to update + styleManager: { + empty: 'New empty state message', + } + } +}); +``` + +Even if the UI shows correctly the updated message, we highly suggest to do all the API calls wrapped in a [plugin](Plugins.html) + +```js +const myPlugin = editor => { + editor.I18n.addMessages({ ... }); + // ... +} + +grapesjs.init({ + // ... + plugins: [myPlugin], +}); +``` + +### Generated strings + +Not all the strings are indicated in the `en` local file as some of them can be generated from `id`s, `name`s, etc. +If you look back at the `styleManager` path from the `en` file you'll notice the empty `properties` key + +```js +... +styleManager: { + ... + properties: { + // float: 'Float', + }, + ... +}, +... +``` + +This object is used to translate property names inside StyleManager, so if you need, for instance, to change the auto-generated names for the `margin` properties + + + +you'd this + +```js +editor.I18n.addMessages({ + en: { + styleManager: { + properties: { + // The key is the property name (or id) + 'margin-top': 'Top', + 'margin-right': 'Right', + 'margin-left': 'Left', + 'margin-bottom': 'Bottom', + }, + } + } +}); +``` + + + + +## Plugin development + +If you're developing a plugin for GrapesJS and you need to support some string localization or simply...