diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..481dd10ff09fc0e7ac3416064d93fd74c50bed54
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "external/lang"]
+	path = external/lang
+	url = https://gitlab.insrt.uk/revolt/translations
diff --git a/external/lang b/external/lang
new file mode 160000
index 0000000000000000000000000000000000000000..0334783257c0b1cd47ee9affdc1a0dddc127edf2
--- /dev/null
+++ b/external/lang
@@ -0,0 +1 @@
+Subproject commit 0334783257c0b1cd47ee9affdc1a0dddc127edf2
diff --git a/package.json b/package.json
index 96d4b973defc76609d2aa596a0820306a048aac6..d64671a405dc62edfeb34df90378a916b43dda3e 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,9 @@
     "@preact/preset-vite": "^2.0.0",
     "@styled-icons/bootstrap": "^10.34.0",
     "@styled-icons/feather": "^10.34.0",
+    "@types/preact-i18n": "^2.3.0",
     "@types/styled-components": "^5.1.10",
-    "preact-i18n": "^1.5.0",
+    "preact-i18n": "^2.4.0-preactx",
     "react-overlapping-panels": "1.1.2-patch.0",
     "rimraf": "^3.0.2",
     "sass": "^1.35.1",
diff --git a/src/app.tsx b/src/app.tsx
index 5b9d0496ad17aef8fd8baee8909f06f62d3d9566..c16cf4ca3884913d50fbce2b5ee499ef52ac584f 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,15 +1,17 @@
 import styled, { createGlobalStyle } from 'styled-components';
 import { useState } from 'preact/hooks';
 
-import { Button } from './components/ui/Button';
-import { Banner } from './components/ui/Banner';
-import { Checkbox } from './components/ui/Checkbox';
-import { ComboBox } from './components/ui/ComboBox';
-import { InputBox } from './components/ui/InputBox';
-import { ColourSwatches } from './components/ui/ColourSwatches';
-import { Tip } from './components/ui/Tip';
-import { Radio } from './components/ui/Radio';
-import { Overline } from './components/ui/Overline';
+import Button from './components/ui/Button';
+import Banner from './components/ui/Banner';
+import Checkbox from './components/ui/Checkbox';
+import ComboBox from './components/ui/ComboBox';
+import InputBox from './components/ui/InputBox';
+import ColourSwatches from './components/ui/ColourSwatches';
+import Tip from './components/ui/Tip';
+import Radio from './components/ui/Radio';
+import Overline from './components/ui/Overline';
+
+import Locale from './context/Locale';
 
 // ! TEMP START
 let a = {"light":false,"accent":"#FD6671","background":"#191919","foreground":"#F6F6F6","block":"#2D2D2D","message-box":"#363636","mention":"rgba(251, 255, 0, 0.06)","success":"#65E572","warning":"#FAA352","error":"#F06464","hover":"rgba(0, 0, 0, 0.1)","sidebar-active":"#FD6671","scrollbar-thumb":"#CA525A","scrollbar-track":"transparent","primary-background":"#242424","primary-header":"#363636","secondary-background":"#1E1E1E","secondary-foreground":"#C8C8C8","secondary-header":"#2D2D2D","tertiary-background":"#4D4D4D","tertiary-foreground":"#848484","status-online":"#3ABF7E","status-away":"#F39F00","status-busy":"#F84848","status-streaming":"#977EFF","status-invisible":"#A5A5A5"};
