diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..da0415a0f5aad2f6109ef1e7ffdcdd1165577665
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8" />
+</project>
\ No newline at end of file
diff --git a/.idea/fileTemplates/code/I18nized Concatenation.java b/.idea/fileTemplates/code/I18nized Concatenation.java
new file mode 100644
index 0000000000000000000000000000000000000000..57d92f8845fe0dcac49dd584c585e4747ffdf9e1
--- /dev/null
+++ b/.idea/fileTemplates/code/I18nized Concatenation.java	
@@ -0,0 +1 @@
+i18nF("${PROPERTY_KEY}", ${PARAMETERS})
diff --git a/.idea/fileTemplates/code/I18nized Expression.java b/.idea/fileTemplates/code/I18nized Expression.java
new file mode 100644
index 0000000000000000000000000000000000000000..d816cf765d099d0cc4c44cd98eb11181bbfd9573
--- /dev/null
+++ b/.idea/fileTemplates/code/I18nized Expression.java	
@@ -0,0 +1 @@
+i18n("${PROPERTY_KEY}")
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000000000000000000000000000000000000..741e3e4f965cf92ce8a910ce02316a71da9b87d2
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,17 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="HardCodedStringLiteral" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <option name="ignoreForAssertStatements" value="true" />
+      <option name="ignoreForExceptionConstructors" value="true" />
+      <option name="ignoreForSpecifiedExceptionConstructors" value="" />
+      <option name="ignoreForJUnitAsserts" value="true" />
+      <option name="ignoreForClassReferences" value="true" />
+      <option name="ignoreForPropertyKeyReferences" value="true" />
+      <option name="ignoreForNonAlpha" value="true" />
+      <option name="ignoreAssignedToConstants" value="false" />
+      <option name="ignoreToString" value="false" />
+      <option name="nonNlsCommentPattern" value="NON-NLS" />
+    </inspection_tool>
+  </profile>
+</component>
\ No newline at end of file
diff --git a/.tx/config b/.tx/config
new file mode 100644
index 0000000000000000000000000000000000000000..dff1b2b932beac7e24680c70b3af7d24cd892ebc
--- /dev/null
+++ b/.tx/config
@@ -0,0 +1,11 @@
+[main]
+host = https://www.transifex.com
+lang_map = zh-Hans: zh_CN, zh-Hant: zh_TW
+
+[briar.briar-desktop]
+file_filter = src/main/resources/strings/BriarDesktop_<lang>.properties
+minimum_perc = 0
+source_file = src/main/resources/strings/BriarDesktop.properties
+source_lang = en
+type = UNICODEPROPERTIES
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 97a1d8f668b3cf6ca561f4d4020e5e75ab0cbe68..a81a7c85831f46ed8c4c778a0257e0f7a1ed944e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -52,6 +52,7 @@ dependencies {
     implementation("com.fasterxml.jackson.core:jackson-databind:2.10.0")
     implementation("com.github.ajalt:clikt:2.2.0")
     implementation("org.jetbrains.compose.material:material-icons-extended:0.4.0")
+    implementation("com.ibm.icu:icu4j:70.1")
 
     implementation(project(path = ":briar-core", configuration = "default"))
     implementation(project(path = ":bramble-java", configuration = "default"))
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/Main.kt b/src/main/kotlin/org/briarproject/briar/desktop/Main.kt
index d2347fa819a7bf5babfd915804941839b6ee3aec..c922146d59965f19569f421f3847039e27688cc4 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/Main.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/Main.kt
@@ -9,6 +9,8 @@ import com.github.ajalt.clikt.parameters.options.option
 import org.briarproject.bramble.BrambleCoreEagerSingletons
 import org.briarproject.briar.BriarCoreEagerSingletons
 import org.briarproject.briar.desktop.utils.FileUtils
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF
 import java.io.File.separator
 import java.io.IOException
 import java.lang.System.getProperty
@@ -24,19 +26,19 @@ private val DEFAULT_DATA_DIR = getProperty("user.home") + separator + ".briar" +
 
 private class Main : CliktCommand(
     name = "briar-desktop",
-    help = "Briar Desktop Client"
+    help = i18n("main.help.title")
 ) {
-    private val debug by option("--debug", "-d", help = "Enable printing of debug messages").flag(
+    private val debug by option("--debug", "-d", help = i18n("main.help.debug")).flag(
         default = false
     )
     private val verbosity by option(
         "--verbose",
         "-v",
-        help = "Print verbose log messages"
+        help = i18n("main.help.verbose")
     ).counted()
     private val dataDir by option(
         "--data-dir",
-        help = "The directory where Briar will store its files. Default: $DEFAULT_DATA_DIR",
+        help = i18nF("main.help.data", DEFAULT_DATA_DIR),
         metavar = "PATH",
         envvar = "BRIAR_DATA_DIR"
     ).default(DEFAULT_DATA_DIR)
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt
index 3a15dc7a06173669c4c87aabbf9a4ef7d6a9a68c..1b01dd7d213e24fe9e55648b6f44e548e6ef6602 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt
@@ -24,6 +24,7 @@ import org.briarproject.briar.desktop.theme.selectedCard
 import org.briarproject.briar.desktop.theme.surfaceVariant
 import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
 import org.briarproject.briar.desktop.ui.HorizontalDivider
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.utils.TimeUtils.getFormattedTimestamp
 
 @Composable
@@ -68,7 +69,7 @@ fun ContactCard(
                         modifier = Modifier.align(Alignment.Start).padding(bottom = 2.dp)
                     )
                     Text(
-                        if (contactItem.isEmpty) "No messages." else getFormattedTimestamp(contactItem.timestamp),
+                        if (contactItem.isEmpty) i18n("contacts.card.nothing") else getFormattedTimestamp(contactItem.timestamp),
                         fontSize = 10.sp,
                         modifier = Modifier.align(Alignment.Start)
                     )
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactDropDown.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactDropDown.kt
index 70c73a89f615bad46840c163eb99d03bb6b696cf..e14593b5608e282f40495fdda0a23973d47062fe 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactDropDown.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactDropDown.kt
@@ -17,6 +17,7 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.sp
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 
 @Composable
 fun ContactDropDown(
@@ -31,24 +32,24 @@ fun ContactDropDown(
         onDismissRequest = { isExpanded(false) },
     ) {
         DropdownMenuItem(onClick = { isExpanded(false); onMakeIntroduction() }) {
-            Text("Make Introduction", fontSize = 14.sp)
+            Text(i18n("contacts.dropdown.introduction"), fontSize = 14.sp)
         }
         DropdownMenuItem(onClick = {}) {
-            Text("Disappearing Messages", fontSize = 14.sp)
+            Text(i18n("contacts.dropdown.disappearing"), fontSize = 14.sp)
         }
         DropdownMenuItem(onClick = {}) {
-            Text("Delete all messages", fontSize = 14.sp)
+            Text(i18n("contacts.dropdown.delete.all"), fontSize = 14.sp)
         }
         DropdownMenuItem(onClick = { connectionMode = true; isExpanded(false) }) {
             Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
-                Text("Connections", fontSize = 14.sp, modifier = Modifier.align(Alignment.CenterVertically))
-                Icon(Icons.Filled.ArrowRight, "connections", modifier = Modifier.align(Alignment.CenterVertically))
+                Text(i18n("contacts.dropdown.connections"), fontSize = 14.sp, modifier = Modifier.align(Alignment.CenterVertically))
+                Icon(Icons.Filled.ArrowRight, i18n("access.contacts.dropdown.connections.expand"), modifier = Modifier.align(Alignment.CenterVertically))
             }
         }
         DropdownMenuItem(onClick = { contactMode = true; isExpanded(false) }) {
             Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
-                Text("Contact", fontSize = 14.sp, modifier = Modifier.align(Alignment.CenterVertically))
-                Icon(Icons.Filled.ArrowRight, "connections", modifier = Modifier.align(Alignment.CenterVertically))
+                Text(i18n("contacts.dropdown.contact"), fontSize = 14.sp, modifier = Modifier.align(Alignment.CenterVertically))
+                Icon(Icons.Filled.ArrowRight, i18n("access.contacts.dropdown.contacts.expand"), modifier = Modifier.align(Alignment.CenterVertically))
             }
         }
     }
@@ -58,13 +59,13 @@ fun ContactDropDown(
             onDismissRequest = { connectionMode = false },
         ) {
             DropdownMenuItem(onClick = { false }) {
-                Text("Connections", fontSize = 12.sp)
+                Text(i18n("contacts.dropdown.connections.title"), fontSize = 12.sp)
             }
             DropdownMenuItem(onClick = { false }) {
-                Text("Connect via Bluetooth", fontSize = 14.sp)
+                Text(i18n("contacts.dropdown.connections.bluetooth"), fontSize = 14.sp)
             }
             DropdownMenuItem(onClick = { false }) {
-                Text("Connect via Removable Device", fontSize = 14.sp)
+                Text(i18n("contacts.dropdown.connections.removable"), fontSize = 14.sp)
             }
         }
     }
@@ -74,13 +75,13 @@ fun ContactDropDown(
             onDismissRequest = { contactMode = false },
         ) {
             DropdownMenuItem(onClick = { false }) {
-                Text("Contact", fontSize = 12.sp)
+                Text(i18n("contacts.dropdown.contact.title"), fontSize = 12.sp)
             }
             DropdownMenuItem(onClick = { false }) {
-                Text("Change contact name", fontSize = 14.sp)
+                Text(i18n("contacts.dropdown.contact.change"), fontSize = 14.sp)
             }
             DropdownMenuItem(onClick = { false }) {
-                Text("Delete contact", fontSize = 14.sp)
+                Text(i18n("contacts.dropdown.contact.delete"), fontSize = 14.sp)
             }
         }
     }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt
