Skip to content

Commit

Permalink
Merge pull request #132 from lightning-js/dev
Browse files Browse the repository at this point in the history
Release 1.2.0
  • Loading branch information
michielvandergeest authored Aug 5, 2024
2 parents 89dafe2 + 5f9fd9f commit dba3333
Show file tree
Hide file tree
Showing 22 changed files with 554 additions and 31 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## v1.2.0

_5 aug 2024_

- Added plugin system for Blits
- Added Language plugin, modeled after L2-SDK Language plugin
- Removed unused built-in Image component
- Marked `this.trigger()`-method as deprecated in definition file
- Removed double assignment of `this.node` during element creation

## v1.1.0

_25 july 2024_
Expand Down
1 change: 1 addition & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
- [For loop](/built-in/for-loop.md)
- Plugins
- [Text-to-Speech / Announcer](/plugins/text-to-speech-announcer.md)
- [Language](/plugins/language.md)
174 changes: 174 additions & 0 deletions docs/plugins/language.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Blits - Lightning 3 App Development Framework

## Language plugin

Blits comes with a built-in Language plugin, that can be used to add internationalization (i18n) to your App.

The Blits Language plugin is a simple and lightweight implementation, closely modeled after the tried and tested Language plugin in the Lightning-SDK (for Lightning 2). It is designed to provide a flexible and easy way of translating texts to multiple languages, in a performant way.

### Registering the plugin

While the Language plugin is provided in the core Blits package, it's an _optional_ plugin that needs to be registered before you're able to use it. As such the Language plugin is _tree-shakable_, i.e. for Apps that don't have a need for translation functionality, the Language plugin will not be part of the final App bundle.

The Language plugin should be imported from Blits and registered in the App's `index.js`, as demonstrated in the example below.

Make sure to place the `Blits.Plugin()` method _before_ calling the `Blits.Launch()` method

```js
// index.js

import Blits from '@lightningjs/blits'
// import the language plugin
import { language } from '@lightningjs/blits/plugins'

import App from './App.js'

// Use the Blits Language plugin and
// optionally pass in a settings object
Blits.Plugin(language, {
file: '/assets/translations.json', // file with translations
language: 'EN-us', // default language
})


Blits.Launch(App, 'app', {
// launch settings
})
```

The Language plugin accepts an optional configuration object with 2 keys:

- `file` - the JSON file with translations to be loaded at initialization
- `language` - the default language to use

After registration of the Language plugin, it will be availabe in each Blits Component as `this.$language`.

### Translations file

The most common way of defining a set of translations, is to use a dedicated JSON file, modeled after
the following format:

```json
{
"nl-NL": {
"hello": "Hallo, alles goed?",
"bye": "Doei!",
"replacement": "Mijn naam is {first} {last}, ik ben {age} jaar"
},
"en-US": {
"hello": "Hello, how are you doing?",
"bye": "Goodbye!",
"replacement": "My name is {first} {last}, I'm {age} years old"
},
"fr-FR": {
"hello": "Bonjour, ça va?",
"bye": "Au revoir!",
"replacement": "Je m'appelle {first} {last}, j'ai {age} ans"
},
"de-DE": {
"hello": "Gutentag, wie geht's?",
"bye": "Tschüss!",
"replacement": "Mein Name ist {first} {last}, ich bin {age} Jahre alt"
}
}
```

Each top level key represents a language, which contains an object with translation key-value pairs. The language key
doesn't require to be in a specific format, but it's recommended to use ISO-codes.

When the JSON file is specified in the Plugin registration method, the language file is loaded automatically upon instantiation.

In case you want to load the file with translations manually, you can use the `this.$language.load()`-method anywhere in a Component and pass in the path to the JSON file as the first argument.

### Defining translations manualy

As an alternative to a JSON file with translations, you can also define translations directly via an object, using the `translations()`-method.

The `this.$language.translations()`-methods accepts a translations object as it's first argument, following the same format as the JSON file. The method is typically used in an `init` or `ready` hook

```js
Blits.Component('MyComponent', {
// ...
hooks: {
init() {
this.$language.translations({
english: {
hello: "Hello",
world: "World"
},
italian: {
hello: "Ciao",
world: "Mondo"
}
})
}
},
})
```

### Setting the language

In order to set (or change) the current language, the `this.$language.set()`-method can be used, passing in the new language code (matching one of the languages defined in the translations).

```js
Blits.Component('MyComponent', {
// ...
input: {
enter() {
this.$language.set('pt-BR')
}
},
})
```

### Getting the current language

The current language can be retrieved via the `this.$language.get()` method. It will return the currently set language code.

### Translating

The most important functionality in the Language plugin is translating. The Language plugin exposes a `translate()` method that takes as string to translate as it's first argument. It will return the translated value in the currently set language. If either the language or the string to translate can't be found, it will return the inputed string.

The translate method can be used both inside the javascript business logic, but also directly in the template.

```js
Blits.Component('MyComponent', {
template: `
<Element>
<Text :content="$$language.translate('hello')" />
<Text y="100" :content="$$language.get()" />
</Element>
`,
hooks: {
ready() {
console.log(this.$language.translate('world'))
}
},
})
```

Note that in the template definition 2 consecutive dollars signs are used (`$$language`). The first `$`-sign is used
to refer to the Component scope, as with any other state variable or computed property refereced in the template.