@@ -42,34 +44,36 @@ export function App() {
 	return (
 		<>
 			<GlobalTheme />
-			<UIDemo>
-				<Button>Button (normal)</Button>
-				<Button contrast>Button (contrast)</Button>
-				<Button error>Button (error)</Button>
-				<Button contrast error>Button (contrast + error)</Button>
-				<Banner>I am a banner!</Banner>
-				<Checkbox checked={checked} onChange={setChecked} description="ok gamer">Do you want thing??</Checkbox>
-				<ComboBox>
-					<option>Select an option.</option>
-					<option>1</option>
-					<option>2</option>
-					<option>3</option>
-				</ComboBox>
-				<InputBox placeholder="Normal input box..." />
-				<InputBox placeholder="Contrast input box..." contrast />
-				<InputBox value="Input box with value" />
-				<InputBox value="Contrast with value" contrast />
-				<ColourSwatches value={colour} onChange={v => setColour(v)} />
-				<Tip>I am a tip! I provide valuable information.</Tip>
-				<Radio checked={selected === 'a'} onSelect={() => setSelected('a')}>First option</Radio>
-				<Radio checked={selected === 'b'} onSelect={() => setSelected('b')}>Second option</Radio>
-				<Radio checked={selected === 'c'} onSelect={() => setSelected('c')}>Last option</Radio>
-				<Overline>Normal overline</Overline>
-				<Overline type="subtle">Subtle overline</Overline>
-				<Overline type="error">Error overline</Overline>
-				<Overline error="with error">Normal overline</Overline>
-				<Overline type="subtle" error="with error">Subtle overline</Overline>
-			</UIDemo>
+			<Locale>
+				<UIDemo>
+					<Button>Button (normal)</Button>
+					<Button contrast>Button (contrast)</Button>
+					<Button error>Button (error)</Button>
+					<Button contrast error>Button (contrast + error)</Button>
+					<Banner>I am a banner!</Banner>
+					<Checkbox checked={checked} onChange={setChecked} description="ok gamer">Do you want thing??</Checkbox>
+					<ComboBox>
+						<option>Select an option.</option>
+						<option>1</option>
+						<option>2</option>
+						<option>3</option>
+					</ComboBox>
+					<InputBox placeholder="Normal input box..." />
+					<InputBox placeholder="Contrast input box..." contrast />
+					<InputBox value="Input box with value" />
+					<InputBox value="Contrast with value" contrast />
+					<ColourSwatches value={colour} onChange={v => setColour(v)} />
+					<Tip>I am a tip! I provide valuable information.</Tip>
+					<Radio checked={selected === 'a'} onSelect={() => setSelected('a')}>First option</Radio>
+					<Radio checked={selected === 'b'} onSelect={() => setSelected('b')}>Second option</Radio>
+					<Radio checked={selected === 'c'} onSelect={() => setSelected('c')}>Last option</Radio>
+					<Overline>Normal overline</Overline>
+					<Overline type="subtle">Subtle overline</Overline>
+					<Overline type="error">Error overline</Overline>
+					<Overline error="with error">Normal overline</Overline>
+					<Overline type="subtle" error="with error">Subtle overline</Overline>
+				</UIDemo>
+			</Locale>
 		</>
 	)
 }
diff --git a/src/components/ui/Banner.tsx b/src/components/ui/Banner.tsx
index 15c0d5caa5a1bcce9a85c762d5f08a308cb01843..04bc326ff150f3983c8f710c35b1f4ec225290cd 100644
--- a/src/components/ui/Banner.tsx
+++ b/src/components/ui/Banner.tsx
@@ -1,6 +1,6 @@
 import styled from "styled-components";
 
-export const Banner = styled.div`
+export default styled.div`
 	padding: 8px;
     font-size: 14px;
     text-align: center;
diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx
index 3952e3541197ea4a47060e97c75be274da2b61e6..e4d949e0d6792905d2a645128c327bdafd1b006b 100644
--- a/src/components/ui/Button.tsx
+++ b/src/components/ui/Button.tsx
@@ -5,7 +5,7 @@ interface Props {
     readonly error?: boolean;
 };
 
-export const Button = styled.button<Props>`
+export default styled.button<Props>`
 	z-index: 1;
 	padding: 8px;
 	font-size: 16px;
diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx
index bccbefb12a5d033796790faf38e1a6cfe8c88ea6..ae837d1b35d0f413054a937929a844871e4aa3bf 100644
--- a/src/components/ui/Checkbox.tsx
+++ b/src/components/ui/Checkbox.tsx
@@ -67,7 +67,7 @@ interface Props {
     onChange: (state: boolean) => void;
 }
 
