diff --git a/packages/language-select/.gitignore b/packages/language-select/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e62f92aa809c7345cb05914d7143b0322ab6d725 --- /dev/null +++ b/packages/language-select/.gitignore @@ -0,0 +1,5 @@ +dist +node_modules +.idea +npm-debug.log +package-lock.json diff --git a/packages/language-select/.gitlab-ci.yml b/packages/language-select/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..9cadb871dc906edde0c952330b3a14f0b5767a58 --- /dev/null +++ b/packages/language-select/.gitlab-ci.yml @@ -0,0 +1,20 @@ +image: debian:buster + +before_script: + - apt update + - apt install -y git + - "sed -i 's|git@gitlab.tugraz.at:VPU|../..|g' .gitmodules" + - git submodule sync + - git submodule update --init + +stages: + - test + +test: + stage: test + script: + - apt update + - apt install -y npm chromium + - npm install + - npm run build + - npm test diff --git a/packages/language-select/README.md b/packages/language-select/README.md new file mode 100644 index 0000000000000000000000000000000000000000..06e898d21ea5bb320dec8d327bdfed1e7db225cb --- /dev/null +++ b/packages/language-select/README.md @@ -0,0 +1,21 @@ +## VPU Language Select Web Component + +[GitLab Repository](https://gitlab.tugraz.at/VPU/WebComponents/LanguageSelect) + +## Local development + +```bash +# get the source +git clone git@gitlab.tugraz.at:VPU/WebComponents/LanguageSelect.git +cd LanguageSelect +git submodule update --init + +# install dependencies (make sure you have npm version 4+ installed, so symlinks to the git submodules are created automatically) +npm install + +# constantly build dist/bundle.js and run a local web-server on port 8002 +npm run watch-local + +# run tests +npm test +``` diff --git a/packages/language-select/assets/index.html b/packages/language-select/assets/index.html new file mode 100644 index 0000000000000000000000000000000000000000..41d0d5e54aa7fcb16545bd7df93b05118db5b3b9 --- /dev/null +++ b/packages/language-select/assets/index.html @@ -0,0 +1,21 @@ +<!doctype html> +<html> +<head> + <meta charset="UTF-8"> + <script type="module" src="bundle.js"></script> + <style> + body { + font-family: sans; + } + </style> +</head> + +<body> + <vpu-language-select></vpu-language-select> + + <br> + <br> + <br> + Current language: <vpu-language-select-demo></vpu-language-select-demo> +</body> +</html> diff --git a/packages/language-select/i18next-scanner.config.js b/packages/language-select/i18next-scanner.config.js new file mode 100644 index 0000000000000000000000000000000000000000..8c277798414be902846c6eb4f0e95a19750cda39 --- /dev/null +++ b/packages/language-select/i18next-scanner.config.js @@ -0,0 +1,15 @@ +module.exports = { + input: [ + 'src/*.js', + ], + output: './', + options: { + debug: false, + removeUnusedKeys: true, + lngs: ['en','de'], + resource: { + loadPath: 'src/i18n/{{lng}}/{{ns}}.json', + savePath: 'src/i18n/{{lng}}/{{ns}}.json' + }, + }, +} diff --git a/packages/language-select/karma.conf.js b/packages/language-select/karma.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..901e07806c448e53fd84367917e67267a1836771 --- /dev/null +++ b/packages/language-select/karma.conf.js @@ -0,0 +1,23 @@ +// Trick to use the auto-downloaded puppeteer chrome binary +process.env.CHROME_BIN = require('puppeteer').executablePath(); + +module.exports = function(config) { + config.set({ + basePath: 'dist', + frameworks: ['mocha', 'chai'], + files: [ + './bundle.js', + {pattern: './**/*', included: false, watched: true, served: true}, + ], + autoWatch: true, + browsers: ['ChromeHeadlessNoSandbox'], + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: 'ChromeHeadless', + flags: ['--no-sandbox'] + } + }, + singleRun: false, + logLevel: config.LOG_ERROR + }); +} diff --git a/packages/language-select/package.json b/packages/language-select/package.json new file mode 100644 index 0000000000000000000000000000000000000000..902340abe030731dfdf4cb3ede0ce9dbecc71874 --- /dev/null +++ b/packages/language-select/package.json @@ -0,0 +1,43 @@ +{ + "name": "vpu-person-select", + "version": "1.0.0", + "main": "src/index.js", + "devDependencies": { + "karma": "^4.2.0", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^3.0.0", + "karma-mocha": "^1.3.0", + "node-sass": "^4.12.0", + "puppeteer": "^1.15.0", + "mocha": "^6.2.0", + "chai": "^4.2.0", + "rollup": "^1.11.3", + "rollup-plugin-commonjs": "^9.3.4", + "rollup-plugin-copy": "^2.0.1", + "rollup-plugin-node-resolve": "^4.2.3", + "rollup-plugin-serve": "^1.0.1", + "rollup-plugin-terser": "^4.0.4", + "rollup-plugin-json": "^4.0.0", + "rollup-plugin-replace": "^2.2.0", + "rollup-plugin-multi-entry": "^2.1.0", + "i18next-scanner": "^2.10.2" + }, + "dependencies": { + "i18next": "^17.0.3", + "lit-element": "^2.1.0" + }, + "scripts": { + "clean": "rm dist/*", + "build": "npm run build-local", + "build-local": "rollup -c", + "build-dev": "rollup -c --environment BUILD:development", + "build-prod": "rollup -c --environment BUILD:production", + "build-demo": "rollup -c --environment BUILD:demo", + "build-test": "rollup -c --environment BUILD:test", + "i18next": "i18next-scanner", + "watch": "npm run watch-local", + "watch-local": "rollup -c --watch", + "watch-dev": "rollup -c --watch --environment BUILD:development", + "test": "npm run build-test && karma start --singleRun" + } +} diff --git a/packages/language-select/rollup.config.js b/packages/language-select/rollup.config.js new file mode 100644 index 0000000000000000000000000000000000000000..b02426e5acfffdbee6e2df9b8fdb731149944687 --- /dev/null +++ b/packages/language-select/rollup.config.js @@ -0,0 +1,36 @@ +import resolve from 'rollup-plugin-node-resolve'; +import commonjs from 'rollup-plugin-commonjs'; +import copy from 'rollup-plugin-copy'; +import {terser} from "rollup-plugin-terser"; +import json from 'rollup-plugin-json'; +import replace from "rollup-plugin-replace"; +import serve from 'rollup-plugin-serve'; +import multiEntry from 'rollup-plugin-multi-entry'; + +const build = (typeof process.env.BUILD !== 'undefined') ? process.env.BUILD : 'local'; +console.log("build: " + build); + +export default { + input: (build != 'test') ? 'src/demo.js' : 'test/**/*.js', + output: { + file: 'dist/bundle.js', + format: 'esm' + }, + plugins: [ + multiEntry(), + resolve(), + commonjs(), + json(), + replace({ + "process.env.BUILD": '"' + build + '"', + }), + (build !== 'local' && build !== 'test') ? terser() : false, + copy({ + targets: [ + 'assets/index.html', + ], + outputFolder: 'dist' + }), + (process.env.ROLLUP_WATCH === 'true') ? serve({contentBase: 'dist', host: '127.0.0.1', port: 8002}) : false + ] +}; diff --git a/packages/language-select/src/demo.js b/packages/language-select/src/demo.js new file mode 100644 index 0000000000000000000000000000000000000000..6926c4fe33017d61d4d65b79d79086ae2499f59a --- /dev/null +++ b/packages/language-select/src/demo.js @@ -0,0 +1,36 @@ +import {html, LitElement} from 'lit-element'; +import './language-select.js'; + +class LanguageSelectDemo extends LitElement { + + constructor() { + super(); + this.lang = 'de'; + } + + static get properties() { + return { + lang: {type: String}, + }; + } + + handleChange(e) { + this.lang = e.detail.lang; + } + + connectedCallback() { + super.connectedCallback(); + window.addEventListener('vpu-language-changed', this.handleChange.bind(this)); + } + + disconnectedCallback() { + windows.removeEventListener('vpu-language-changed', this.handleChange.bind(this)); + super.disconnectedCallback(); + } + + render() { + return html`${this.lang}`; + } +} + +customElements.define('vpu-language-select-demo', LanguageSelectDemo); diff --git a/packages/language-select/src/i18n.js b/packages/language-select/src/i18n.js new file mode 100644 index 0000000000000000000000000000000000000000..a2380632e7095df7cc09dddf372598f5d6b5898c --- /dev/null +++ b/packages/language-select/src/i18n.js @@ -0,0 +1,29 @@ +import i18next from 'i18next'; + +import de from './i18n/de/translation.json'; +import en from './i18n/en/translation.json'; + +const i18n = i18next.createInstance(); + +i18n.init({ + lng: 'de', + fallbackLng: ['de'], + debug: false, + initImmediate: false, // Don't init async + resources: { + en: {translation: en}, + de: {translation: de} + }, +}); + +console.assert(i18n.isInitialized); + +function dateTimeFormat(date, options) { + return new Intl.DateTimeFormat(i18n.languages, options).format(date); +} + +function numberFormat(number, options) { + return new Intl.NumberFormat(i18n.languages, options).format(number); +} + +export {i18n, dateTimeFormat, numberFormat}; diff --git a/packages/language-select/src/i18n/de/translation.json b/packages/language-select/src/i18n/de/translation.json new file mode 100644 index 0000000000000000000000000000000000000000..5ee4f041a1eaf738ff35cb75b10571e8c4767fe2 --- /dev/null +++ b/packages/language-select/src/i18n/de/translation.json @@ -0,0 +1,4 @@ +{ + "de": "Deutsch", + "en": "Englisch" +} diff --git a/packages/language-select/src/i18n/en/translation.json b/packages/language-select/src/i18n/en/translation.json new file mode 100644 index 0000000000000000000000000000000000000000..f8615781a0a69ba9377d94f69937da07af459592 --- /dev/null +++ b/packages/language-select/src/i18n/en/translation.json @@ -0,0 +1,4 @@ +{ + "de": "German", + "en": "English" +} diff --git a/packages/language-select/src/index.js b/packages/language-select/src/index.js new file mode 100644 index 0000000000000000000000000000000000000000..0792a1f13f8ed433cd4c1dc3d5e00338fc70fc24 --- /dev/null +++ b/packages/language-select/src/index.js @@ -0,0 +1 @@ +import './language-select.js'; diff --git a/packages/language-select/src/language-select.js b/packages/language-select/src/language-select.js new file mode 100644 index 0000000000000000000000000000000000000000..02087b71856a4b13e53e62b4c76b962130470421 --- /dev/null +++ b/packages/language-select/src/language-select.js @@ -0,0 +1,87 @@ +import {html, css, LitElement} from 'lit-element'; +import {i18n, dateTimeFormat, numberFormat} from './i18n.js'; + +/** + * Emits a vpu-language-changed event where event.detail.lang is the new selected language + */ +class LanguageSelect extends LitElement { + + constructor() { + super(); + this.lang = 'en'; + this.languages = ['de', 'en']; + + i18n.t('de'); + i18n.t('en'); + } + + static get properties() { + return { + lang: {type: String}, + languages: {type: Array}, + }; + } + + static get styles() { + return css` + select { + background: + linear-gradient(45deg, transparent 50%, black 50%), + linear-gradient(135deg, black 50%, transparent 50%), + linear-gradient(to right, white, white); + background-position: + calc(100% - 21px) calc(1em + 2px), + calc(100% - 16px) calc(1em + 2px), + 100% 0; + background-size: + 5px 5px, + 5px 5px, + 2.5em 2.5em; + background-repeat: no-repeat; + + border: 1px solid #000; + line-height: 1.5em; + padding: 0.5em 3.5em 0.5em 0.5em; + + border-radius: 0; + margin: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-appearance:none; + -moz-appearance:none; + } + `; + } + + updated(changedProperties) { + changedProperties.forEach((oldValue, propName) => { + if (propName === 'lang') { + const event = new CustomEvent("vpu-language-changed", { + bubbles: true, + detail: {'lang': this.lang}, + }); + window.dispatchEvent(event); + + i18n.changeLanguage(this.lang); + this.requestUpdate(); + } + }); + + super.updated(changedProperties); + } + + onSelectChange(e) { + this.lang = e.target.value; + } + + render() { + return html` + <select @change=${this.onSelectChange}> + ${this.languages.map(i => html`<option value="${i}" ?selected=${this.lang === i}>${i18n.t(i)}</option>`)} + </select> + `; + } +} + +customElements.define('vpu-language-select', LanguageSelect); diff --git a/packages/language-select/test/unit.js b/packages/language-select/test/unit.js new file mode 100644 index 0000000000000000000000000000000000000000..37bd3c1ab3184453251ad667e9290712379a1946 --- /dev/null +++ b/packages/language-select/test/unit.js @@ -0,0 +1,19 @@ +import '../src/language-select'; + +describe('vpu-language-select basics', () => { + let node; + + beforeEach(async () => { + node = document.createElement('vpu-language-select'); + document.body.appendChild(node); + await node.updateComplete; + }); + + afterEach(() => { + node.remove(); + }); + + it('should render', () => { + expect(node).to.have.property('shadowRoot'); + }); +});