index 61d13a2f4cd48fa352f4cc2b36cd6909b483624a..735e7f87fb53d822c66b97b0f269d20919890b46 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt
@@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 
 @Composable
 fun SearchTextField(searchValue: String, onValueChange: (String) -> Unit, onContactAdd: () -> Unit) {
@@ -28,11 +29,11 @@ fun SearchTextField(searchValue: String, onValueChange: (String) -> Unit, onCont
         onValueChange = onValueChange,
         singleLine = true,
         textStyle = TextStyle(fontSize = 16.sp, color = MaterialTheme.colors.onSurface),
-        placeholder = { Text("Contacts") },
+        placeholder = { Text(i18n("contacts.search.title")) },
         shape = RoundedCornerShape(0.dp),
         leadingIcon = {
             val padding = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 12.dp)
-            Icon(Icons.Filled.Search, "search contacts", padding)
+            Icon(Icons.Filled.Search, i18n("access.contacts.search"), padding)
         },
         trailingIcon = {
             IconButton(
@@ -40,7 +41,7 @@ fun SearchTextField(searchValue: String, onValueChange: (String) -> Unit, onCont
                 modifier = Modifier.padding(end = 10.dp).size(32.dp)
                     .background(MaterialTheme.colors.primary, CircleShape)
             ) {
-                Icon(Icons.Filled.PersonAdd, "add contact", tint = Color.White, modifier = Modifier.size(20.dp))
+                Icon(Icons.Filled.PersonAdd, i18n("access.contacts.add"), tint = Color.White, modifier = Modifier.size(20.dp))
             }
         },
         modifier = Modifier.fillMaxSize()
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt
index 7a92663a4f2275af8d431a605b3b40cc2d474501..8f28474fe8fe0c501ff327f7348012d715c9da91 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt
@@ -28,6 +28,7 @@ import org.briarproject.briar.desktop.theme.outline
 import org.briarproject.briar.desktop.theme.surfaceVariant
 import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
 import org.briarproject.briar.desktop.ui.HorizontalDivider
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 
 @Composable
 fun ConversationHeader(
@@ -62,7 +63,7 @@ fun ConversationHeader(
             onClick = { setExpanded(!isExpanded) },
             modifier = Modifier.align(Alignment.CenterEnd).padding(end = 16.dp)
         ) {
-            Icon(Icons.Filled.MoreVert, "contact info", modifier = Modifier.size(24.dp))
+            Icon(Icons.Filled.MoreVert, i18n("access.contact.menu"), modifier = Modifier.size(24.dp))
             ContactDropDown(isExpanded, setExpanded, onMakeIntroduction)
         }
         HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationInput.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationInput.kt
index dcfa6d755ff7af6a179a2a4975f0c68b575e0d30..8818ad76ef78de0a58575aeec1cfe817cc03475e 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationInput.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationInput.kt
@@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import org.briarproject.briar.desktop.theme.DarkColors
 import org.briarproject.briar.desktop.ui.HorizontalDivider
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 
 @Preview
 @Composable
@@ -46,7 +47,7 @@ fun ConversationInput(text: String, updateText: (String) -> Unit, onSend: () ->
             onValueChange = updateText,
             maxLines = 10,
             textStyle = TextStyle(fontSize = 16.sp, lineHeight = 16.sp),
-            placeholder = { Text("Message") },
+            placeholder = { Text(i18n("conversation.message.new")) },
             modifier = Modifier.fillMaxWidth(),
             shape = RoundedCornerShape(0.dp),
             colors = TextFieldDefaults.textFieldColors(
@@ -60,7 +61,7 @@ fun ConversationInput(text: String, updateText: (String) -> Unit, onSend: () ->
                     Modifier.padding(4.dp).size(32.dp)
                         .background(MaterialTheme.colors.primary, CircleShape),
                 ) {
-                    Icon(Icons.Filled.Add, "add attachment", Modifier.size(24.dp), Color.White)
+                    Icon(Icons.Filled.Add, i18n("access.attachment"), Modifier.size(24.dp), Color.White)
                 }
             },
             trailingIcon = {
@@ -69,7 +70,7 @@ fun ConversationInput(text: String, updateText: (String) -> Unit, onSend: () ->
                 ) {
                     Icon(
                         Icons.Filled.Send,
-                        "send message",
+                        i18n("access.message.send"),
                         tint = MaterialTheme.colors.secondary,
                         modifier = Modifier.size(24.dp)
                     )
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/TextBubble.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/TextBubble.kt
index 63fc4c7f177b26799dd00baf65322f7e41be3938..47f6711eb5ce775da9a575d60b64f0b315c0b551 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/TextBubble.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/TextBubble.kt
@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import org.briarproject.briar.desktop.theme.awayMsgBubble
 import org.briarproject.briar.desktop.theme.localMsgBubble
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.utils.TimeUtils
 
 @Composable
@@ -47,7 +48,7 @@ fun TextBubble(m: ConversationMessageItem) {
                                 if (m.isSeen) Icons.Filled.DoneAll // acknowledged
                                 else if (m.isSent) Icons.Filled.Done // sent
                                 else Icons.Filled.Schedule // waiting
-                            Icon(icon, "sent", modifier)
+                            Icon(icon, i18n("access.message.sent"), modifier)
                         }
                     }
                 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/introduction/ContactDrawerMakeIntro.kt b/src/main/kotlin/org/briarproject/briar/desktop/introduction/ContactDrawerMakeIntro.kt
index 68b78c670fc047af6545ea71ae84335b2ca1c28c..8716c44ef85401d10401dcdc55c33f028588690e 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/introduction/ContactDrawerMakeIntro.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/introduction/ContactDrawerMakeIntro.kt
@@ -29,6 +29,9 @@ import org.briarproject.briar.desktop.contact.ContactCard
 import org.briarproject.briar.desktop.contact.ProfileCircle
 import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
 import org.briarproject.briar.desktop.ui.HorizontalDivider
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF
+import java.util.Locale
 
 @Composable
 fun ContactDrawerMakeIntro(
@@ -43,10 +46,10 @@ fun ContactDrawerMakeIntro(
                         onClick = { setInfoDrawer(false) },
                         Modifier.padding(horizontal = 11.dp).size(32.dp).align(Alignment.CenterVertically)
                     ) {
-                        Icon(Icons.Filled.Close, "close make intro screen")
+                        Icon(Icons.Filled.Close, i18n("access.introduction.close"))
                     }
                     Text(
-                        text = "Introduce ${viewModel.firstContact.value!!.author.name} to",
+                        text = i18nF("introduction.title_first", viewModel.firstContact.value!!.author.name),
                         fontSize = 16.sp,
                         modifier = Modifier.align(Alignment.CenterVertically)
                     )
@@ -70,10 +73,10 @@ fun ContactDrawerMakeIntro(
                     onClick = viewModel::backToFirstScreen,
                     Modifier.padding(horizontal = 11.dp).size(32.dp).align(Alignment.CenterVertically)
                 ) {
-                    Icon(Icons.Filled.ArrowBack, "go back to make intro contact screen", tint = Color.White)
+                    Icon(Icons.Filled.ArrowBack, i18n("access.introduction.back.contact"), tint = Color.White)
                 }
                 Text(
-                    text = "Introduce Contacts",
+                    text = i18n("introduction.title_second"),
                     fontSize = 16.sp,
                     modifier = Modifier.align(Alignment.CenterVertically)
                 )
@@ -83,7 +86,7 @@ fun ContactDrawerMakeIntro(
                     ProfileCircle(36.dp, viewModel.firstContact.value!!.author.id.bytes)
                     Text(viewModel.firstContact.value!!.author.name, Modifier.padding(top = 4.dp), Color.White, 16.sp)
                 }
-                Icon(Icons.Filled.SwapHoriz, "swap", modifier = Modifier.size(48.dp))
+                Icon(Icons.Filled.SwapHoriz, i18n("access.swap"), modifier = Modifier.size(48.dp))
                 Column(Modifier.align(Alignment.CenterVertically)) {
                     ProfileCircle(36.dp, viewModel.secondContact.value!!.author.id.bytes)
                     Text(viewModel.secondContact.value!!.author.name, Modifier.padding(top = 4.dp), Color.White, 16.sp)
@@ -93,7 +96,7 @@ fun ContactDrawerMakeIntro(
                 TextField(
                     viewModel.introductionMessage.value,
                     viewModel::setIntroductionMessage,
-                    placeholder = { Text(text = "Add a message (optional)") },
+                    placeholder = { Text(text = i18n("introduction.message")) },
                 )
             }
             Row(Modifier.padding(8.dp)) {
@@ -101,7 +104,8 @@ fun ContactDrawerMakeIntro(
                     onClick = { setInfoDrawer(false) },
                     Modifier.fillMaxWidth()
                 ) {
-                    Text("MAKE INTRODUCTION")
+                    val text = i18n("introduction.introduce")
+                    Text(text.uppercase(Locale.getDefault()))
                 }
             }
         }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/Login.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/Login.kt
index 488a225e8a53532798573f64f6b77d06ff4731f6..bfc6ff0d8360769bfcc78f6b71961fc0b5758333 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/Login.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/Login.kt
@@ -34,6 +34,7 @@ import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 
 // TODO: Error handling
 @OptIn(ExperimentalComposeUiApi::class)
@@ -61,7 +62,7 @@ fun Login(
             OutlinedTextField(
                 value = viewModel.password.value,
                 onValueChange = viewModel::setPassword,
-                label = { Text("Password") },
+                label = { Text(i18n("password")) },
                 singleLine = true,
                 visualTransformation = PasswordVisualTransformation(),
                 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
@@ -77,7 +78,7 @@ fun Login(
             )
             Spacer(Modifier.height(16.dp))
             Button(onClick = { signIn() }) {
-                Text("Login")
+                Text(i18n("login.login"))
             }
 
             DisposableEffect(Unit) {
@@ -90,4 +91,4 @@ fun Login(
 
 @Composable
 fun BriarLogo(modifier: Modifier = Modifier.fillMaxWidth().clip(shape = RoundedCornerShape(400.dp))) =
-    Image(painterResource("images/logo_circle.svg"), "Briar logo", modifier)
+    Image(painterResource("images/logo_circle.svg"), i18n("access.logo"), modifier)
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/Registration.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/Registration.kt
index 78758000be6c01d520982cb4c7569d856ecb5f03..e90a0d6c1f378e23c39fa21aeb9db3b165c96c1d 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/Registration.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/Registration.kt
@@ -33,6 +33,8 @@ import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
+import java.util.Locale
 
 // TODO: Error handling and password strength
 @OptIn(ExperimentalComposeUiApi::class)
@@ -61,7 +63,7 @@ fun Registration(
             OutlinedTextField(
                 value = viewModel.username.value,
                 onValueChange = { viewModel.setUsername(it) },
-                label = { Text("Username") },
+                label = { Text(i18n("registration.username")) },
                 singleLine = true,
                 textStyle = TextStyle(color = Color.White),
                 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
@@ -78,7 +80,7 @@ fun Registration(
             OutlinedTextField(
                 value = viewModel.password.value,
                 onValueChange = { viewModel.setPassword(it) },
-                label = { Text("Password") },
+                label = { Text(i18n("password")) },
                 singleLine = true,
                 textStyle = TextStyle(color = Color.White),
                 visualTransformation = PasswordVisualTransformation(),
@@ -93,7 +95,8 @@ fun Registration(
             )
             Spacer(Modifier.height(16.dp))
             Button(onClick = { signUp() }) {
-                Text("Register", color = Color.Black)
+                val text = i18n("registration.register")
+                Text(text.uppercase(Locale.getDefault()), color = Color.Black)
             }
 
             DisposableEffect(Unit) {
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/settings/PlaceHolderSettingsView.kt b/src/main/kotlin/org/briarproject/briar/desktop/settings/PlaceHolderSettingsView.kt
index 7ad692bb97924fc74714c15ccefda2344db6836d..0c94d09f3d02338c81f70f59dc0d06e4a5d982a3 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/settings/PlaceHolderSettingsView.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/settings/PlaceHolderSettingsView.kt
@@ -7,13 +7,14 @@ import androidx.compose.material.Surface
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 
 @Composable
 fun PlaceHolderSettingsView(isDark: Boolean, setDark: (Boolean) -> Unit) {
     Surface(Modifier.fillMaxSize()) {
         Box {
             Button(onClick = { setDark(!isDark) }) {
-                Text("Change Theme")
+                Text(i18n("settings.theme"))
             }
         }
     }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
index 76c607b4402f510e41e39042c662147bc862f4f8..3d46cd5d7ce2685ec0e1dd18e9f64131bcedff7d 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt
@@ -19,6 +19,7 @@ import org.briarproject.briar.desktop.login.Registration
 import org.briarproject.briar.desktop.login.RegistrationViewModel
 import org.briarproject.briar.desktop.navigation.SidebarViewModel
 import org.briarproject.briar.desktop.theme.BriarTheme
+import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import java.awt.Dimension
 import java.util.logging.Logger
 import javax.annotation.concurrent.Immutable
@@ -69,7 +70,7 @@ constructor(
     override fun start() {
         application {
             val (isDark, setDark) = remember { mutableStateOf(true) }
-            val title = "Briar Desktop"
+            val title = i18n("main.title")
             var screenState by remember {
                 mutableStateOf(
                     if (accountManager.hasDatabaseKey()) {
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/utils/InternationalizationUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/utils/InternationalizationUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f2928561d8ed2f056c9e6a90e2f55638f673475f
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/utils/InternationalizationUtils.kt
@@ -0,0 +1,47 @@
+package org.briarproject.briar.desktop.utils
+
+import com.ibm.icu.text.MessageFormat
+import java.util.Locale
+import java.util.ResourceBundle
+
+/**
+ * Helper functions around internationalization.
+ *
+ * Instead of using the default Locale, one can also use, e.g., `Locale("ar")` instead
+ * or call `Locale.setDefault()`.
+ */
+object InternationalizationUtils {
+
+    /**
+     * Returns the translated text of the string identified with `key`
+     */
+    fun i18n(key: String): String {
+        val resourceBundle = createResourceBundle()
+        return resourceBundle.getString(key)
+    }
+
+    /**
+     * Returns the translated text of a string with plurals identified with `key`
+     */
+    fun i18nP(key: String, amount: Int): String {
+        val pattern: String = i18n(key)
+        val messageFormat = MessageFormat(pattern, Locale.getDefault())
+        return messageFormat.format(arrayOf(amount))
+    }
+
+    /**
+     * Returns the translated text of a formatted string with
+     */
+    fun i18nF(key: String, vararg params: Any): String {
+        val pattern: String = i18n(key)
+        return java.text.MessageFormat.format(pattern, *params)
+    }
+
+    /**
+     * Returns the resource bundle used for i18n at Briar Desktop
+     */
+    private fun createResourceBundle(): ResourceBundle {
+        val locale = Locale.getDefault()
+        return ResourceBundle.getBundle("strings.BriarDesktop", locale)
+    }
+}
diff --git a/src/main/resources/strings/BriarDesktop.properties b/src/main/resources/strings/BriarDesktop.properties
new file mode 100644
index 0000000000000000000000000000000000000000..535f818d015728f22b0b1e0bbfd894f1a7bd6b9c
--- /dev/null
+++ b/src/main/resources/strings/BriarDesktop.properties
@@ -0,0 +1,57 @@
+# Accessibility
+access.attachment=Add attachment
+access.contacts.add=Add contact
+access.contact.menu=Show contact menu
+access.contacts.dropdown.connections.expand=Expand connections menu
+access.contacts.dropdown.contacts.expand=Expand connections menu
+access.contacts.search=Icon search contacts
+access.introduction.back.contact=Go back to contact screen of introduction process
+access.introduction.close=Close introduction screen
+access.message.send=Send message
+access.message.sent=Message sent
+access.logo=Briar logo
+access.swap=Icon showing errors between two contacts
+
+# Contacts
+contacts.card.nothing=No messages.
+contacts.dropdown.connections=Connections
+contacts.dropdown.connections.title=Connections
+contacts.dropdown.connections.bluetooth=Connect via Bluetooth
+contacts.dropdown.connections.removable=Connect via Removable Drive
+contacts.dropdown.contact=Contact
+contacts.dropdown.contact.title=Contact
+contacts.dropdown.contact.change=Change contact name
+contacts.dropdown.contact.delete=Delete contact
+contacts.dropdown.delete.all=Delete all messages
+contacts.dropdown.disappearing=Disappearing messages
+contacts.dropdown.introduction=Make Introduction
+contacts.search.title=Contacts
+
+# Conversation
+conversation.message.new=New Message
+
+# Introduction
+introduction.introduce=Make Introduction
+introduction.message=Add a message (optional)
+introduction.title_first=Introduce {0} to
+introduction.title_second=Introduce Contacts
+
+# Login screen
+login.login=Login
+
+# Main
+main.title=Briar Desktop
+main.help.title=Briar Desktop Client
+main.help.debug=Enable printing of debug messages
+main.help.verbose=Print verbose log messages
+main.help.data=The directory where Briar will store its files. Default: {0}
+
+# Miscellaneous
+password=Password
+
+# Registration screen
+registration.username=Username
+registration.register=Create Account
+
+# Settings
+settings.theme=Change Theme
\ No newline at end of file
diff --git a/src/main/resources/strings/BriarDesktop_ar.properties b/src/main/resources/strings/BriarDesktop_ar.properties
new file mode 100644
index 0000000000000000000000000000000000000000..11b29ba8d5b4d80b3558e7b97a7463bde7cae0cb
--- /dev/null
+++ b/src/main/resources/strings/BriarDesktop_ar.properties
@@ -0,0 +1,57 @@
+# Accessibility
+access.attachment=إضافة مرفق
+access.contacts.add=إضافة جهة إتصال
+access.contact.menu=Show contact menu
+access.contacts.dropdown.connections.expand=Expand connections menu
+access.contacts.dropdown.contacts.expand=Expand connections menu
+access.contacts.search=Icon search contacts
+access.introduction.back.contact=Go back to contact screen of introduction process
+access.introduction.close=Close introduction screen
+access.message.send=Send message
+access.message.sent=تم إرسال الرسالة
+access.logo=Briar logo
+access.swap=Icon showing errors between two contacts
+
+# Contacts
+contacts.card.nothing=لا رسائل.
+contacts.dropdown.connections=شبكات الاتصال
+contacts.dropdown.connections.title=شبكات الاتصال
+contacts.dropdown.connections.bluetooth=الإتصال عبر بلوتوث
+contacts.dropdown.connections.removable=Connect via Removable Drive
+contacts.dropdown.contact=للتواصل
+contacts.dropdown.contact.title=للتواصل
+contacts.dropdown.contact.change=تعديل إسم جهة الاتصال
+contacts.dropdown.contact.delete=حذف جهة الإتصال
+contacts.dropdown.delete.all=حذف جميع الرّسائل 
+contacts.dropdown.disappearing=Disappearing messages
+contacts.dropdown.introduction=عمل تقديم
+contacts.search.title=دفتر الاتصالات
+
+# Conversation
+conversation.message.new=رسالة جديدة
+
+# Introduction
+introduction.introduce=عمل تقديم
+introduction.message=اضافة رسالة (إختياري)
+introduction.title_first=Introduce {0} to
+introduction.title_second=قدّم جهات اتصال
+
+# Login screen
+login.login=الدخول
+
+# Main
+main.title=Briar Desktop
+main.help.title=Briar Desktop Client
+main.help.debug=Enable printing of debug messages
+main.help.verbose=Print verbose log messages
+main.help.data=The directory where Briar will store its files. Default: {0}
+
+# Miscellaneous
+password=كلمة السّر
+
+# Registration screen
+registration.username=اسم المستخدم
+registration.register=إنشاء الحساب
+
+# Settings
+settings.theme=Change Theme
\ No newline at end of file
diff --git a/src/main/resources/strings/BriarDesktop_de.properties b/src/main/resources/strings/BriarDesktop_de.properties
new file mode 100644
index 0000000000000000000000000000000000000000..837aea2ab8efee5ee2acfe14cabdd74c50e8899a
--- /dev/null
+++ b/src/main/resources/strings/BriarDesktop_de.properties
@@ -0,0 +1,57 @@
+# Accessibility
+access.attachment=Anhang hinzufügen
+access.contacts.add=Kontakt hinzufügen
+access.contact.menu=Show contact menu
+access.contacts.dropdown.connections.expand=Expand connections menu
+access.contacts.dropdown.contacts.expand=Expand connections menu
+access.contacts.search=Icon search contacts
+access.introduction.back.contact=Go back to contact screen of introduction process
+access.introduction.close=Close introduction screen
+access.message.send=Nachricht senden
+access.message.sent=Nachricht gesendet
+access.logo=Briar logo
+access.swap=Icon showing errors between two contacts
+
+# Contacts
+contacts.card.nothing=Keine Nachrichten.
+contacts.dropdown.connections=Verbindungen
+contacts.dropdown.connections.title=Verbindungen
+contacts.dropdown.connections.bluetooth=Über Bluetooth verbinden
+contacts.dropdown.connections.removable=Über Wechseldatenträger verbinden
+contacts.dropdown.contact=Kontakt
+contacts.dropdown.contact.title=Kontakt
+contacts.dropdown.contact.change=Kontaktnamen ändern
+contacts.dropdown.contact.delete=Kontakt löschen
+contacts.dropdown.delete.all=Alle Nachrichten löschen
+contacts.dropdown.disappearing=Selbstlöschende Nachrichten
+contacts.dropdown.introduction=Kontaktempfehlung abgeben
+contacts.search.title=Kontakte
+
+# Conversation
+conversation.message.new=Neue Nachricht
+
+# Introduction
+introduction.introduce=Kontaktempfehlung abgeben
+introduction.message=Nachricht hinzufügen (optional)
+introduction.title_first=Introduce {0} to
+introduction.title_second=Kontakte untereinander bekannt machen
+
+# Login screen
+login.login=Anmelden
+
+# Main
+main.title=Briar Desktop
+main.help.title=Briar Desktop Client
+main.help.debug=Enable printing of debug messages
+main.help.verbose=Print verbose log messages
+main.help.data=The directory where Briar will store its files. Default: {0}
+
+# Miscellaneous
+password=Passwort
+
+# Registration screen
+registration.username=Benutzername
+registration.register=Konto erstellen
+
+# Settings
+settings.theme=Change Theme
\ No newline at end of file
diff --git a/src/main/resources/strings/BriarDesktop_es.properties b/src/main/resources/strings/BriarDesktop_es.properties
new file mode 100644
index 0000000000000000000000000000000000000000..38f2660951f514d5f7ca71dbd95b3f0e4353aa2a
--- /dev/null
+++ b/src/main/resources/strings/BriarDesktop_es.properties
@@ -0,0 +1,57 @@
+# Accessibility
+access.attachment=Añadir adjunto
+access.contacts.add=Añadir el contacto
+access.contact.menu=Show contact menu
+access.contacts.dropdown.connections.expand=Expand connections menu
+access.contacts.dropdown.contacts.expand=Expand connections menu
+access.contacts.search=Icon search contacts
+access.introduction.back.contact=Go back to contact screen of introduction process
+access.introduction.close=Close introduction screen
+access.message.send=Enviar mensaje
+access.message.sent=Mensaje enviado
+access.logo=Briar logo
+access.swap=Icon showing errors between two contacts
+
+# Contacts
+contacts.card.nothing=Sin mensajes.
+contacts.dropdown.connections=Conexiones
+contacts.dropdown.connections.title=Conexiones
+contacts.dropdown.connections.bluetooth=Conectar mediante Bluetooth
+contacts.dropdown.connections.removable=Conectar a través de Discos Removibles
+contacts.dropdown.contact=Contacto
+contacts.dropdown.contact.title=Contacto
+contacts.dropdown.contact.change=Cambiar nombre del contacto
+contacts.dropdown.contact.delete=Eliminar contacto
+contacts.dropdown.delete.all=Eliminar todos los mensajes
+contacts.dropdown.disappearing=Mensajes con caducidad
+contacts.dropdown.introduction=Hacer presentación
+contacts.search.title=Contactos
+
+# Conversation
+conversation.message.new=Nuevo mensaje
+
+# Introduction
+introduction.introduce=Hacer presentación
+introduction.message=Añade un mensaje (opcional)
+introduction.title_first=Introduce {0} to
+introduction.title_second=Presentar contactos
+
+# Login screen
+login.login=Entrar
+
+# Main
+main.title=Briar Desktop
+main.help.title=Briar Desktop Client
+main.help.debug=Enable printing of debug messages
+main.help.verbose=Print verbose log messages
+main.help.data=The directory where Briar will store its files. Default: {0}
+
+# Miscellaneous
+password=Contraseña
+
+# Registration screen
+registration.username=Nombre de usuario
+registration.register=Crear cuenta
+
+# Settings
+settings.theme=Change Theme
\ No newline at end of file