-export function Checkbox(props: Props) {
+export default function Checkbox(props: Props) {
     return (
         <CheckboxBase disabled={props.disabled}>
             <CheckboxContent>
diff --git a/src/components/ui/ColourSwatches.tsx b/src/components/ui/ColourSwatches.tsx
index 4c33340b24c279e13e04acb147439d4c63956ce7..71f71264941b73b1d8ce516eb871e14fb488f517 100644
--- a/src/components/ui/ColourSwatches.tsx
+++ b/src/components/ui/ColourSwatches.tsx
@@ -86,7 +86,7 @@ const Rows = styled.div`
     }
 `;
 
-export function ColourSwatches({ value, onChange }: Props) {
+export default function ColourSwatches({ value, onChange }: Props) {
     const ref = useRef<HTMLInputElement>();
 
     return (
diff --git a/src/components/ui/ComboBox.tsx b/src/components/ui/ComboBox.tsx
index 7594414200d6bb94c31a7e1f3209abb529054c12..db328c8fda6aab1e477004ab930bb994dfc91519 100644
--- a/src/components/ui/ComboBox.tsx
+++ b/src/components/ui/ComboBox.tsx
@@ -1,6 +1,6 @@
 import styled from "styled-components";
 
-export const ComboBox = styled.select`
+export default styled.select`
 	padding: 8px;
     border-radius: 2px;
     color: var(--secondary-foreground);
diff --git a/src/components/ui/InputBox.tsx b/src/components/ui/InputBox.tsx
index cf8ab6376ae2dc3da61f3bbb85e3b105f3e58124..d5c0d9c7b344a377d1bb0ee04e13bbea0159fa42 100644
--- a/src/components/ui/InputBox.tsx
+++ b/src/components/ui/InputBox.tsx
@@ -4,7 +4,7 @@ interface Props {
     readonly contrast?: boolean;
 };
 
-export const InputBox = styled.input<Props>`
+export default styled.input<Props>`
     z-index: 1;
     padding: 8px 16px;
     border-radius: 6px;
diff --git a/src/components/ui/LineDivider.tsx b/src/components/ui/LineDivider.tsx
index 93fbcdfc73167415fcca567632f8da15dde80157..49ba646139e12c0cd81e6c9465c9138e2e2da934 100644
--- a/src/components/ui/LineDivider.tsx
+++ b/src/components/ui/LineDivider.tsx
@@ -1,6 +1,6 @@
 import styled from 'styled-components';
 
-export const LineDivider = styled.div`
+export default styled.div`
     height: 0px;
     opacity: 0.6;
     flex-shrink: 0;
diff --git a/src/components/ui/Overline.tsx b/src/components/ui/Overline.tsx
index 4aaef59f837f4324c368eea6e7cf357d8f240214..f2db34f5f1ea856e70de2bdb14a85244feb91aa2 100644
--- a/src/components/ui/Overline.tsx
+++ b/src/components/ui/Overline.tsx
@@ -32,7 +32,7 @@ const OverlineBase = styled.div<Omit<Props, 'children' | 'error'>>`
     ${ props => props.block && css`display: block;` }
 `;
 
-export function Overline(props: Props) {
+export default function Overline(props: Props) {
     return (
         <OverlineBase {...props}>
             { props.children }
diff --git a/src/components/ui/Preloader.tsx b/src/components/ui/Preloader.tsx
index 79cd39c59cdbf1a2bd3688be5e68fa28386b4bd4..217d5b4c830eb25ff752ab230c4db8d527c0824d 100644
--- a/src/components/ui/Preloader.tsx
+++ b/src/components/ui/Preloader.tsx
@@ -1,3 +1,3 @@
-export function Preloader() {
+export default function Preloader() {
     return <span>LOADING</span>
 }
diff --git a/src/components/ui/Radio.tsx b/src/components/ui/Radio.tsx
index 0ca1828d869d434b43c6c8ea5c81a080ee229baf..b6cc99269547d1b03a13315eb5e06852da528036 100644
--- a/src/components/ui/Radio.tsx
+++ b/src/components/ui/Radio.tsx
@@ -81,7 +81,7 @@ const RadioDescription = styled.span<BaseProps>`
     ` }
 `;
 
-export function Radio(props: Props) {
+export default function Radio(props: Props) {
     return (
         <RadioBase
             selected={props.checked}
diff --git a/src/components/ui/Tip.tsx b/src/components/ui/Tip.tsx
index cc4a1da019b1af2d6fe425f54abe708ebf2d97a0..ed2bb95388794dcc1fbc823018a770e30632dc58 100644
--- a/src/components/ui/Tip.tsx
+++ b/src/components/ui/Tip.tsx
@@ -26,7 +26,7 @@ export const TipBase = styled.div`
     }
 `;
 
-export function Tip(props: { children: Children }) {
+export default function Tip(props: { children: Children }) {
     return (
         <TipBase>
             <Info size={20} strokeWidth={2} />
diff --git a/src/context/Locale.tsx b/src/context/Locale.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6ff7155e7f01ac48f91339719a2da728e6178a97
--- /dev/null
+++ b/src/context/Locale.tsx
@@ -0,0 +1,14 @@
+import { IntlProvider } from "preact-i18n";
+import definition from "../../external/lang/en.json";
+
+interface Props {
+    children: JSX.Element | JSX.Element[]
+}
+
+export default function Locale({ children }: Props) {
+    return (
+        <IntlProvider definition={definition}>
+            { children }
+        </IntlProvider>
+    )
+}
diff --git a/yarn.lock b/yarn.lock
index 85009143f473799b1ea519f68cf3f97648e23472..f68abfd6d86cba6d7216f56fb3a366a748d46108 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -318,6 +318,13 @@
     "@types/react" "*"
     hoist-non-react-statics "^3.3.0"
 
+"@types/preact-i18n@^2.3.0":
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/@types/preact-i18n/-/preact-i18n-2.3.0.tgz#d99d4a9ad03b0b65e57ed4d874447de74384e32f"
+  integrity sha512-qDgb5QbPnWJ141y+fca5R3MBQis5h7ITnSB9WQiHj5WH41Q5g9Wc4rCnqYERfqSBSC0ac4cE1JAlFisiAUIiLw==
+  dependencies:
+    preact "^10.0.0"
+
 "@types/prop-types@*":
   version "15.7.3"
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
@@ -745,14 +752,20 @@ postcss@^8.3.0:
     nanoid "^3.1.23"
     source-map-js "^0.6.2"
 
-preact-i18n@^1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/preact-i18n/-/preact-i18n-1.5.0.tgz#f9e2ad29cb3abc8b810554963cc90f1a52242605"
-  integrity sha512-gOSyXag3uP1ISSvpcvfP8rdONeLxtQWCEfEZyt+REnGcBUzEWwf7/tjBVZp/ymZeTv4mGh9sALd1NqPYyICkRA==
+preact-i18n@^2.4.0-preactx:
+  version "2.4.0-preactx"
+  resolved "https://registry.yarnpkg.com/preact-i18n/-/preact-i18n-2.4.0-preactx.tgz#fbcb2e3ae22744c7fef5a102db2ef7506057d082"
+  integrity sha512-XJ15wZKdJrpuz2KBs8BkDB9gl85MjIkER5tx8r6RM1+j53hw3/XzUq2DMnkMoLVQiS0VSPI6bXk41CioKwTJIA==
   dependencies:
     dlv "^1.1.3"
+    preact-markup "^2.0.0"
+
+preact-markup@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/preact-markup/-/preact-markup-2.1.1.tgz#0451e7eed1dac732d7194c34a7f16ff45a2cfdd7"
+  integrity sha512-8JL2p36mzK8XkspOyhBxUSPjYwMxDM0L5BWBZWxsZMVW8WsGQrYQDgVuDKkRspt2hwrle+Cxr/053hpc9BJwfw==
 
-preact@^10.5.13:
+preact@^10.0.0, preact@^10.5.13:
   version "10.5.13"
   resolved "https://registry.yarnpkg.com/preact/-/preact-10.5.13.tgz#85f6c9197ecd736ce8e3bec044d08fd1330fa019"
   integrity sha512-q/vlKIGNwzTLu+jCcvywgGrt+H/1P/oIRSD6mV4ln3hmlC+Aa34C7yfPI4+5bzW8pONyVXYS7SvXosy2dKKtWQ==