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');
+  });
+});