The second `$`-sign is needed, since the Language plugin itself is prefixed with a dollar sign on the Component scope as well (i.e `this.$language`).


#### Dynamic replacements

The `tranlate()`-method also supports dynamic replacements. This allows you to specify variables inside your translation string, and replace them with dynamic values passed into the `translate()`-method.

Replacements are marked in the translation value using _handlebars_, like so:

```js
{
replacements = "My name is {first} {last}, I'm {age} years old"
}
```

Now when you call the `translate()`-method and pass in an object with dynamic values, those will be replaced
in the final string accordingly:

```js
this.$language.translate('replacements', {name: 'John', last: 'Doe', age: 18})
// result: My name is John Doe, I'm 18 years old
```
4 changes: 4 additions & 0 deletions docs/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@
{
"text": "Text-to-Speech / Announcer",
"link": "/plugins/text-to-speech-announcer"
},
{
"text": "Language",
"link": "/plugins/language"
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import Component from './src/component.js'
import Application from './src/application.js'
import Launch from './src/launch.js'
import Plugin from './src/plugin.js'

/**
* Blits - The Lightning 3 App Development Framework
Expand All @@ -26,6 +27,7 @@ declare module Blits {
export { Component }
export { Application }
export { Launch }
export { Plugin }
}

export default Blits
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
import Component from './src/component.js'
import Application from './src/application.js'
import Launch from './src/launch.js'
import Plugin from './src/plugin.js'

export default {
Component,
Application,
Launch,
Plugin,
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "@lightningjs/blits",
"version": "1.1.0",
"version": "1.2.0",
"description": "Blits: The Lightning 3 App Development Framework",
"bin": "bin/index.js",
"exports": {
".": "./index.js",
"./vite": "./vite/index.js",
"./transitions": "./src/router/transitions/index.js",
"./precompiler": "./src/lib/precompiler/precompiler.js"
"./precompiler": "./src/lib/precompiler/precompiler.js",
"./plugins": "./src/plugins/index.js"
},
"scripts": {
"test": "c8 npm run test:run",
Expand Down
6 changes: 6 additions & 0 deletions src/component.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,12 @@ declare namespace Component {
* Triggers a forced update on state variables.
*/
$trigger: (key: string) => void
/**
* @deprecated
*
* Triggers a forced update on state variables.
* Deprecated: use `this.$trigger()` instead
*/
trigger: (key: string) => void

/**
Expand Down
27 changes: 26 additions & 1 deletion src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import Settings from './settings.js'
import setupComponent from './component/setup/index.js'
import components from './components/index.js'

import { plugins } from './plugin.js'

// object to store global components
let globalComponents

Expand Down Expand Up @@ -202,7 +204,30 @@ const Component = (name = required('name'), config = required('config')) => {
}

const factory = (options = {}, parentEl, parentComponent, rootComponent) => {
// setup the component once, using Base as the prototype
// Register user defined plugins once on the Base object
if (Base[symbols['pluginsRegistered']] === false) {
Object.keys(plugins).forEach((pluginName) => {
const prefixedPluginName = `$${pluginName}`
if (prefixedPluginName in Base) {
Log.warn(
`"${pluginName}" (this.${prefixedPluginName}) already exists as a property or plugin on the Base Component. You may be overwriting built-in functionality. Proceed with care!`
)
}

const plugin = plugins[pluginName]

Object.defineProperty(Base, prefixedPluginName, {
// instantiate the plugin, passing in provided options
value: plugin.plugin(plugin.options),
writable: false,
enumerable: true,
configurable: false,
})
})
Base[symbols['pluginsRegistered']] = true
}

// setup the component once per component type, using Base as the prototype
if (!base) {
Log.debug(`Setting up ${name} component`)
base = setupComponent(Object.create(Base), config)
Expand Down
5 changes: 4 additions & 1 deletion src/component/base/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ import log from './log.js'
import router from './router.js'
import announcer from './announcer.js'
import utils from './utils.js'
import symbols from '../../lib/symbols.js'

export default Object.defineProperties(
{},
{
[symbols['pluginsRegistered']]: false,
},
{ ...methods, ...scheduling, ...events, ...log, ...router, ...announcer, ...utils }
)
2 changes: 1 addition & 1 deletion src/component/base/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default {
$log: {
value: log('App'),
writable: false,
enumerable: false,
enumerable: true,
configurable: false,
},
}
2 changes: 0 additions & 2 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@
* SPDX-License-Identifier: Apache-2.0
*/

import Image from './Image.js'
import Circle from './Circle.js'
import RouterView from './RouterView.js'
import Sprite from './Sprite.js'
import FPScounter from './FPScounter.js'
import Layout from './Layout.js'

export default () => ({
Image: Image(),
Circle: Circle(),
RouterView: RouterView(),
Sprite: Sprite(),
Expand Down
2 changes: 1 addition & 1 deletion src/engines/L3/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ const Element = {

this.node = props.__textnode
? renderer.createTextNode({ ...textDefaults, ...this.props.props })
: (this.node = renderer.createNode(this.props.props))
: renderer.createNode(this.props.props)

if (props['@loaded']) {
this.node.on('loaded', (el, { type, dimensions }) => {
Expand Down
Loading

0 comments on commit dba3333

Please sign in to comment.