[plasma/plasma-workspace] /: merge Language and Formats

Han Young null at kde.org
Sat Jul 2 10:30:00 BST 2022


Git commit 134e2d5c989c36ac0e985ee0ae382996c6b7b56e by Han Young.
Committed on 02/07/2022 at 09:29.
Pushed by hanyoung into branch 'master'.

merge Language and Formats

BUG: 192019
BUG: 341235
BUG: 344588
BUG: 394477
BUG: 397974
BUG: 397975
BUG: 403580
BUG: 417564
BUG: 420268
BUG: 429474
BUG: 431292
BUG: 444772
BUG: 446785
BUG: 447787
BUG: 448324
BUG: 448355
BUG: 451919
BUG: 451944
BUG: 454991

closes https://invent.kde.org/plasma/plasma-workspace/-/issues/23

M  +1    -0    .kde-ci.yml
M  +52   -8    CMakeLists.txt
M  +2    -1    config-workspace.h.cmake
D  +0    -2    doc/kcontrol/formats/CMakeLists.txt
D  +0    -63   doc/kcontrol/formats/index.docbook
R  +1    -1    doc/kcontrol/region_language/CMakeLists.txt [from: doc/kcontrol/translations/CMakeLists.txt - 063% similarity]
R  +-    --    doc/kcontrol/region_language/go-top.png [from: doc/kcontrol/translations/go-top.png - 100% similarity]
R  +36   -23   doc/kcontrol/region_language/index.docbook [from: doc/kcontrol/translations/index.docbook - 050% similarity]
A  +-    --    doc/kcontrol/region_language/list-remove.png
D  +-    --    doc/kcontrol/translations/list-remove.png
M  +1    -2    kcms/CMakeLists.txt
D  +0    -30   kcms/formats/CMakeLists.txt
D  +0    -5    kcms/formats/Messages.sh
D  +0    -61   kcms/formats/formatssettings.kcfg
D  +0    -80   kcms/formats/kcmformats.cpp
D  +0    -34   kcms/formats/kcmformats.h
D  +0    -183  kcms/formats/localelistmodel.cpp
D  +0    -140  kcms/formats/optionsmodel.cpp
D  +0    -126  kcms/formats/package/contents/ui/main.qml
A  +75   -0    kcms/region_language/CMakeLists.txt
A  +8    -0    kcms/region_language/Messages.sh
R  +2    -27   kcms/region_language/exampleutility.cpp [from: kcms/formats/exampleutility.cpp - 052% similarity]
A  +22   -0    kcms/region_language/exampleutility.h     [License: GPL(v2.0+)]
A  +82   -0    kcms/region_language/kcm_regionandlang.desktop [TRAILING SPACE] ** [TRAILING SPACE] **
R  +4    -4    kcms/region_language/kcm_regionandlang.json [from: kcms/formats/kcm_formats.json - 098% similarity]
A  +250  -0    kcms/region_language/kcmregionandlang.cpp     [License: GPL(v2.0+)]
A  +58   -0    kcms/region_language/kcmregionandlang.h     [License: GPL(v2.0+)]
A  +372  -0    kcms/region_language/languagelistmodel.cpp     [License: GPL(v2.0+)]
A  +100  -0    kcms/region_language/languagelistmodel.h     [License: GPL(v2.0+)]
A  +30   -0    kcms/region_language/localegenerator.cpp     [License: LGPL(v2.0+)]
A  +17   -0    kcms/region_language/localegenerator.h     [License: LGPL(v2.0+)]
A  +19   -0    kcms/region_language/localegeneratorbase.cpp     [License: GPL(v2.0+)]
A  +23   -0    kcms/region_language/localegeneratorbase.h     [License: GPL(v2.0+)]
A  +32   -0    kcms/region_language/localegeneratorglibc.cpp     [License: GPL(v2.0+)]
A  +24   -0    kcms/region_language/localegeneratorglibc.h     [License: GPL(v2.0+)]
A  +101  -0    kcms/region_language/localegeneratorubuntu.cpp     [License: GPL(v2.0+)]
A  +27   -0    kcms/region_language/localegeneratorubuntu.h     [License: GPL(v2.0+)]
A  +36   -0    kcms/region_language/localegenhelper/CMakeLists.txt
A  +187  -0    kcms/region_language/localegenhelper/localegenhelper.cpp     [License: GPL(v2.0+)]
A  +46   -0    kcms/region_language/localegenhelper/localegenhelper.h     [License: GPL(v2.0+)]
A  +20   -0    kcms/region_language/localegenhelper/org.kde.localegenhelper.conf
A  +21   -0    kcms/region_language/localegenhelper/org.kde.localegenhelper.policy
A  +8    -0    kcms/region_language/localegenhelper/org.kde.localegenhelper.service.in
A  +158  -0    kcms/region_language/localelistmodel.cpp     [License: GPL (v2+)]
R  +22   -22   kcms/region_language/localelistmodel.h [from: kcms/formats/localelistmodel.h - 060% similarity]
A  +197  -0    kcms/region_language/optionsmodel.cpp     [License: GPL(v2.0+)]
R  +21   -11   kcms/region_language/optionsmodel.h [from: kcms/formats/optionsmodel.h - 054% similarity]
A  +204  -0    kcms/region_language/package/contents/ui/AdvancedLanguageSelectPage.qml     [License: LGPL(v3.0+)]
A  +238  -0    kcms/region_language/package/contents/ui/main.qml     [License: LGPL(v3.0+)]
A  +64   -0    kcms/region_language/regionandlangsettings.cpp     [License: GPL(v2.0+)]
A  +21   -0    kcms/region_language/regionandlangsettings.h     [License: GPL(v2.0+)]
A  +37   -0    kcms/region_language/regionandlangsettingsbase.kcfg
R  +2    -2    kcms/region_language/regionandlangsettingsbase.kcfgc [from: kcms/formats/formatssettings.kcfgc - 055% similarity]
A  +17   -0    kcms/region_language/settingtype.h     [License: GPL(v2.0+)]
D  +0    -49   kcms/translations/CMakeLists.txt
D  +0    -2    kcms/translations/Messages.sh
D  +0    -191  kcms/translations/language.cpp
D  +0    -32   kcms/translations/language.h
D  +0    -312  kcms/translations/package/contents/ui/main.qml
D  +0    -86   kcms/translations/translations.cpp
D  +0    -53   kcms/translations/translations.h
D  +0    -193  kcms/translations/translationsmodel.cpp
D  +0    -72   kcms/translations/translationsmodel.h
D  +0    -27   kcms/translations/translationssettings.cpp
D  +0    -24   kcms/translations/translationssettings.h
D  +0    -28   kcms/translations/translationssettingsbase.kcfg
D  +0    -6    kcms/translations/translationssettingsbase.kcfgc

The files marked with ** at the end have a problem. Either the file contains a trailing space or the file contains a call to potentially dangerous code. Please read: https://community.kde.org/Sysadmin/CommitHooks#Email_notifications for further information. Please either fix the trailing space or review the dangerous code.


https://invent.kde.org/plasma/plasma-workspace/commit/134e2d5c989c36ac0e985ee0ae382996c6b7b56e

diff --git a/.kde-ci.yml b/.kde-ci.yml
index 645a456e0..36931c639 100644
--- a/.kde-ci.yml
+++ b/.kde-ci.yml
@@ -62,3 +62,4 @@ Dependencies:
 - 'on': ['Linux']
   'require':
     'frameworks/networkmanager-qt': '@latest'
+    'libraries/polkit-qt-1': '@latest'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9d241ca02..8eae594cc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.22)
 
 project(plasma-workspace)
 set(PROJECT_VERSION "5.25.80")
@@ -169,14 +169,58 @@ if(${AppStreamQt_FOUND})
     set(HAVE_APPSTREAMQT true)
 endif()
 
-find_package(PackageKitQt${QT_MAJOR_VERSION})
-set_package_properties(PackageKitQt${QT_MAJOR_VERSION}
-        PROPERTIES DESCRIPTION "Software Manager integration"
-        TYPE OPTIONAL
-        PURPOSE "Used to install additional language packages on demand"
+if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+    # find out if the build system is Ubuntu based
+    cmake_host_system_information(RESULT DISTRO_NAME QUERY DISTRIB_ID)
+    cmake_host_system_information(RESULT DISTRO_ID_LIKE QUERY DISTRIB_ID_LIKE)
+    string(COMPARE EQUAL "ubuntu" "${DISTRO_NAME}" SYSTEM_UBUNTU_BASED)
+    string(FIND "${DISTRO_ID_LIKE}" "ubuntu" FINDINDEX)
+    if(NOT (FINDINDEX EQUAL -1))
+        set(SYSTEM_UBUNTU_BASED ON)
+    endif()
+
+    option(UBUNTU_PACKAGEKIT "Install required package for language on Ubuntu Based systems, PackageKitQt5 required" ${SYSTEM_UBUNTU_BASED})
+    if(UBUNTU_PACKAGEKIT)
+        set(OS_UBUNTU TRUE)
+    endif()
+
+    set(GLIBC_LOCALE_GEN_DEFAULT ON)
+    if(UBUNTU_PACKAGEKIT)
+        set(GLIBC_LOCALE_GEN_DEFAULT OFF)
+    endif()
+
+    option(GLIBC_LOCALE_GEN "Auto generate Glibc locale with locale-gen and /etc/locale.gen" ${GLIBC_LOCALE_GEN_DEFAULT})
+    if(GLIBC_LOCALE_GEN AND UBUNTU_PACKAGEKIT)
+        message(FATAL_ERROR "UBUNTU_PACKAGEKIT and GLIBC_LOCALE_GEN both enabled, only UBUNTU_PACKEGKIT will be used")
+    endif()
+    if(GLIBC_LOCALE_GEN)
+        set(GLIBC_LOCALE TRUE)
+    endif()
+    if(UBUNTU_PACKAGEKIT OR GLIBC_LOCALE_GEN)
+        set(REGION_LANG_GENERATE_LOCALE TRUE)
+    endif()
+
+    ################## Find libraries ###################
+    if(REGION_LANG_GENERATE_LOCALE)
+        find_package(PolkitQt5-1)
+        set_package_properties(PolkitQt5-1
+                PROPERTIES DESCRIPTION "DBus interface wrapper for Polkit"
+                PURPOSE "Communicate with localegen helper in region & lang kcm"
+                TYPE REQUIRED
         )
-if(PackageKitQt${QT_MAJOR_VERSION}_FOUND)
-    set(HAVE_PACKAGEKIT TRUE)
+    endif()
+
+    if(UBUNTU_PACKAGEKIT)
+        find_package(PackageKitQt${QT_MAJOR_VERSION})
+        set_package_properties(PackageKitQt${QT_MAJOR_VERSION}
+                PROPERTIES DESCRIPTION "Software Manager integration"
+                TYPE OPTIONAL
+                PURPOSE "Used to install additional language packages on demand"
+                )
+
+    endif()
+
+    find_package(KF5ItemModels)
 endif()
 
 find_package(ICU COMPONENTS i18n uc)
diff --git a/config-workspace.h.cmake b/config-workspace.h.cmake
index 0e08829ad..079d84cab 100644
--- a/config-workspace.h.cmake
+++ b/config-workspace.h.cmake
@@ -11,4 +11,5 @@
 
 #define WORKSPACE_VERSION_STRING "${PROJECT_VERSION}"
 
-#cmakedefine HAVE_PACKAGEKIT "${HAVE_PACKAGEKIT}"
+#cmakedefine OS_UBUNTU 1
+#cmakedefine GLIBC_LOCALE 1
diff --git a/doc/kcontrol/formats/CMakeLists.txt b/doc/kcontrol/formats/CMakeLists.txt
deleted file mode 100644
index 36db2cbdd..000000000
--- a/doc/kcontrol/formats/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-########### install files ###############
-kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/formats)
diff --git a/doc/kcontrol/formats/index.docbook b/doc/kcontrol/formats/index.docbook
deleted file mode 100644
index 194462743..000000000
--- a/doc/kcontrol/formats/index.docbook
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" ?>
-<!DOCTYPE article PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN"
-"dtd/kdedbx45.dtd" [
-<!ENTITY % addindex "IGNORE">
-<!ENTITY % English "INCLUDE" > <!-- change language only here -->
-]>
-
-<article id="formats" lang="&language;">
-<articleinfo>
-<title>Formats</title>
-<authorgroup>
-<author>&Mike.McBride; &Mike.McBride.mail;</author>
-<author>&Krishna.Tateneni; &Krishna.Tateneni.mail;</author>
-<!-- TRANS:ROLES_OF_TRANSLATORS -->
- </authorgroup>
-
-	  <date>2015-05-18</date>
-	  <releaseinfo>Plasma 5.3</releaseinfo>
-
-	  <keywordset>
-		<keyword>KDE</keyword>
-		<keyword>Systemsettings</keyword>
-		<keyword>locale</keyword>
-		<keyword>country</keyword>
-		<keyword>language</keyword>
-		<keyword>number</keyword>
-		<keyword>currency</keyword>
-	  </keywordset>
-</articleinfo>
-
-<para>
-This module of the &kde; &systemsettings; allows you to select customization
-options that depend on the region of the world that you happen to live in.
-</para>
-
-<para>
-In most cases, you can simply select the region, and all
-options will be set in an appropriate manner.
-</para>
-
-<para>
-On the bottom of this module you can see examples how the settings look
-like and which measurement units are used. In addition to numbers, you can see 
-how currency values, dates, and times in long and short format are displayed.
-When you change any of the settings, the preview shows the effects of the
-changes before you apply them.
-</para>
-
-<para>
-The <guilabel>Region</guilabel> drop down box contains the list of available
-countries and will initially show your currently selected country. If the
-selection shows <quote>Default</quote> then you have not set a country
-and are defaulting to the Country set by the system, which will also be shown.
-</para>
-
-<para>
-In case you need different individual settings enable <guilabel>Detailed Settings</guilabel> 
-and select the country format for <guilabel>Numbers</guilabel>, <guilabel>Time</guilabel>, 
-<guilabel>Currency</guilabel>, <guilabel>Measurement Units</guilabel> or 
-<guilabel>Collation and Sorting</guilabel> rules from the drop down boxes.
-</para>
-
-</article>
diff --git a/doc/kcontrol/translations/CMakeLists.txt b/doc/kcontrol/region_language/CMakeLists.txt
similarity index 63%
rename from doc/kcontrol/translations/CMakeLists.txt
rename to doc/kcontrol/region_language/CMakeLists.txt
index f950fdb3b..1954291f1 100644
--- a/doc/kcontrol/translations/CMakeLists.txt
+++ b/doc/kcontrol/region_language/CMakeLists.txt
@@ -1,2 +1,2 @@
 ########### install files ###############
-kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/translations)
+kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/region_language)
diff --git a/doc/kcontrol/translations/go-top.png b/doc/kcontrol/region_language/go-top.png
similarity index 100%
rename from doc/kcontrol/translations/go-top.png
rename to doc/kcontrol/region_language/go-top.png
diff --git a/doc/kcontrol/translations/index.docbook b/doc/kcontrol/region_language/index.docbook
similarity index 50%
rename from doc/kcontrol/translations/index.docbook
rename to doc/kcontrol/region_language/index.docbook
index 456c25c81..a98a676d9 100644
--- a/doc/kcontrol/translations/index.docbook
+++ b/doc/kcontrol/region_language/index.docbook
@@ -5,17 +5,18 @@
 <!ENTITY % English "INCLUDE" > <!-- change language only here -->
 ]>
 
-<article id="translations" lang="&language;">
+<article id="region_language" lang="&language;">
 <articleinfo>
-<title>Language</title>
+<title>Region And Language</title>
 <authorgroup>
 <author>&Mike.McBride; &Mike.McBride.mail;</author>
 <author>&Krishna.Tateneni; &Krishna.Tateneni.mail;</author>
+<author>&Han.Young; &Han.Young.mail;</author>
 <!-- TRANS:ROLES_OF_TRANSLATORS -->
  </authorgroup>
 
-	  <date>2021-04-09</date>
-	  <releaseinfo>Plasma 5.20</releaseinfo>
+          <date>2022-06-23</date>
+          <releaseinfo>Plasma 5.26</releaseinfo>
 
 	  <keywordset>
 		<keyword>KDE</keyword>
@@ -23,17 +24,20 @@
 		<keyword>locale</keyword>
 		<keyword>country</keyword>
 		<keyword>language</keyword>
-		<keyword>translation</keyword>
-		<keyword>Language</keyword>
+		<keyword>number</keyword>
+                <keyword>translation</keyword>
+		<keyword>currency</keyword>
 	  </keywordset>
 </articleinfo>
 
 <para>
-On this page you can set your preferred languages for the &plasma; Workspace and
+On this page you can set your preferred languages and formats for the &plasma; Workspace and
 Applications to be displayed in.
 </para>
 
 <para>
+In most cases, you can simply select the language, and all
+options will be set in an appropriate manner.
 The &plasma; Workspace and &kde; Applications are written in American English and are
 translated into many different languages by teams of volunteers. These
 translations need to be installed first before you can choose to use them.
@@ -43,25 +47,26 @@ languages you want to use.</para>
 <para>As &plasma; is build upon the &Qt; libraries, you need the &Qt; translations for the selected
 languages as well to have a fully localized &GUI;.</para></note>
 
+
 <para>
-The list shows the localized language names that will
-be used when displaying the &plasma; Workspace and Applications. Because not all
-of the &plasma; Workspace and Applications may be translated into every language
-&plasma; will try to find suitable translations for you by working down the
-list until it finds a translation. If
-none of your preferred languages have a required translation then the original
-American English will be used.
+On each selection bar you can see examples how the settings look
+like and which measurement units are used. In addition to numbers, you can see 
+how currency values, dates, and times in long and short format are displayed.
+When you change any of the settings, the preview shows the effects of the
+changes before you apply them.
 </para>
 
 <para>
-You can add a language to the main list by clicking the <guibutton>Add languages...</guibutton>
-button.
-The localized language names of &systemsettings; translations
-installed and available on your system are displayed. If the language you want to use is
+Clicking on the <guilabel>Add more...</guilabel> button on the language
+sub page will show you the list of available
+languages and will initially show your currently selected language. If the
+selection shows <quote>No Language Configured</quote> then you have not set a country
+and are defaulting to the language set by the system, normally C.
+If the language you want to use is
 not shown in this list then you will need to install it using the usual method
 for your system.
-Select one or more languages in the list and click <guibutton>Add</guibutton>.
 </para>
+
 <para>You can remove a language from the main list by selecting it and then clicking
 on the <inlinemediaobject><imageobject><imagedata fileref="list-remove.png" format="PNG"/>
 </imageobject></inlinemediaobject> icon. You can change the order of preference in the
@@ -70,12 +75,20 @@ list by selecting a language and clicking on the <inlinemediaobject><imageobject
 </imageobject></inlinemediaobject> icon.
 </para>
 
+<para>
+In case you need different individual format settings,
+you can select the locale for <guilabel>Numbers</guilabel>, <guilabel>Time</guilabel>,
+<guilabel>Currency</guilabel> and <guilabel>Measurement Units</guilabel>.
+</para>
+
 <note>
 <para>
-Language and Formats are independent settings. Changing a language does
-<emphasis>not</emphasis> automatically change the settings for numbers,
-currency &etc; to the corresponding country or region.
+Previously, Language and Formats are independent settings. Since &plasma; 5.26, they were
+merged into this KCM called Region And Language. Changing a language does
+<emphasis>automatically change</emphasis> the settings for numbers,
+currency &etc; to the corresponding country or region. However, this change
+does not break the old configuration. The configuration file still located
+at <emphasis>~/.config/plasma-localerc</emphasis> .
 </para>
 </note>
-
 </article>
diff --git a/doc/kcontrol/region_language/list-remove.png b/doc/kcontrol/region_language/list-remove.png
new file mode 100644
index 000000000..551cddbe9
Binary files /dev/null and b/doc/kcontrol/region_language/list-remove.png differ
diff --git a/doc/kcontrol/translations/list-remove.png b/doc/kcontrol/translations/list-remove.png
deleted file mode 100644
index 4980ce171..000000000
Binary files a/doc/kcontrol/translations/list-remove.png and /dev/null differ
diff --git a/kcms/CMakeLists.txt b/kcms/CMakeLists.txt
index 53eadf845..5ce1f797c 100644
--- a/kcms/CMakeLists.txt
+++ b/kcms/CMakeLists.txt
@@ -2,7 +2,6 @@ add_subdirectory(krdb)
 
 add_subdirectory(desktoptheme)
 add_subdirectory(icons)
-add_subdirectory(translations)
 
 if(KUserFeedback_FOUND)
     add_subdirectory(feedback)
@@ -21,7 +20,7 @@ if(FONTCONFIG_FOUND)
 endif()
 
 add_subdirectory(autostart)
-add_subdirectory(formats)
+add_subdirectory(region_language)
 add_subdirectory(notifications)
 add_subdirectory(nightcolor)
 add_subdirectory(users)
diff --git a/kcms/formats/CMakeLists.txt b/kcms/formats/CMakeLists.txt
deleted file mode 100644
index ac6e7ec98..000000000
--- a/kcms/formats/CMakeLists.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-# KI18N Translation Domain for this library
-add_definitions(-DTRANSLATION_DOMAIN=\"kcmformats\")
-
-set(kcm_formats_PART_SRCS
-    kcmformats.cpp
-    localelistmodel.cpp
-    formatssettings.cpp
-    exampleutility.cpp
-    optionsmodel.cpp
-)
-
-kconfig_add_kcfg_files(kcm_formats_PART_SRCS formatssettings.kcfgc GENERATE_MOC)
-
-add_library(kcm_formats MODULE ${kcm_formats_PART_SRCS})
-
-target_link_libraries(kcm_formats
-    Qt::Core
-    KF5::ConfigCore
-    KF5::ConfigGui
-    KF5::CoreAddons
-    KF5::I18n
-    KF5::QuickAddons)
-if (QT_MAJOR_VERSION EQUAL "6")
-    target_link_libraries(kcm_formats Qt::Core5Compat) # for QTextCodec
-endif()
-
-########### install files ###############
-install(TARGETS kcm_formats  DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/kcms/systemsettings)
-install(FILES kcm_formats.desktop DESTINATION ${KDE_INSTALL_APPDIR})
-kpackage_install_package(package kcm_formats kcms)
diff --git a/kcms/formats/Messages.sh b/kcms/formats/Messages.sh
deleted file mode 100644
index 0d4787b30..000000000
--- a/kcms/formats/Messages.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /usr/bin/env bash
-$EXTRACTRC *.ui >> rc.cpp
-$XGETTEXT *.cpp -o $podir/kcmformats.pot
-rm -f rc.cpp
-
diff --git a/kcms/formats/formatssettings.kcfg b/kcms/formats/formatssettings.kcfg
deleted file mode 100644
index e6110621d..000000000
--- a/kcms/formats/formatssettings.kcfg
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
-      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
-      http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
-  <include>QtGlobal</include>
-  <include>KLocalizedString</include>
-  <kcfgfile name="plasma-localerc" />
-  <group name="Formats">
-    <entry name="useDetailed" type="Bool">
-        <default>false</default>
-    </entry>
-    <entry key="LANG" name="lang" type="String">
-        <default code="true">QString::fromLocal8Bit(qgetenv("LANG"))</default>
-    </entry>
-    <entry key="LC_NUMERIC" name="numeric" type="String">
-        <code>
-            auto lc_numeric {QString::fromLocal8Bit(qgetenv("LC_NUMERIC"))};
-            lc_numeric = lc_numeric.isEmpty() ? i18n("Default") : lc_numeric;
-        </code>
-        <default code="true">
-            lc_numeric
-        </default>
-    </entry>
-    <entry key="LC_TIME" name="time" type="String">
-        <code>
-            auto lc_time {QString::fromLocal8Bit(qgetenv("LC_TIME"))};
-            lc_time = lc_time.isEmpty() ? i18n("Default") : lc_time;
-        </code>
-        <default code="true">lc_time</default>
-    </entry>
-    <entry key="LC_MONETARY" name="monetary" type="String">
-        <code>
-            auto lc_monetary {QString::fromLocal8Bit(qgetenv("LC_MONETARY"))};
-            lc_monetary = lc_monetary.isEmpty() ? i18n("Default") : lc_monetary;
-        </code>
-        <default code="true">lc_monetary</default>
-    </entry>
-    <entry key="LC_MEASUREMENT" name="measurement" type="String">
-        <code>
-            auto lc_measurement {QString::fromLocal8Bit(qgetenv("LC_MEASUREMENT"))};
-            lc_measurement = lc_measurement.isEmpty() ? i18n("Default") : lc_measurement;
-        </code>
-        <default code="true">lc_measurement</default>
-    </entry>
-    <entry key="LC_CTYPE" name="ctype" type="String">
-        <code>
-            auto lc_ctype {QString::fromLocal8Bit(qgetenv("LC_CTYPE"))};
-            lc_ctype = lc_ctype.isEmpty() ? i18n("Default") : lc_ctype;
-        </code>
-        <default code="true">lc_ctype</default>
-    </entry>
-    <entry key="LANGUAGE" name="language" type="String">
-       <code>
-            auto language {QString::fromLocal8Bit(qgetenv("LANGUAGE"))};
-            language = language.isEmpty() ? i18n("Default") : language;
-        </code>
-        <default code="true">language</default>
-    </entry>
-  </group>
-</kcfg>
diff --git a/kcms/formats/kcmformats.cpp b/kcms/formats/kcmformats.cpp
deleted file mode 100644
index da98a4e8c..000000000
--- a/kcms/formats/kcmformats.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
-    kcmformats.cpp
-    SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas at kde.org>
-    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
-
-    SPDX-License-Identifier: GPL-2.0-or-later
-*/
-
-#include <QCollator>
-
-#include <KAboutData>
-#include <KLocalizedString>
-#include <KPluginFactory>
-#include <KSharedConfig>
-
-#include "formatssettings.h"
-#include "kcmformats.h"
-#include "localelistmodel.h"
-#include "optionsmodel.h"
-
-K_PLUGIN_CLASS_WITH_JSON(KCMFormats, "kcm_formats.json")
-
-KCMFormats::KCMFormats(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
-    : KQuickAddons::ManagedConfigModule(parent, data, args)
-    , m_optionsModel(new OptionsModel(this))
-{
-    KAboutData *aboutData = new KAboutData(QStringLiteral("kcm_formats"),
-                                           i18nc("@title", "Formats"),
-                                           QStringLiteral("0.1"),
-                                           QLatin1String(""),
-                                           KAboutLicense::LicenseKey::GPL_V2,
-                                           i18nc("@info:credit", "Copyright 2021 Han Young"));
-
-    aboutData->addAuthor(i18nc("@info:credit", "Han Young"), i18nc("@info:credit", "Author"), QStringLiteral("hanyoung at protonmail.com"));
-
-    setAboutData(aboutData);
-    setQuickHelp(i18n("You can configure the formats used for time, dates, money and other numbers here."));
-
-    qmlRegisterAnonymousType<FormatsSettings>("kcmformats", 1);
-    qmlRegisterType<LocaleListModel>("LocaleListModel", 1, 0, "LocaleListModel");
-    qmlRegisterAnonymousType<OptionsModel>("kcmformats_optionsmodel", 1);
-}
-
-FormatsSettings *KCMFormats::settings() const
-{
-    return m_optionsModel->settings();
-}
-
-OptionsModel *KCMFormats::optionsModel() const
-{
-    return m_optionsModel;
-}
-QQuickItem *KCMFormats::getSubPage(int index) const
-{
-    return subPage(index);
-}
-
-void KCMFormats::unset(const QString &setting)
-{
-    const char *entry;
-    if (setting == QStringLiteral("lang")) {
-        entry = "LANG";
-        settings()->setLang(settings()->defaultLangValue());
-    } else if (setting == QStringLiteral("numeric")) {
-        entry = "LC_NUMERIC";
-        settings()->setNumeric(settings()->defaultNumericValue());
-    } else if (setting == QStringLiteral("time")) {
-        entry = "LC_TIME";
-        settings()->setTime(settings()->defaultTimeValue());
-    } else if (setting == QStringLiteral("measurement")) {
-        entry = "LC_MEASUREMENT";
-        settings()->setMeasurement(settings()->defaultMeasurementValue());
-    } else {
-        entry = "LC_MONETARY";
-        settings()->setMonetary(settings()->defaultMonetaryValue());
-    }
-    settings()->config()->group(QStringLiteral("Formats")).deleteEntry(entry);
-}
-#include "kcmformats.moc"
-#include "moc_kcmformats.cpp"
diff --git a/kcms/formats/kcmformats.h b/kcms/formats/kcmformats.h
deleted file mode 100644
index 0b6abaf42..000000000
--- a/kcms/formats/kcmformats.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-    kcmformats.h
-    SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas at kde.org>
-    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
-
-    SPDX-License-Identifier: GPL-2.0-or-later
-*/
-
-#pragma once
-
-#include <KConfigGroup>
-#include <KQuickAddons/ManagedConfigModule>
-
-class FormatsSettings;
-class OptionsModel;
-class KCMFormats : public KQuickAddons::ManagedConfigModule
-{
-    Q_OBJECT
-    Q_PROPERTY(FormatsSettings *settings READ settings CONSTANT)
-    Q_PROPERTY(OptionsModel *optionsModel READ optionsModel CONSTANT)
-public:
-    explicit KCMFormats(QObject *parent, const KPluginMetaData &data, const QVariantList &list = QVariantList());
-    virtual ~KCMFormats() override = default;
-
-    FormatsSettings *settings() const;
-    OptionsModel *optionsModel() const;
-    Q_INVOKABLE QQuickItem *getSubPage(int index) const; // proxy from KQuickAddons to Qml
-    Q_INVOKABLE void unset(const QString &setting);
-
-private:
-    QHash<QString, QString> m_cachedFlags;
-
-    OptionsModel *m_optionsModel = nullptr;
-};
diff --git a/kcms/formats/localelistmodel.cpp b/kcms/formats/localelistmodel.cpp
deleted file mode 100644
index 598c190f0..000000000
--- a/kcms/formats/localelistmodel.cpp
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- *  localelistmodel.cpp
- *  Copyright 2014 Sebastian Kügler <sebas at kde.org>
- *  Copyright 2021 Han Young <hanyoung at protonmail.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- */
-#include "localelistmodel.h"
-#include "exampleutility.cpp"
-#include "kcmformats.h"
-#include <KLocalizedString>
-#include <QTextCodec>
-LocaleListModel::LocaleListModel()
-{
-    QList<QLocale> m_locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
-    m_localeTuples.reserve(m_locales.size() + 1);
-    m_localeTuples.push_back(std::tuple<QString, QString, QString, QLocale>(i18n("Default"), i18n("Default"), i18n("Default"), QLocale()));
-    for (auto &locale : m_locales) {
-        m_localeTuples.push_back(
-            std::tuple<QString, QString, QString, QLocale>(locale.nativeLanguageName(), locale.nativeCountryName(), locale.name(), locale));
-    }
-}
-int LocaleListModel::rowCount(const QModelIndex &parent) const
-{
-    Q_UNUSED(parent)
-    if (m_noFilter) {
-        return m_localeTuples.size();
-    } else {
-        return m_filteredLocales.size();
-    }
-}
-QVariant LocaleListModel::data(const QModelIndex &index, int role) const
-{
-    int tupleIndex;
-    if (m_noFilter) {
-        tupleIndex = index.row();
-    } else {
-        tupleIndex = m_filteredLocales.at(index.row());
-    }
-
-    const auto &[lang, country, name, locale] = m_localeTuples.at(tupleIndex);
-    switch (role) {
-    case FlagIcon: {
-        QString flagCode;
-        const QStringList split = name.split(QLatin1Char('_'));
-        if (split.count() > 1) {
-            flagCode = split[1].toLower();
-        }
-        auto flagIconPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
-                                                   QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/locale/countries/%1/flag.png").arg(flagCode));
-        return flagIconPath;
-    }
-    case DisplayName: {
-        const QString clabel = !country.isEmpty() ? country : QLocale::countryToString(locale.country());
-        if (!lang.isEmpty()) {
-            return lang + QStringLiteral(" (") + clabel + QStringLiteral(")");
-        } else {
-            return name + QStringLiteral(" (") + clabel + QStringLiteral(")");
-        }
-    }
-    case LocaleName: {
-        QString cvalue = name;
-        if (!cvalue.contains(QLatin1Char('.')) && cvalue != QLatin1Char('C')) {
-            // explicitly add the encoding,
-            // otherwise Qt doesn't accept dead keys and garbles the output as well
-            cvalue.append(QLatin1Char('.') + QTextCodec::codecForLocale()->name());
-        }
-        return cvalue;
-    }
-    case Example: {
-        switch (m_configType) {
-        case Lang:
-            return QVariant();
-        case Numeric:
-            return Utility::numericExample(locale);
-        case Time:
-            return Utility::shortTimeExample(locale);
-        case Currency:
-            return Utility::monetaryExample(locale);
-        case Measurement:
-            return Utility::measurementExample(locale);
-        case Collate:
-            return Utility::collateExample(locale);
-        default:
-            return QVariant();
-        }
-    }
-    default:
-        return QVariant();
-    }
-}
-
-QHash<int, QByteArray> LocaleListModel::roleNames() const
-{
-    return {{LocaleName, "localeName"}, {DisplayName, "display"}, {FlagIcon, "flag"}, {Example, "example"}};
-}
-
-const QString &LocaleListModel::filter() const
-{
-    return m_filter;
-}
-
-void LocaleListModel::setFilter(const QString &filter)
-{
-    if (m_filter == filter) {
-        return;
-    }
-    m_filter = filter;
-    filterLocale();
-}
-
-void LocaleListModel::filterLocale()
-{
-    beginResetModel();
-    if (!m_filter.isEmpty()) {
-        m_filteredLocales.clear();
-        int i{0};
-        for (const auto &[lang, country, name, _locale] : m_localeTuples) {
-            if (lang.indexOf(m_filter, 0, Qt::CaseInsensitive) != -1) {
-                m_filteredLocales.push_back(i);
-            } else if (country.indexOf(m_filter, 0, Qt::CaseInsensitive) != -1) {
-                m_filteredLocales.push_back(i);
-            } else if (name.indexOf(m_filter, 0, Qt::CaseInsensitive) != -1) {
-                m_filteredLocales.push_back(i);
-            }
-            i++;
-        }
-        m_noFilter = false;
-    } else {
-        m_noFilter = true;
-    }
-    endResetModel();
-}
-
-QString LocaleListModel::selectedConfig() const
-{
-    switch (m_configType) {
-    case Lang:
-        return QStringLiteral("lang");
-    case Numeric:
-        return QStringLiteral("numeric");
-    case Time:
-        return QStringLiteral("time");
-    case Currency:
-        return QStringLiteral("currency");
-    case Measurement:
-        return QStringLiteral("measurement");
-    case Collate:
-        return QStringLiteral("collate");
-    }
-    // won't reach here
-    return QString();
-}
-
-void LocaleListModel::setSelectedConfig(const QString &config)
-{
-    if (config == QStringLiteral("lang")) {
-        m_configType = Lang;
-    } else if (config == QStringLiteral("numeric")) {
-        m_configType = Numeric;
-    } else if (config == QStringLiteral("time")) {
-        m_configType = Time;
-    } else if (config == QStringLiteral("measurement")) {
-        m_configType = Measurement;
-    } else if (config == QStringLiteral("currency")) {
-        m_configType = Currency;
-    } else {
-        m_configType = Collate;
-    }
-    Q_EMIT selectedConfigChanged();
-    Q_EMIT dataChanged(createIndex(0, 0), createIndex(rowCount(), 0), QVector<int>(1, Example));
-}
diff --git a/kcms/formats/optionsmodel.cpp b/kcms/formats/optionsmodel.cpp
deleted file mode 100644
index 7110f1079..000000000
--- a/kcms/formats/optionsmodel.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
-    optionsmodel.cpp
-    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
-
-    SPDX-License-Identifier: GPL-2.0-or-later
-*/
-#include <KLocalizedString>
-
-#include "exampleutility.cpp"
-#include "formatssettings.h"
-#include "optionsmodel.h"
-
-OptionsModel::OptionsModel(QObject *parent)
-    : QAbstractListModel(parent)
-    , m_settings(new FormatsSettings(this))
-{
-    m_staticNames = {{{i18n("Region"), QStringLiteral("lang")},
-                      {i18n("Numbers"), QStringLiteral("numeric")},
-                      {i18n("Time"), QStringLiteral("time")},
-                      {i18n("Currency"), QStringLiteral("currency")},
-                      {i18n("Measurement"), QStringLiteral("measurement")}}};
-    connect(m_settings, &FormatsSettings::langChanged, this, &OptionsModel::handleLangChange);
-    connect(m_settings, &FormatsSettings::numericChanged, this, [this] {
-        Q_EMIT dataChanged(createIndex(1, 0), createIndex(1, 0), {Subtitle, Example});
-    });
-    connect(m_settings, &FormatsSettings::timeChanged, this, [this] {
-        Q_EMIT dataChanged(createIndex(2, 0), createIndex(2, 0), {Subtitle, Example});
-    });
-    connect(m_settings, &FormatsSettings::monetaryChanged, this, [this] {
-        Q_EMIT dataChanged(createIndex(3, 0), createIndex(3, 0), {Subtitle, Example});
-    });
-    connect(m_settings, &FormatsSettings::measurementChanged, this, [this] {
-        Q_EMIT dataChanged(createIndex(4, 0), createIndex(4, 0), {Subtitle, Example});
-    });
-}
-int OptionsModel::rowCount(const QModelIndex &parent) const
-{
-    Q_UNUSED(parent)
-    return m_staticNames.size();
-}
-QVariant OptionsModel::data(const QModelIndex &index, int role) const
-{
-    const int row = index.row();
-    if (row < 0 || row >= (int)m_staticNames.size())
-        return QVariant();
-
-    switch (role) {
-    case Name:
-        return m_staticNames[row].first;
-    case Subtitle: {
-        switch (row) {
-        case 0:
-            return m_settings->lang();
-        case 1:
-            return m_settings->numeric();
-        case 2:
-            return m_settings->time();
-        case 3:
-            return m_settings->monetary();
-        case 4:
-            return m_settings->measurement();
-        default:
-            return QVariant();
-        }
-    }
-    case Example: {
-        switch (row) {
-        case 0:
-            return QString();
-        case 1:
-            return numberExample();
-        case 2:
-            return timeExample();
-        case 3:
-            return currencyExample();
-        case 4:
-            return measurementExample();
-        default:
-            return QVariant();
-        }
-    }
-    case Page:
-        return m_staticNames[row].second;
-    default:
-        return QVariant();
-    }
-}
-
-QHash<int, QByteArray> OptionsModel::roleNames() const
-{
-    return {{Name, "name"}, {Subtitle, "localeName"}, {Example, "example"}, {Page, "page"}};
-}
-
-void OptionsModel::handleLangChange()
-{
-    Q_EMIT dataChanged(createIndex(0, 0), createIndex(0, 0), {Subtitle, Example});
-
-    QString defaultVal = i18n("Default");
-    if (m_settings->numeric() == defaultVal) {
-        Q_EMIT dataChanged(createIndex(1, 0), createIndex(1, 0), {Subtitle, Example});
-    }
-    if (m_settings->time() == defaultVal) {
-        Q_EMIT dataChanged(createIndex(2, 0), createIndex(2, 0), {Subtitle, Example});
-    }
-    if (m_settings->measurement() == defaultVal) {
-        Q_EMIT dataChanged(createIndex(3, 0), createIndex(3, 0), {Subtitle, Example});
-    }
-    if (m_settings->monetary() == defaultVal) {
-        Q_EMIT dataChanged(createIndex(4, 0), createIndex(4, 0), {Subtitle, Example});
-    }
-}
-
-QString OptionsModel::numberExample() const
-{
-    return Utility::numericExample(localeWithDefault(m_settings->numeric()));
-}
-QString OptionsModel::timeExample() const
-{
-    return Utility::timeExample(localeWithDefault(m_settings->time()));
-}
-QString OptionsModel::currencyExample() const
-{
-    return Utility::monetaryExample(localeWithDefault(m_settings->monetary()));
-}
-QString OptionsModel::measurementExample() const
-{
-    return Utility::measurementExample(localeWithDefault(m_settings->measurement()));
-}
-QLocale OptionsModel::localeWithDefault(const QString &val) const
-{
-    if (val != i18n("Default")) {
-        return QLocale(val);
-    } else {
-        return QLocale(m_settings->lang());
-    }
-}
-FormatsSettings *OptionsModel::settings() const
-{
-    return m_settings;
-}
diff --git a/kcms/formats/package/contents/ui/main.qml b/kcms/formats/package/contents/ui/main.qml
deleted file mode 100644
index 43dedfd3c..000000000
--- a/kcms/formats/package/contents/ui/main.qml
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
-  SPDX-FileCopyrightLabel: 2021 Han Young <hanyoung at protonmail.com>
-
-  SPDX-License-Identifier: LGPL-3.0-or-later
-*/
-import QtQuick 2.15
-import QtQuick.Controls 2.15 as QQC2
-import QtQuick.Layouts 1.15
-
-import org.kde.kirigami 2.15 as Kirigami
-import org.kde.kcm 1.2 as KCM
-import LocaleListModel 1.0
-
-KCM.ScrollViewKCM {
-    id: root
-    implicitHeight: Kirigami.Units.gridUnit * 40
-    implicitWidth: Kirigami.Units.gridUnit * 20
-    header: Kirigami.InlineMessage {
-        id: helpMsg
-        text: i18n("Your changes will take effect the next time you log in.")
-    }
-
-    view: ListView {
-        model: kcm.optionsModel
-        delegate: Kirigami.BasicListItem {
-            text: model.name
-            subtitle: model.localeName
-            trailing: QQC2.Label {
-                text: model.example
-            }
-            reserveSpaceForSubtitle: true
-            onClicked: {
-                if (kcm.depth === 1) {
-                    localeListPage.active = true;
-                    localeListPage.item.setting = page;
-                    kcm.push(localeListPage.item);
-                } else {
-                    kcm.getSubPage(0).setting = page;
-                    kcm.getSubPage(0).filterText = '';
-                    kcm.currentIndex = 1;
-                }
-            }
-        }
-    }
-
-    Loader {
-        id: localeListPage
-        active: false
-        sourceComponent: KCM.ScrollViewKCM {
-            property string setting: "lang"
-            property alias filterText: searchField.text
-            title: {
-                localeListView.currentIndex = -1;
-                localeListModel.selectedConfig = setting;
-                switch (setting) {
-                case "lang":
-                    return i18n("Region");
-                case "numeric":
-                    return i18n("Numbers");
-                case "time":
-                    return i18n("Time");
-                case "currency":
-                    return i18n("Currency");
-                case "measurement":
-                    return i18n("Measurement");
-                case "collate":
-                    return i18n("Collate");
-                }
-            }
-
-            LocaleListModel {
-                id: localeListModel
-            }
-
-            header: Kirigami.SearchField {
-                id: searchField
-                Layout.fillWidth: true
-                onTextChanged: localeListModel.filter = text
-            }
-
-            view: ListView {
-                id: localeListView
-                clip: true
-                model: localeListModel
-                delegate: Kirigami.BasicListItem {
-                    icon: model.flag
-                    text: model.display
-                    subtitle: model.localeName
-                    trailing: QQC2.Label {
-                        color: Kirigami.Theme.disabledTextColor
-                        text: model.example ? model.example : ''
-                    }
-                    onClicked: {
-                        if (model.localeName !== i18n("Default")) {
-                            switch (setting) {
-                            case "lang":
-                                kcm.settings.lang = localeName;
-                                break;
-                            case "numeric":
-                                kcm.settings.numeric = localeName;
-                                break;
-                            case "time":
-                                kcm.settings.time = localeName;
-                                break;
-                            case "currency":
-                                kcm.settings.monetary = localeName;
-                                break;
-                            case "measurement":
-                                kcm.settings.measurement = localeName;
-                                break;
-                            case "collate":
-                                kcm.settings.collate = localeName;
-                                break;
-                            }
-                        } else {
-                            kcm.unset(setting);
-                        }
-
-                        kcm.currentIndex = 0;
-                        helpMsg.visible = true;
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/kcms/region_language/CMakeLists.txt b/kcms/region_language/CMakeLists.txt
new file mode 100644
index 000000000..df3215128
--- /dev/null
+++ b/kcms/region_language/CMakeLists.txt
@@ -0,0 +1,75 @@
+#CMakeLists.txt
+# SPDX-License-Identifier: BSD-2-Clause
+# SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+# KI18N Translation Domain for this library
+add_definitions(-DTRANSLATION_DOMAIN=\"kcm_regionandlang\")
+################## Set sources files #################
+set(kcm_regionandlang_PART_SRCS
+    settingtype.h
+    kcmregionandlang.cpp
+    localelistmodel.cpp
+    exampleutility.cpp
+    optionsmodel.cpp
+    languagelistmodel.cpp
+    localegenerator.cpp
+    localegeneratorbase.cpp
+    regionandlangsettings.cpp
+)
+kconfig_add_kcfg_files(kcm_regionandlang_PART_SRCS regionandlangsettingsbase.kcfgc GENERATE_MOC)
+
+if(GLIBC_LOCALE_GEN)
+    set(kcm_regionandlang_PART_SRCS
+        ${kcm_regionandlang_PART_SRCS}
+        localegeneratorglibc.cpp)
+endif()
+if(UBUNTU_PACKAGEKIT)
+    set(kcm_regionandlang_PART_SRCS
+        ${kcm_regionandlang_PART_SRCS}
+        localegeneratorubuntu.cpp)
+endif()
+ecm_qt_declare_logging_category(
+    kcm_regionandlang_PART_SRCS
+    HEADER kcm_regionandlang_debug.h
+    IDENTIFIER KCM_REGIONANDLANG
+    CATEGORY_NAME org.kde.kcm_regionandlang
+    DESCRIPTION "Region and Language KCM"
+    EXPORT kcm_regionandlang
+)
+
+################ Build helper and add additional source files #############
+if(REGION_LANG_GENERATE_LOCALE)
+    add_subdirectory("localegenhelper")
+    qt5_generate_dbus_interface(
+        ${CMAKE_CURRENT_SOURCE_DIR}/localegenhelper/localegenhelper.h
+        org.kde.localegenhelper.xml
+        OPTIONS -s -m
+    )
+    qt5_add_dbus_interface(kcm_regionandlang_PART_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.localegenhelper.xml localegenhelperinterface)
+endif()
+
+#################### Declare target #######################
+kcoreaddons_add_plugin(kcm_regionandlang SOURCES ${kcm_regionandlang_PART_SRCS} INSTALL_NAMESPACE "plasma/kcms/systemsettings")
+
+ecm_qt_install_logging_categories(
+    EXPORT kcm_regionandlang
+    DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
+)
+
+################# Link libraries ####################
+target_link_libraries(kcm_regionandlang
+    Qt::Core
+    Qt::DBus
+    KF5::I18n
+    KF5::KCMUtils
+    KF5::QuickAddons
+    KF5::ItemModels)
+if (QT_MAJOR_VERSION EQUAL "6")
+    target_link_libraries(kcm_formats Qt::Core5Compat) # for QTextCodec
+endif()
+if(UBUNTU_PACKAGEKIT)
+    target_link_libraries(kcm_regionandlang PK::packagekitqt${QT_MAJOR_VERSION})
+endif()
+########### install files ###############
+install(FILES kcm_regionandlang.desktop DESTINATION ${KDE_INSTALL_APPDIR})
+kpackage_install_package(package kcm_regionandlang kcms)
diff --git a/kcms/region_language/Messages.sh b/kcms/region_language/Messages.sh
new file mode 100644
index 000000000..78b2e9137
--- /dev/null
+++ b/kcms/region_language/Messages.sh
@@ -0,0 +1,8 @@
+#! /usr/bin/env bash
+#Messages.sh
+#SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+#SPDX-License-Identifier: GPL-2.0-or-later
+
+$EXTRACTRC `find . -name \*.kcfg` >> rc.cpp
+$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/kcm_regionandlang.pot
+
diff --git a/kcms/formats/exampleutility.cpp b/kcms/region_language/exampleutility.cpp
similarity index 52%
rename from kcms/formats/exampleutility.cpp
rename to kcms/region_language/exampleutility.cpp
index 13b40d2e4..49e5e722f 100644
--- a/kcms/formats/exampleutility.cpp
+++ b/kcms/region_language/exampleutility.cpp
@@ -5,31 +5,7 @@
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
-#include <KLocalizedString>
-#include <QCollator>
-#include <QDateTime>
-#include <QLocale>
-
-class Utility
-{
-public:
-    template<class T>
-    inline static QString collateExample(const T &collate);
-    inline static QString numericExample(const QLocale &locale);
-    inline static QString timeExample(const QLocale &locale);
-    inline static QString shortTimeExample(const QLocale &locale);
-    inline static QString measurementExample(const QLocale &locale);
-    inline static QString monetaryExample(const QLocale &locale);
-};
-
-template<class T>
-QString Utility::collateExample(const T &collate)
-{
-    auto example{QStringLiteral("abcdefgxyzABCDEFGXYZÅåÄäÖöÅåÆæØø")};
-    auto collator{QCollator{collate}};
-    std::sort(example.begin(), example.end(), collator);
-    return example;
-}
+#include "exampleutility.h"
 
 QString Utility::monetaryExample(const QLocale &locale)
 {
@@ -38,8 +14,7 @@ QString Utility::monetaryExample(const QLocale &locale)
 
 QString Utility::timeExample(const QLocale &locale)
 {
-    return i18n("%1 (long format)", locale.toString(QDateTime::currentDateTime())) + QLatin1Char('\n')
-        + i18n("%1 (short format)", locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat));
+    return locale.toString(QDateTime::currentDateTime()) + QLatin1Char('\n') + locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat);
 }
 
 QString Utility::shortTimeExample(const QLocale &locale)
diff --git a/kcms/region_language/exampleutility.h b/kcms/region_language/exampleutility.h
new file mode 100644
index 000000000..102e4a596
--- /dev/null
+++ b/kcms/region_language/exampleutility.h
@@ -0,0 +1,22 @@
+/*
+    exampleutility.h
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <KLocalizedString>
+#include <QDateTime>
+#include <QLocale>
+
+class Utility
+{
+public:
+    static QString numericExample(const QLocale &locale);
+    static QString timeExample(const QLocale &locale);
+    static QString shortTimeExample(const QLocale &locale);
+    static QString measurementExample(const QLocale &locale);
+    static QString monetaryExample(const QLocale &locale);
+};
diff --git a/kcms/region_language/kcm_regionandlang.desktop b/kcms/region_language/kcm_regionandlang.desktop
new file mode 100644
index 000000000..b32a2c413
--- /dev/null
+++ b/kcms/region_language/kcm_regionandlang.desktop
@@ -0,0 +1,82 @@
+[Desktop Entry]
+Name=Region & Language
+Name[ar]=التنسيقات
+Name[az]=Formatlar
+Name[ca]=Formats
+Name[cs]=Formáty
+Name[da]=Formater
+Name[de]=Formate
+Name[en_GB]=Formats
+Name[es]=Formatos
+Name[eu]=Formatuak
+Name[fi]=Muotoilu
+Name[fr]=Formats
+Name[hi]=प्रारूप 
+Name[hsb]=Formaty
+Name[hu]=Formátumok
+Name[ia]=Formatos
+Name[id]=Format
+Name[it]=Formati
+Name[ja]=形式
+Name[ko]=형식
+Name[lt]=Formatai
+Name[ml]=ഫോർമാറ്റുകൾ
+Name[nl]=Formaten
+Name[nn]=Format
+Name[pa]=ਫਾਰਮੈਟ
+Name[pl]=Formaty
+Name[pt]=Formatos
+Name[pt_BR]=Formatos
+Name[ro]=Formate
+Name[ru]=Форматы
+Name[sk]=Formáty
+Name[sl]=Formati
+Name[sv]=Format
+Name[ta]=படிவங்கள்
+Name[tr]=Biçimler
+Name[uk]=Формати
+Name[vi]=Dạng thức
+Name[x-test]=xxFormatsxx
+Name[zh_CN]=格式
+Comment=Set Language, Numeric, Currency and Time Formats
+Comment[ar]=تنسيقات الأرقام والعملات والوقت 
+Comment[az]=Say, Valyuta və Vaxt formatları
+Comment[ca]=Format numèric, monetari i horari
+Comment[cs]=Formáty čísel, měny a času
+Comment[da]=Formater for tal, valuta og tid
+Comment[de]=Formate für Zahlen, Währung und Zeitangaben
+Comment[en_GB]=Numeric, Currency and Time Formats
+Comment[es]=Formatos numérico, de moneda y de hora
+Comment[eu]=Zenbakizko, diruzko eta denborazko formatuak
+Comment[fi]=Lukujen, rahasummien ja ajanilmausten muotoilu
+Comment[fr]=Format numérique, de monnaie et d'heure
+Comment[hi]=संख्यात्मक, मुद्रा और समय प्रारूप
+Comment[hsb]=Numeriske, pjenježne a časowe formaty
+Comment[hu]=Numerikus,Pénznem- és időformátumok
+Comment[ia]=Formatos numeric, de numerario e de tempore
+Comment[it]=Formati numerici, delle valute e dell'orario
+Comment[ko]=숫자, 통화, 시간 형식
+Comment[lt]=Skaitmenų, valiutos ir laiko formatai
+Comment[ml]=സംഖ്യ, കറൻസി, സമയ ഫോർമാറ്റുകൾ
+Comment[nl]=Indeling voor numerieke getallen, valuta en tijd
+Comment[nn]=Format for tal, valuta og tid
+Comment[pa]=ਅੰਕੀ, ਕਰੰਸੀ ਅਤੇ ਸਮੇਂ ਦੇ ਫਾਰਮੈਟ
+Comment[pl]=Formaty liczb, waluty i czasu
+Comment[pt]=Formatos Numéricos, Monetários e de Datas
+Comment[pt_BR]=Formatos de data/hora, numérico e monetário
+Comment[ro]=Formate numerice, valutare și orare
+Comment[ru]=Форматы записи чисел, времени и денежных сумм
+Comment[sk]=Numerické, mena a časové formáty
+Comment[sl]=Številski, valuta in oblike časa
+Comment[sv]=Numeriska format, valutaformat och tidsformat
+Comment[ta]=எண்களும் பண மதிப்புகளும் நேரங்களும் எழுதப்படும் விதம்
+Comment[tr]=Sayısal, Para Birimi ve Saat Biçimleri
+Comment[uk]=Формати запису чисел, грошових сум та часу
+Comment[vi]=Dạng thức số, tiền tệ và thời gian
+Comment[x-test]=xxNumeric, Currency and Time Formatsxx
+Comment[zh_CN]=数字、货币和时间格式
+
+Icon=preferences-desktop-locale
+Type=Application
+Exec=systemsettings kcm_regionandlang
+NoDisplay=true
diff --git a/kcms/formats/kcm_formats.json b/kcms/region_language/kcm_regionandlang.json
similarity index 98%
rename from kcms/formats/kcm_formats.json
rename to kcms/region_language/kcm_regionandlang.json
index fedaee398..862c911e9 100644
--- a/kcms/formats/kcm_formats.json
+++ b/kcms/region_language/kcm_regionandlang.json
@@ -1,6 +1,6 @@
 {
     "KPlugin": {
-        "Description": "Numeric, Currency and Time Formats",
+        "Description": "Language Setting, Numeric, Currency and Time Formats",
         "Description[ar]": "تنسيقات الأرقام والعملات والوقت ",
         "Description[az]": "Say, Valyuta və Vaxt formatları",
         "Description[bg]": "Формат на числа, валута и време",
@@ -44,8 +44,8 @@
             "desktop"
         ],
         "Icon": "preferences-desktop-locale",
-        "Id": "kcm_formats",
-        "Name": "Formats",
+        "Id": "kcm_regionandlang",
+        "Name": "Region & Language",
         "Name[ar]": "التنسيقات",
         "Name[az]": "Formatlar",
         "Name[bg]": "Формати",
@@ -89,7 +89,7 @@
         "Name[zh_CN]": "格式",
         "Name[zh_TW]": "格式"
     },
-    "X-DocPath": "kcontrol/formats/index.html",
+    "X-DocPath": "kcontrol/region_language/index.html",
     "X-KDE-Keywords": "language,translation,number format,locale,Country,charsets,character sets,Decimal symbol,Thousands separator,symbol,separator,sign,positive,negative,currency,money,fractional digits,calendar,time,date,formats,week,week start,first,paper,size,letter,A4,measure,metric,English,Imperial,region,measurement units,collation,sorting,date format",
     "X-KDE-Keywords[ar]": "لغة,ترجمة,تنسيق رقم,لغة,بلد,أحرف,مجموعات أحرف,رمز عشري,فاصل آلاف,رمز,فاصل,علامة,موجب,سلبي,عملة,نقود,علامة جزئية,تقويم,وقت,تاريخ,تنسيقات,أسبوع,أسبوع ابدأ,أولا,ورقة,حجم,حرف,A4,قياس,متري,إنجليزي,إمبراطوري,منطقة,وحدة القياسات,تنسيق التاريخ",
     "X-KDE-Keywords[az]": "language,translation,number format,locale,Country,charsets,character sets,Decimal symbol,Thousands separator,symbol,separator,sign,positive,negative,currency,money,fractional digits,calendar,time,date,formats,week,week start,first,paper,size,letter,A4,measure,metric,English,Imperial,region,measurement units,collation,sorting,date format,dil,tərcümə,say formatı,yerli, Ölkə,tablolar,simvol dəstləri,Onluqlar simvolu,Minlik ayırıcı,simvol,ayırıcı,işarə,müsbət,mənfi,valyuta,pul,kəsirrəqəmləri,təqvim,vaxt,tarix,formatlar,həftə, həftənin başlanğıcı,ilk,kağız,ölçü,məktub,A4,ölçü,metrik,İngilis,bölgə,ölçü vahisləri,toplama,çeşidləmə,tarix formatı",
diff --git a/kcms/region_language/kcmregionandlang.cpp b/kcms/region_language/kcmregionandlang.cpp
new file mode 100644
index 000000000..1009030d8
--- /dev/null
+++ b/kcms/region_language/kcmregionandlang.cpp
@@ -0,0 +1,250 @@
+/*
+    kcmregionandlang.cpp
+    SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas at kde.org>
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#include "config-workspace.h"
+
+#include "kcmregionandlang.h"
+
+#include <QDBusConnection>
+#include <QDBusMessage>
+#include <QDBusPendingCall>
+
+#include <KLocalizedString>
+#include <KPluginFactory>
+#include <KSharedConfig>
+
+#include "languagelistmodel.h"
+#include "localegenerator.h"
+#include "localegeneratorbase.h"
+#include "localelistmodel.h"
+#include "optionsmodel.h"
+#include "regionandlangsettings.h"
+
+using namespace KCM_RegionAndLang;
+
+K_PLUGIN_CLASS_WITH_JSON(KCMRegionAndLang, "kcm_regionandlang.json")
+
+KCMRegionAndLang::KCMRegionAndLang(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
+    : KQuickAddons::ManagedConfigModule(parent, data, args)
+    , m_settings(new RegionAndLangSettings(this))
+    , m_optionsModel(new OptionsModel(this))
+    , m_generator(LocaleGenerator::getGenerator())
+{
+    connect(m_generator, &LocaleGeneratorBase::userHasToGenerateManually, this, &KCMRegionAndLang::userHasToGenerateManually);
+    connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::generateFinished);
+    connect(m_generator, &LocaleGeneratorBase::needsFont, this, &KCMRegionAndLang::requireInstallFont);
+    connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::saveToConfigFile);
+
+    // if we don't support auto locale generation for current system (BSD, musl etc.), userHasToGenerateManually regarded as success
+    if (strcmp(m_generator->metaObject()->className(), "LocaleGeneratorBase") != 0) {
+        connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::takeEffectNextTime);
+    } else {
+        connect(m_generator, &LocaleGeneratorBase::userHasToGenerateManually, this, &KCMRegionAndLang::takeEffectNextTime);
+    }
+
+    setQuickHelp(i18n("You can configure the formats used for time, dates, money and other numbers here."));
+
+    qmlRegisterAnonymousType<RegionAndLangSettings>("kcmregionandlang", 1);
+    qmlRegisterAnonymousType<OptionsModel>("kcmregionandlang", 1);
+    qmlRegisterAnonymousType<SelectedLanguageModel>("kcmregionandlang", 1);
+    qmlRegisterType<LocaleListModel>("kcmregionandlang", 1, 0, "LocaleListModel");
+    qmlRegisterType<LanguageListModel>("kcmregionandlang", 1, 0, "LanguageListModel");
+    qmlRegisterUncreatableMetaObject(KCM_RegionAndLang::staticMetaObject, "kcmregionandlang", 1, 0, "SettingType", "Error: SettingType is an enum");
+}
+
+void KCMRegionAndLang::save()
+{
+    // assemble full locales in use
+    QStringList locales;
+    if (settings()->isDefaultSetting(SettingType::Lang)) {
+        locales.append(settings()->lang());
+    }
+    if (settings()->isDefaultSetting(SettingType::Numeric)) {
+        locales.append(settings()->numeric());
+    }
+    if (settings()->isDefaultSetting(SettingType::Time)) {
+        locales.append(settings()->time());
+    }
+    if (settings()->isDefaultSetting(SettingType::Measurement)) {
+        locales.append(settings()->measurement());
+    }
+    if (settings()->isDefaultSetting(SettingType::Currency)) {
+        locales.append(settings()->monetary());
+    }
+    if (!settings()->language().isEmpty()) {
+        QStringList languages = settings()->language().split(QLatin1Char(':'));
+        for (const QString &lang : languages) {
+            QString glibcLocale = toGlibcLocale(lang);
+            if (!glibcLocale.isEmpty()) {
+                locales.append(glibcLocale);
+            }
+        }
+    }
+
+    if (!locales.isEmpty()) {
+        Q_EMIT startGenerateLocale();
+        m_generator->localesGenerate(locales);
+    }
+    Q_EMIT saveClicked();
+}
+
+void KCMRegionAndLang::saveToConfigFile()
+{
+    KQuickAddons::ManagedConfigModule::save();
+}
+
+RegionAndLangSettings *KCMRegionAndLang::settings() const
+{
+    return m_settings;
+}
+
+OptionsModel *KCMRegionAndLang::optionsModel() const
+{
+    return m_optionsModel;
+}
+
+void KCMRegionAndLang::unset(SettingType setting)
+{
+    const char *entry = nullptr;
+    if (setting == SettingType::Lang) {
+        entry = "LANG";
+        settings()->setLang(settings()->defaultLangValue());
+    } else if (setting == SettingType::Numeric) {
+        entry = "LC_NUMERIC";
+        settings()->setNumeric(settings()->defaultNumericValue());
+    } else if (setting == SettingType::Time) {
+        entry = "LC_TIME";
+        settings()->setTime(settings()->defaultTimeValue());
+    } else if (setting == SettingType::Measurement) {
+        entry = "LC_MEASUREMENT";
+        settings()->setMeasurement(settings()->defaultMeasurementValue());
+    } else {
+        entry = "LC_MONETARY";
+        settings()->setMonetary(settings()->defaultMonetaryValue());
+    }
+    settings()->config()->group(QStringLiteral("Formats")).deleteEntry(entry);
+}
+
+void KCMRegionAndLang::reboot()
+{
+    auto method = QDBusMessage::createMethodCall(QStringLiteral("org.kde.LogoutPrompt"),
+                                                 QStringLiteral("/LogoutPrompt"),
+                                                 QStringLiteral("org.kde.LogoutPrompt"),
+                                                 QStringLiteral("promptReboot"));
+    QDBusConnection::sessionBus().asyncCall(method);
+}
+
+bool KCMRegionAndLang::isGlibc()
+{
+#ifdef OS_UBUNTU
+    return true;
+#elif GLIBC_LOCALE
+    return true;
+#else
+    return false;
+#endif
+}
+
+QString KCMRegionAndLang::toGlibcLocale(const QString &lang)
+{
+    static bool inited = false;
+    static std::unordered_map<QString, QString> map;
+
+    if (!inited) {
+        map = constructGlibcLocaleMap();
+        inited = true;
+    }
+
+    if (map.count(lang)) {
+        return map[lang];
+    } else {
+        return lang;
+    }
+}
+
+std::unordered_map<QString, QString> KCMRegionAndLang::constructGlibcLocaleMap()
+{
+    std::unordered_map<QString, QString> localeMap;
+
+    QDir glibcLocaleDir(QStringLiteral("/usr/share/i18n/locales"));
+    auto availableLocales = glibcLocaleDir.entryList(QDir::Files);
+    // not glibc system or corrupted system
+    if (availableLocales.size() == 0) {
+        return localeMap;
+    }
+
+    // map base locale code to actual glibc locale filename: "en" => ["en_US", "en_GB"]
+    std::unordered_map<QString, std::vector<QString>> baseLocaleMap(availableLocales.size());
+    for (const auto &glibcLocale : availableLocales) {
+        // we want only absolute base locale code, for sr at ijekavian and en_US, we get sr and en
+        auto baseLocale = glibcLocale.split('_')[0].split('@')[0];
+        if (baseLocaleMap.count(baseLocale)) {
+            baseLocaleMap[baseLocale].push_back(glibcLocale);
+        } else {
+            baseLocaleMap.insert({baseLocale, {glibcLocale}});
+        }
+    }
+
+    auto plasmaLocales = KLocalizedString::availableDomainTranslations(QByteArrayLiteral("plasmashell")).values();
+    for (const auto &plasmaLocale : plasmaLocales) {
+        auto baseLocale = plasmaLocale.split('_')[0].split('@')[0];
+        if (baseLocaleMap.count(baseLocale)) {
+            const auto &prefixedLocales = baseLocaleMap[baseLocale];
+
+            // if we have one to one match, use that. Eg. en_US to en_US
+            auto fullMatch = std::find(prefixedLocales.begin(), prefixedLocales.end(), plasmaLocale);
+            if (fullMatch != prefixedLocales.end()) {
+                localeMap.insert({plasmaLocale, *fullMatch + ".UTF-8"});
+                continue;
+            }
+
+            // language name with same country code has higher priority, eg. es_ES > es_PA, de_DE > de_DE at euro
+            auto mainLocale = plasmaLocale + "_" + plasmaLocale.toUpper();
+            fullMatch = std::find(prefixedLocales.begin(), prefixedLocales.end(), mainLocale);
+            if (fullMatch != prefixedLocales.end()) {
+                localeMap.insert({plasmaLocale, *fullMatch + ".UTF-8"});
+                continue;
+            }
+
+            // we try to match the locale with least characters diff (compare language code with country code, "sv" with "SE".lower() for "sv_SE"),
+            // so ca at valencia matches with ca_ES at valencia
+            // bad case: ca matches with ca_AD but not ca_ES
+            int closestMatchIndex = 0;
+            float minDiffPercentage = 1.0;
+            std::array<int, 255> frequencyMap = {0};
+            for (QChar c : plasmaLocale) {
+                // to lower so "sv_SE" has higher priority than "sv_FI" for language "sv"
+                frequencyMap[int(c.toLower().toLatin1())]++;
+            }
+
+            int i = 0;
+            for (const auto &glibcLocale : prefixedLocales) {
+                auto duplicated = frequencyMap;
+                int skipBase = baseLocale.size() + 1; // we skip "sv_" part of "sv_SE", eg. compare "SE" part with "sv"
+                for (QChar c : glibcLocale) {
+                    if (skipBase--) {
+                        continue;
+                    }
+                    duplicated[int(c.toLower().toLatin1())]--;
+                }
+                int diffChars = std::reduce(duplicated.begin(), duplicated.end(), 0, [](int sum, int diff) {
+                    return sum + std::abs(diff);
+                });
+                float diffPercentage = float(diffChars) / glibcLocale.size();
+                if (diffPercentage < minDiffPercentage) {
+                    minDiffPercentage = diffPercentage;
+                    closestMatchIndex = i;
+                }
+                i++;
+            }
+            localeMap.insert({plasmaLocale, prefixedLocales[closestMatchIndex] + ".UTF-8"});
+        }
+    }
+    return localeMap;
+}
+#include "kcmregionandlang.moc"
+#include "moc_kcmregionandlang.cpp"
diff --git a/kcms/region_language/kcmregionandlang.h b/kcms/region_language/kcmregionandlang.h
new file mode 100644
index 000000000..cf6e5ec15
--- /dev/null
+++ b/kcms/region_language/kcmregionandlang.h
@@ -0,0 +1,58 @@
+/*
+    kcmregionandlang.h
+    SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas at kde.org>
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <unordered_map>
+
+#include "settingtype.h"
+
+#include <KConfigGroup>
+#include <KQuickAddons/ManagedConfigModule>
+
+class RegionAndLangSettings;
+class OptionsModel;
+class LocaleGeneratorBase;
+
+class KCMRegionAndLang : public KQuickAddons::ManagedConfigModule
+{
+    Q_OBJECT
+    Q_PROPERTY(RegionAndLangSettings *settings READ settings CONSTANT)
+    Q_PROPERTY(OptionsModel *optionsModel READ optionsModel CONSTANT)
+
+public:
+    explicit KCMRegionAndLang(QObject *parent, const KPluginMetaData &data, const QVariantList &list = QVariantList());
+    void save() override;
+
+    RegionAndLangSettings *settings() const;
+
+    OptionsModel *optionsModel() const;
+    static bool isGlibc();
+    Q_INVOKABLE void unset(KCM_RegionAndLang::SettingType setting);
+    Q_INVOKABLE static QString toGlibcLocale(const QString &lang);
+    Q_INVOKABLE void reboot();
+Q_SIGNALS:
+    void saveClicked();
+    void takeEffectNextTime();
+    void startGenerateLocale();
+    void generateFinished();
+    void requireInstallFont();
+    void userHasToGenerateManually(const QString &reason);
+
+private Q_SLOTS:
+    void saveToConfigFile();
+
+private:
+    static std::unordered_map<QString, QString> constructGlibcLocaleMap();
+
+    QHash<QString, QString> m_cachedFlags;
+
+    RegionAndLangSettings *m_settings;
+    OptionsModel *m_optionsModel;
+    LocaleGeneratorBase *m_generator = nullptr;
+};
diff --git a/kcms/region_language/languagelistmodel.cpp b/kcms/region_language/languagelistmodel.cpp
new file mode 100644
index 000000000..709aac1b6
--- /dev/null
+++ b/kcms/region_language/languagelistmodel.cpp
@@ -0,0 +1,372 @@
+/*
+    languagelistmodel.h
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens at enioka.com>
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "languagelistmodel.h"
+#include "exampleutility.h"
+#include "kcm_regionandlang_debug.h"
+#include "kcmregionandlang.h"
+#include "regionandlangsettings.h"
+
+using namespace KCM_RegionAndLang;
+
+LanguageListModel::LanguageListModel(QObject *parent)
+    : QAbstractListModel(parent)
+    , m_selectedLanguageModel(new SelectedLanguageModel(this))
+{
+    connect(this, &LanguageListModel::isPreviewExampleChanged, this, &LanguageListModel::exampleChanged);
+    connect(m_selectedLanguageModel, &SelectedLanguageModel::exampleChanged, this, &LanguageListModel::exampleChanged);
+    m_availableLanguages = KLocalizedString::availableDomainTranslations("plasmashell").values();
+    m_availableLanguages.sort();
+    m_availableLanguages.push_front(QStringLiteral("C"));
+}
+
+int LanguageListModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent)
+    return m_availableLanguages.size();
+}
+
+QVariant LanguageListModel::data(const QModelIndex &index, int role) const
+{
+    const auto row = index.row();
+    if (row < 0 || row >= m_availableLanguages.size()) {
+        return {};
+    }
+    switch (role) {
+    case NativeName:
+        return languageCodeToName(m_availableLanguages.at(row));
+    case LanguageCode:
+        return m_availableLanguages.at(row);
+    case Flag: {
+        QString flagCode;
+        const QStringList split = QLocale(m_availableLanguages.at(row)).name().split(QLatin1Char('_'));
+        if (split.size() > 1) {
+            flagCode = split[1].toLower();
+        }
+        return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/locale/countries/%1/flag.png").arg(flagCode));
+    }
+    }
+    Q_UNREACHABLE();
+    return {};
+}
+
+QHash<int, QByteArray> LanguageListModel::roleNames() const
+{
+    return {{NativeName, QByteArrayLiteral("nativeName")}, {LanguageCode, QByteArrayLiteral("languageCode")}, {Flag, QByteArrayLiteral("flag")}};
+}
+
+QString LanguageListModel::languageCodeToName(const QString &languageCode)
+{
+    const QLocale locale(languageCode);
+    const QString languageName = locale.nativeLanguageName();
+
+    if (languageName.isEmpty()) {
+        return languageCode;
+    }
+
+    if (languageCode.contains(QLatin1Char('@'))) {
+        return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode);
+    }
+
+    //    if (locale.name() != languageCode && m_availableLanguages.contains(locale.name())) {
+    //        // KDE languageCode got translated by QLocale to a locale code we also have on
+    //        // the list. Currently this only happens with pt that gets translated to pt_BR.
+    //        if (languageCode == QLatin1String("pt")) {
+    //            return QLocale(QStringLiteral("pt_PT")).nativeLanguageName();
+    //        }
+
+    //        return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode);
+    //    }
+
+    return languageName;
+}
+
+int LanguageListModel::currentIndex() const
+{
+    return m_index;
+}
+
+void LanguageListModel::setCurrentIndex(int index)
+{
+    if (index == m_index || index < 0 || index >= m_availableLanguages.size()) {
+        return;
+    }
+
+    m_index = index;
+    Q_EMIT exampleChanged();
+}
+
+QString LanguageListModel::exampleHelper(const std::function<QString(const QLocale &)> &func) const
+{
+    if (!m_settings) {
+        return {};
+    }
+
+    QString result = func(QLocale(m_settings->langWithFallback()));
+    if (m_isPreviewExample) {
+        if (m_index < 0) {
+            result = func(QLocale(m_settings->langWithFallback()));
+        } else {
+            result = func(QLocale(m_availableLanguages[m_index]));
+        }
+    }
+    return result;
+}
+
+QString LanguageListModel::numberExample() const
+{
+    return exampleHelper(Utility::numericExample);
+}
+
+QString LanguageListModel::currencyExample() const
+{
+    return exampleHelper(Utility::monetaryExample);
+}
+
+QString LanguageListModel::timeExample() const
+{
+    return exampleHelper(Utility::timeExample);
+}
+
+QString LanguageListModel::metric() const
+{
+    return exampleHelper(Utility::measurementExample);
+}
+
+void LanguageListModel::setRegionAndLangSettings(QObject *settings)
+{
+    if (auto *regionandlangsettings = dynamic_cast<RegionAndLangSettings *>(settings)) {
+        m_settings = regionandlangsettings;
+        m_selectedLanguageModel->setRegionAndLangSettings(regionandlangsettings);
+        Q_EMIT exampleChanged();
+    }
+}
+
+bool LanguageListModel::isPreviewExample() const
+{
+    return m_isPreviewExample;
+}
+
+void LanguageListModel::setIsPreviewExample(bool preview)
+{
+    m_isPreviewExample = preview;
+}
+
+void SelectedLanguageModel::setRegionAndLangSettings(RegionAndLangSettings *settings)
+{
+    m_settings = settings;
+
+    beginResetModel();
+    if (m_settings->language().isEmpty() && m_settings->isDefaultSetting(SettingType::Lang)) {
+        // no language but have lang
+        m_selectedLanguages = {m_settings->lang()};
+        m_selectedLanguages.first().remove(QStringLiteral(".UTF-8"));
+    } else if (!m_settings->language().isEmpty()) {
+        // have language, ignore lang
+        m_selectedLanguages = m_settings->language().split(QLatin1Char(':'));
+    } else {
+        // have nothing, figure out from env
+        QString lang = envLang();
+        QString language = envLanguage();
+        if (!language.isEmpty()) {
+            QStringList langlist = language.split(QLatin1Char(':'));
+            for (QString &lang : langlist) {
+                lang = lang.split(QLatin1Char('.'))[0];
+            }
+            m_selectedLanguages = langlist;
+        } else if (!lang.isEmpty()) {
+            lang.remove(QStringLiteral(".UTF-8"));
+            m_selectedLanguages = {lang};
+        }
+        m_hasImplicitLang = true;
+        Q_EMIT hasImplicitLangChanged();
+    }
+    endResetModel();
+
+    // check for invalid lang
+    if (const QString lang = KCMRegionAndLang::toGlibcLocale(m_selectedLanguages.front()); lang.isEmpty()) {
+        m_unsupportedLanguage = m_selectedLanguages.front();
+        Q_EMIT unsupportedLanguageChanged();
+    } else if (!m_unsupportedLanguage.isEmpty()) {
+        m_unsupportedLanguage.clear();
+        Q_EMIT unsupportedLanguageChanged();
+    }
+}
+
+SelectedLanguageModel *LanguageListModel::selectedLanguageModel() const
+{
+    return m_selectedLanguageModel;
+}
+
+int SelectedLanguageModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent)
+    return m_selectedLanguages.size();
+}
+
+QVariant SelectedLanguageModel::data(const QModelIndex &index, int role) const
+{
+    Q_UNUSED(role)
+    const auto row = index.row();
+    if (row < 0 || row > m_selectedLanguages.size()) {
+        return {};
+    }
+    // "add Language" Item
+    if (row == m_selectedLanguages.size()) {
+        return {};
+    }
+
+    return LanguageListModel::languageCodeToName(m_selectedLanguages.at(row));
+}
+
+bool SelectedLanguageModel::shouldWarnMultipleLang() const
+{
+    if (m_selectedLanguages.size() >= 2) {
+        if (m_selectedLanguages.front() == QStringLiteral("en_US")) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void SelectedLanguageModel::move(int from, int to)
+{
+    if (from == to || from < 0 || from >= m_selectedLanguages.size() || to < 0 || to >= m_selectedLanguages.size()) {
+        return;
+    }
+
+    if (m_hasImplicitLang) {
+        m_hasImplicitLang = false;
+        Q_EMIT hasImplicitLangChanged();
+    }
+
+    beginResetModel();
+    m_selectedLanguages.move(from, to);
+    endResetModel();
+    saveLanguages();
+    Q_EMIT shouldWarnMultipleLangChanged();
+    Q_EMIT exampleChanged();
+}
+
+void SelectedLanguageModel::remove(int index)
+{
+    if (index < 0 || index >= m_selectedLanguages.size()) {
+        return;
+    }
+    beginRemoveRows(QModelIndex(), index, index);
+    m_selectedLanguages.removeAt(index);
+    endRemoveRows();
+    saveLanguages();
+    Q_EMIT shouldWarnMultipleLangChanged();
+    Q_EMIT exampleChanged();
+}
+
+void SelectedLanguageModel::addLanguage(const QString &lang)
+{
+    if (lang.isEmpty() || m_selectedLanguages.indexOf(lang) != -1) {
+        return;
+    }
+
+    // fix Kirigami.SwipeListItem doesn't connect to Actions' visible property.
+    // Reset model enforce a refresh of delegate
+    beginResetModel();
+    if (m_hasImplicitLang) {
+        m_hasImplicitLang = false;
+        Q_EMIT hasImplicitLangChanged();
+    }
+    m_selectedLanguages.push_back(lang);
+    endResetModel();
+    saveLanguages();
+    Q_EMIT shouldWarnMultipleLangChanged();
+    Q_EMIT exampleChanged();
+}
+
+void SelectedLanguageModel::replaceLanguage(int index, const QString &lang)
+{
+    if (index < 0 || index >= m_selectedLanguages.size() || lang.isEmpty()) {
+        return;
+    }
+
+    int existLangIndex = m_selectedLanguages.indexOf(lang);
+    // return if no change, but allow change implicit lang to explicit
+    if (existLangIndex == index && !m_hasImplicitLang) {
+        return;
+    }
+
+    beginResetModel();
+    m_selectedLanguages[index] = lang;
+    if (!m_hasImplicitLang) {
+        // delete duplicate lang
+        if (existLangIndex != -1) {
+            m_selectedLanguages.removeAt(existLangIndex);
+        }
+    } else {
+        m_hasImplicitLang = false;
+        Q_EMIT hasImplicitLangChanged();
+    }
+    endResetModel();
+    saveLanguages();
+    Q_EMIT shouldWarnMultipleLangChanged();
+    Q_EMIT exampleChanged();
+}
+
+void SelectedLanguageModel::saveLanguages()
+{
+    // implicit lang means no change
+    if (!m_settings || m_hasImplicitLang) {
+        return;
+    }
+    if (m_selectedLanguages.empty()) {
+        m_settings->setLang(m_settings->defaultLangValue());
+        m_settings->config()->group(QStringLiteral("Formats")).deleteEntry("lang");
+        m_settings->config()->group(QStringLiteral("Translations")).deleteEntry("language");
+    } else {
+        QString lang = KCMRegionAndLang::toGlibcLocale(m_selectedLanguages.front());
+        if (lang.isEmpty()) {
+            m_unsupportedLanguage = m_selectedLanguages.front();
+            Q_EMIT unsupportedLanguageChanged();
+        } else {
+            if (!m_unsupportedLanguage.isEmpty()) {
+                m_unsupportedLanguage.clear();
+                Q_EMIT unsupportedLanguageChanged();
+            }
+            // don't set LANG in non glibc systems, because in non glibc systems KCMRegionAndLang::toGlibcLocale return the language itself
+            if (KCMRegionAndLang::isGlibc()) {
+                m_settings->setLang(lang);
+            }
+        }
+        QString languages;
+        for (auto i = m_selectedLanguages.cbegin(); i != m_selectedLanguages.cend(); i++) {
+            languages.push_back(*i);
+            // no ':' at end
+            if (i + 1 != m_selectedLanguages.cend()) {
+                languages.push_back(QLatin1Char(':'));
+            }
+        }
+        m_settings->setLanguage(languages);
+    }
+}
+
+bool SelectedLanguageModel::hasImplicitLang() const
+{
+    return m_hasImplicitLang;
+}
+
+const QString &SelectedLanguageModel::unsupportedLanguage() const
+{
+    return m_unsupportedLanguage;
+}
+
+QString SelectedLanguageModel::envLang() const
+{
+    return qEnvironmentVariable("LANG");
+}
+
+QString SelectedLanguageModel::envLanguage() const
+{
+    return qEnvironmentVariable("LANGUAGE");
+}
diff --git a/kcms/region_language/languagelistmodel.h b/kcms/region_language/languagelistmodel.h
new file mode 100644
index 000000000..c439f51da
--- /dev/null
+++ b/kcms/region_language/languagelistmodel.h
@@ -0,0 +1,100 @@
+/*
+    languagelistmodel.cpp
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens at enioka.com>
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <QAbstractListModel>
+
+class SelectedLanguageModel;
+class RegionAndLangSettings;
+
+class LanguageListModel : public QAbstractListModel
+{
+    Q_OBJECT
+    Q_PROPERTY(SelectedLanguageModel *selectedLanguageModel READ selectedLanguageModel CONSTANT)
+    Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+    Q_PROPERTY(QString numberExample READ numberExample NOTIFY exampleChanged)
+    Q_PROPERTY(QString currencyExample READ currencyExample NOTIFY exampleChanged)
+    Q_PROPERTY(QString timeExample READ timeExample NOTIFY exampleChanged)
+    Q_PROPERTY(QString metric READ metric NOTIFY exampleChanged)
+    Q_PROPERTY(bool isPreviewExample READ isPreviewExample WRITE setIsPreviewExample NOTIFY isPreviewExampleChanged)
+public:
+    enum Roles { NativeName = Qt::UserRole + 1, LanguageCode, Flag };
+    explicit LanguageListModel(QObject *parent = nullptr);
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+    QHash<int, QByteArray> roleNames() const override;
+
+    SelectedLanguageModel *selectedLanguageModel() const;
+
+    int currentIndex() const;
+    void setCurrentIndex(int index);
+    QString numberExample() const;
+    QString currencyExample() const;
+    QString timeExample() const;
+    QString metric() const;
+
+    // currently unused, but we need it if we want preview examples in add langauge overlay
+    bool isPreviewExample() const;
+    void setIsPreviewExample(bool preview);
+
+    Q_INVOKABLE void setRegionAndLangSettings(QObject *settings);
+Q_SIGNALS:
+    void currentIndexChanged();
+    void exampleChanged();
+    void isPreviewExampleChanged();
+
+protected:
+    friend class SelectedLanguageModel;
+    static QString languageCodeToName(const QString &languageCode);
+
+private:
+    QString exampleHelper(const std::function<QString(const QLocale &)> &func) const;
+    RegionAndLangSettings *m_settings{nullptr};
+    QList<QString> m_availableLanguages;
+    SelectedLanguageModel *m_selectedLanguageModel;
+    int m_index = -1;
+    bool m_isPreviewExample = false;
+};
+
+class SelectedLanguageModel : public QAbstractListModel
+{
+    Q_OBJECT
+    Q_PROPERTY(bool shouldWarnMultipleLang READ shouldWarnMultipleLang NOTIFY shouldWarnMultipleLangChanged)
+    Q_PROPERTY(bool hasImplicitLang READ hasImplicitLang NOTIFY hasImplicitLangChanged)
+    Q_PROPERTY(QString unsupportedLanguage READ unsupportedLanguage NOTIFY unsupportedLanguageChanged)
+public:
+    using QAbstractListModel::QAbstractListModel;
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+    void setRegionAndLangSettings(RegionAndLangSettings *settings);
+
+    bool shouldWarnMultipleLang() const;
+    bool hasImplicitLang() const;
+    const QString &unsupportedLanguage() const;
+
+    Q_INVOKABLE void move(int from, int to);
+    Q_INVOKABLE void remove(int index);
+    Q_INVOKABLE void addLanguage(const QString &lang);
+    Q_INVOKABLE void replaceLanguage(int index, const QString &lang);
+Q_SIGNALS:
+    void exampleChanged();
+    void shouldWarnMultipleLangChanged();
+    void hasImplicitLangChanged();
+    void unsupportedLanguageChanged();
+
+private:
+    QString envLang() const;
+    QString envLanguage() const;
+    void saveLanguages();
+    RegionAndLangSettings *m_settings{nullptr};
+    QList<QString> m_selectedLanguages;
+    bool m_hasImplicitLang{false};
+    QString m_unsupportedLanguage;
+};
diff --git a/kcms/region_language/localegenerator.cpp b/kcms/region_language/localegenerator.cpp
new file mode 100644
index 000000000..09dc988ef
--- /dev/null
+++ b/kcms/region_language/localegenerator.cpp
@@ -0,0 +1,30 @@
+/*
+    SPDX-FileCopyrightText: 2014 John Layt <john at layt.net>
+    SPDX-FileCopyrightText: 2018 Eike Hein <hein at kde.org>
+    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens at enioka.com>
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "config-workspace.h"
+
+#include "localegenerator.h"
+
+#include "localegeneratorbase.h"
+
+#ifdef OS_UBUNTU
+#include "localegeneratorubuntu.h"
+#elif GLIBC_LOCALE
+#include "localegeneratorglibc.h"
+#endif
+
+LocaleGeneratorBase *LocaleGenerator::getGenerator()
+{
+#ifdef OS_UBUNTU
+    static LocaleGeneratorUbuntu singleton;
+#elif GLIBC_LOCALE
+    static LocaleGeneratorGlibc singleton;
+#else
+    static LocaleGeneratorBase singleton;
+#endif
+    return &singleton;
+}
diff --git a/kcms/region_language/localegenerator.h b/kcms/region_language/localegenerator.h
new file mode 100644
index 000000000..0e03fc207
--- /dev/null
+++ b/kcms/region_language/localegenerator.h
@@ -0,0 +1,17 @@
+/*
+    SPDX-FileCopyrightText: 2014 John Layt <john at layt.net>
+    SPDX-FileCopyrightText: 2018 Eike Hein <hein at kde.org>
+    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens at enioka.com>
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+    SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#pragma once
+
+class LocaleGeneratorBase;
+
+class LocaleGenerator
+{
+public:
+    static LocaleGeneratorBase *getGenerator();
+};
diff --git a/kcms/region_language/localegeneratorbase.cpp b/kcms/region_language/localegeneratorbase.cpp
new file mode 100644
index 000000000..641973e7f
--- /dev/null
+++ b/kcms/region_language/localegeneratorbase.cpp
@@ -0,0 +1,19 @@
+/*
+    localegeneratorbase.cpp
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "localegeneratorbase.h"
+
+#include <KLocalizedString>
+
+void LocaleGeneratorBase::localesGenerate(const QStringList &list)
+{
+    Q_UNUSED(list)
+    Q_EMIT userHasToGenerateManually(i18nc("@info:warning",
+                                           "Locale has been configured, but this KCM currently "
+                                           "doesn't support auto locale generation on non-glibc systems, "
+                                           "please refer to your distribution's manual to install fonts and generate locales"));
+}
diff --git a/kcms/region_language/localegeneratorbase.h b/kcms/region_language/localegeneratorbase.h
new file mode 100644
index 000000000..ee5584d4a
--- /dev/null
+++ b/kcms/region_language/localegeneratorbase.h
@@ -0,0 +1,23 @@
+/*
+    localegeneratorbase.h
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <QObject>
+
+class LocaleGeneratorBase : public QObject
+{
+    Q_OBJECT
+public:
+    using QObject::QObject;
+    Q_INVOKABLE virtual void localesGenerate(const QStringList &list);
+
+Q_SIGNALS:
+    void success();
+    void needsFont();
+    void userHasToGenerateManually(const QString &reason);
+};
diff --git a/kcms/region_language/localegeneratorglibc.cpp b/kcms/region_language/localegeneratorglibc.cpp
new file mode 100644
index 000000000..e83f52696
--- /dev/null
+++ b/kcms/region_language/localegeneratorglibc.cpp
@@ -0,0 +1,32 @@
+/*
+    localegeneratorglibc.cpp
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "localegeneratorglibc.h"
+#include "kcm_regionandlang_debug.h"
+
+LocaleGeneratorGlibc::LocaleGeneratorGlibc(QObject *parent)
+    : LocaleGeneratorBase(parent)
+    , m_interface(new LocaleGenHelper(QStringLiteral("org.kde.localegenhelper"), QStringLiteral("/LocaleGenHelper"), QDBusConnection::systemBus(), this))
+{
+    qCDebug(KCM_REGIONANDLANG) << "connect: " << m_interface->isValid();
+    connect(m_interface, &LocaleGenHelper::success, this, [this]() {
+        Q_EMIT this->needsFont();
+    });
+    connect(m_interface, &LocaleGenHelper::error, this, &LocaleGeneratorGlibc::userHasToGenerateManually);
+}
+
+void LocaleGeneratorGlibc::localesGenerate(const QStringList &list)
+{
+    qCDebug(KCM_REGIONANDLANG) << "enable locales: " << list;
+    if (!QFile(QStringLiteral("/etc/locale.gen")).exists()) {
+        // fedora or centos
+        Q_EMIT success();
+        return;
+    }
+    qDebug() << "send polkit request";
+    m_interface->enableLocales(list);
+}
diff --git a/kcms/region_language/localegeneratorglibc.h b/kcms/region_language/localegeneratorglibc.h
new file mode 100644
index 000000000..3be7ddad4
--- /dev/null
+++ b/kcms/region_language/localegeneratorglibc.h
@@ -0,0 +1,24 @@
+/*
+    localegeneratorglibc.h
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include "localegeneratorbase.h"
+#include "localegenhelperinterface.h"
+
+using LocaleGenHelper = org::kde::localegenhelper::LocaleGenHelper;
+
+class LocaleGeneratorGlibc : public LocaleGeneratorBase
+{
+    Q_OBJECT
+public:
+    explicit LocaleGeneratorGlibc(QObject *parent = nullptr);
+    Q_INVOKABLE void localesGenerate(const QStringList &list) override;
+
+private:
+    LocaleGenHelper *m_interface = nullptr;
+};
diff --git a/kcms/region_language/localegeneratorubuntu.cpp b/kcms/region_language/localegeneratorubuntu.cpp
new file mode 100644
index 000000000..ecfeefd89
--- /dev/null
+++ b/kcms/region_language/localegeneratorubuntu.cpp
@@ -0,0 +1,101 @@
+/*
+    localegeneratorubuntu.cpp
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <PackageKit/Daemon>
+
+#include <QStandardPaths>
+
+#include <KLocalizedString>
+
+#include "kcm_regionandlang_debug.h"
+#include "localegeneratorubuntu.h"
+
+void LocaleGeneratorUbuntu::localesGenerate(const QStringList &list)
+{
+    ubuntuInstall(list);
+}
+
+void LocaleGeneratorUbuntu::ubuntuInstall(const QStringList &locales)
+{
+    m_packageIDs.clear();
+    if (!m_proc) {
+        m_proc = new QProcess(this);
+    }
+    QStringList args;
+    args.reserve(locales.size());
+    for (const auto &locale : locales) {
+        auto localeStripped = locale;
+        localeStripped.remove(QStringLiteral(".UTF-8"));
+        args.append(QStringLiteral("--language=%1").arg(localeStripped));
+    }
+    const QString binaryPath = QStandardPaths::findExecutable(QStringLiteral("check-language-support"));
+    if (!binaryPath.isEmpty()) {
+        m_proc->setProgram(binaryPath);
+        m_proc->setArguments(args);
+        connect(m_proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &LocaleGeneratorUbuntu::ubuntuLangCheck);
+        m_proc->start();
+    } else {
+        Q_EMIT userHasToGenerateManually(i18nc("@info:warning", "Can't locate executable `check-language-support`"));
+    }
+}
+
+void LocaleGeneratorUbuntu::ubuntuLangCheck(int status, QProcess::ExitStatus exit)
+{
+    Q_UNUSED(exit)
+    if (status != 0) {
+        // Something wrong with this Ubuntu, don't try further
+        Q_EMIT userHasToGenerateManually(i18nc("the arg is the output of failed check-language-support call",
+                                               "check-language-support failed, output: %1",
+                                               QString::fromUtf8(m_proc->readAllStandardOutput())));
+        return;
+    }
+    const QString output = QString::fromUtf8(m_proc->readAllStandardOutput().simplified());
+    QStringList packages = output.split(QLatin1Char(' '));
+    packages.erase(std::remove_if(packages.begin(),
+                                  packages.end(),
+                                  [](QString &i) {
+                                      return i.isEmpty();
+                                  }),
+                   packages.end());
+
+    if (!packages.isEmpty()) {
+        auto transaction = PackageKit::Daemon::resolve(packages, PackageKit::Transaction::FilterNotInstalled | PackageKit::Transaction::FilterArch);
+        connect(transaction,
+                &PackageKit::Transaction::package,
+                this,
+                [this](PackageKit::Transaction::Info info, const QString &packageID, const QString &summary) {
+                    Q_UNUSED(info);
+                    Q_UNUSED(summary);
+                    m_packageIDs << packageID;
+                });
+        connect(transaction, &PackageKit::Transaction::errorCode, this, [](PackageKit::Transaction::Error error, const QString &details) {
+            qCDebug(KCM_REGIONANDLANG) << "resolve error" << error << details;
+        });
+        connect(transaction, &PackageKit::Transaction::finished, this, [packages, this](PackageKit::Transaction::Exit status, uint code) {
+            qCDebug(KCM_REGIONANDLANG) << "resolve finished" << status << code << m_packageIDs;
+            if (m_packageIDs.size() != packages.size()) {
+                qCWarning(KCM_REGIONANDLANG) << "Not all missing packages managed to resolve!" << packages << m_packageIDs;
+                const QString packagesString = packages.join(QLatin1Char(';'));
+                Q_EMIT userHasToGenerateManually(i18nc("%1 is a list of package names", "Not all missing packages managed to resolve! %1", packagesString));
+                return;
+            }
+            auto transaction = PackageKit::Daemon::installPackages(m_packageIDs);
+            connect(transaction, &PackageKit::Transaction::errorCode, this, [this](PackageKit::Transaction::Error error, const QString &details) {
+                qCDebug(KCM_REGIONANDLANG) << "install error:" << error << details;
+                Q_EMIT userHasToGenerateManually(i18nc("%1 is a list of package names", "Failed to install package %1", details));
+            });
+            connect(transaction, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status, uint code) {
+                qCDebug(KCM_REGIONANDLANG) << "install finished:" << status << code;
+                if (status == PackageKit::Transaction::Exit::ExitSuccess) {
+                    Q_EMIT success();
+                }
+            });
+        });
+    } else {
+        Q_EMIT success();
+    }
+}
diff --git a/kcms/region_language/localegeneratorubuntu.h b/kcms/region_language/localegeneratorubuntu.h
new file mode 100644
index 000000000..2ec5a8163
--- /dev/null
+++ b/kcms/region_language/localegeneratorubuntu.h
@@ -0,0 +1,27 @@
+/*
+    localegeneratorubuntu.h
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include "localegeneratorbase.h"
+#include <QProcess>
+
+class LocaleGeneratorUbuntu : public LocaleGeneratorBase
+{
+    Q_OBJECT
+public:
+    using LocaleGeneratorBase::LocaleGeneratorBase;
+    Q_INVOKABLE void localesGenerate(const QStringList &list) override;
+
+private:
+    QProcess *m_proc{nullptr};
+    QStringList m_packageIDs;
+
+    void ubuntuInstall(const QStringList &locales);
+private Q_SLOTS:
+    void ubuntuLangCheck(int statusCode, QProcess::ExitStatus status);
+};
diff --git a/kcms/region_language/localegenhelper/CMakeLists.txt b/kcms/region_language/localegenhelper/CMakeLists.txt
new file mode 100644
index 000000000..80ce95b04
--- /dev/null
+++ b/kcms/region_language/localegenhelper/CMakeLists.txt
@@ -0,0 +1,36 @@
+#CMakeLists.txt
+# SPDX-License-Identifier: BSD-2-Clause
+# SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+
+########### Build localegen helper ############
+set(localegen_Helper_SRCS
+    localegenhelper.cpp
+)
+qt5_generate_dbus_interface(
+    ${CMAKE_CURRENT_SOURCE_DIR}/localegenhelper.h
+    org.kde.localegenhelper.xml
+    OPTIONS -s -m
+)
+qt5_add_dbus_adaptor(localegen_Helper_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.localegenhelper.xml
+                    ${CMAKE_CURRENT_SOURCE_DIR}/localegenhelper.h LocaleGenHelper)
+add_executable(plasma-localegen-helper ${localegen_Helper_SRCS})
+target_link_libraries(plasma-localegen-helper
+    Qt::DBus
+    PolkitQt5-1::Core
+    KF5::I18n
+)
+ecm_install_configured_files(INPUT org.kde.localegenhelper.service.in DESTINATION ${DBUS_SYSTEM_SERVICES_INSTALL_DIR} @ONLY)
+install(TARGETS plasma-localegen-helper ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
+
+option(USE_CMAKE_PREFIX "Use CMAKE_INSTALL_PREFIX instead of `/usr` for DBus and PolicyKit config files" ON)
+set(POLICY_FILES_INSTALL_DIR "/usr")
+if(USE_CMAKE_PREFIX)
+    if(NOT ${CMAKE_INSTALL_PREFIX} STREQUAL POLICY_FILES_INSTALL_DIR)
+        MESSAGE(WARNING "INSTALL_BROKEN_POLICY_FILES is enabled. The following files will be installed to ${CMAKE_INSTALL_PREFIX} instead of ${POLICY_FILES_INSTALL_DIR}.")
+    endif()
+    set(POLICY_FILES_INSTALL_DIR ${CMAKE_INSTALL_PREFIX})
+endif()
+
+install(FILES org.kde.localegenhelper.conf DESTINATION "${POLICY_FILES_INSTALL_DIR}/share/dbus-1/system.d")
+install(FILES org.kde.localegenhelper.policy DESTINATION "${POLICY_FILES_INSTALL_DIR}/share/polkit-1/actions")
diff --git a/kcms/region_language/localegenhelper/localegenhelper.cpp b/kcms/region_language/localegenhelper/localegenhelper.cpp
new file mode 100644
index 000000000..5fb218de9
--- /dev/null
+++ b/kcms/region_language/localegenhelper/localegenhelper.cpp
@@ -0,0 +1,187 @@
+/*
+    localegenhelper.cpp
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#include "localegenhelper.h"
+#include "localegenhelperadaptor.h"
+
+#include <KLocalizedString>
+
+#include <QDBusConnection>
+#include <QDebug>
+#include <QFile>
+#include <QTimer>
+
+#include <chrono>
+#include <set>
+
+LocaleGenHelper::LocaleGenHelper()
+    : m_authority(PolkitQt1::Authority::instance())
+{
+    new LocaleGenHelperAdaptor(this);
+    if (!QDBusConnection::systemBus().registerService(QStringLiteral("org.kde.localegenhelper"))) {
+        qWarning() << "another helper is already running";
+        QCoreApplication::instance()->exit();
+    }
+    if (!QDBusConnection::systemBus().registerObject(QStringLiteral("/LocaleGenHelper"), this)) {
+        qWarning() << "unable to register service interface to dbus";
+        QCoreApplication::instance()->exit();
+    }
+    connect(m_authority, &PolkitQt1::Authority::checkAuthorizationFinished, this, &LocaleGenHelper::enableLocalesPrivate);
+    connect(&m_timer, &QTimer::timeout, this, [] {
+        QCoreApplication::instance()->exit();
+    });
+    exitAfterTimeOut();
+}
+
+void LocaleGenHelper::enableLocales(const QStringList &locales)
+{
+    qDebug() << locales;
+    if (m_timer.isActive()) {
+        m_timer.stop();
+    }
+    if (m_isGenerating) {
+        Q_EMIT error(i18n("Another process is already running, please retry later"));
+        exitAfterTimeOut();
+        return;
+    }
+    processLocales(locales);
+    m_isGenerating = true;
+    if (shouldGenerate()) {
+        m_authority->checkAuthorization(QStringLiteral("org.kde.localegenhelper.enableLocales"),
+                                        PolkitQt1::SystemBusNameSubject(message().service()),
+                                        PolkitQt1::Authority::AllowUserInteraction);
+    } else {
+        exitAfterTimeOut();
+        Q_EMIT success();
+    }
+}
+
+void LocaleGenHelper::enableLocalesPrivate(PolkitQt1::Authority::Result result)
+{
+    qDebug() << result;
+    if (result != PolkitQt1::Authority::Result::Yes) {
+        Q_EMIT error(i18n("Unauthorized to edit locale configuration file"));
+        exitAfterTimeOut();
+        return;
+    }
+
+    // if success, handleLocaleGen will call exit
+    if (editLocaleGen()) {
+        exitAfterTimeOut();
+    }
+}
+
+bool LocaleGenHelper::shouldGenerate()
+{
+    QFile localegen(QStringLiteral("/etc/locale.gen"));
+    if (!localegen.open(QIODevice::ReadOnly)) {
+        return false;
+    }
+    m_alreadyEnabled.clear();
+    while (!localegen.atEnd()) {
+        QString locale = localegen.readLine().simplified();
+        if (!m_comment && locale == QStringLiteral("# generated by KDE Plasma Region & Language KCM")) {
+            m_comment = true;
+        }
+        if (locale.isEmpty() || locale.front() == QLatin1Char('#')) {
+            continue;
+        }
+        QStringList localeAndCharset = locale.split(QLatin1Char(' '));
+        if (localeAndCharset.size() != 2 || localeAndCharset.at(1) != QStringLiteral("UTF-8")) {
+            continue;
+        } else {
+            QString localeNameWithoutCharset = localeAndCharset.front().remove(QStringLiteral(".UTF-8"));
+            m_alreadyEnabled.insert(localeNameWithoutCharset);
+        }
+    }
+    for (const auto &locale : std::as_const(m_locales)) {
+        if (locale == QStringLiteral("C")) {
+            continue;
+        }
+        if (m_alreadyEnabled.count(locale) == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool LocaleGenHelper::editLocaleGen()
+{
+    bool result = false;
+    QFile localegen(QStringLiteral("/etc/locale.gen"));
+    if (!localegen.open(QIODevice::Append)) {
+        Q_EMIT error(i18n("Can't open file `/etc/locale.gen`"));
+        return result;
+    }
+    for (const auto &locale : std::as_const(m_locales)) {
+        if (m_alreadyEnabled.count(locale) || locale == QStringLiteral("C")) {
+            continue;
+        }
+        // start at newline first time
+        if (!m_comment) {
+            localegen.write("\n# generated by KDE Plasma Region & Language KCM\n");
+            m_comment = true;
+        }
+        localegen.write(locale.toUtf8() + ".UTF-8 UTF-8\n");
+    }
+
+    QString locale_gen = QStandardPaths::findExecutable(QStringLiteral("locale-gen"));
+    if (!locale_gen.isEmpty()) {
+        auto *process = new QProcess(this);
+        process->setProgram(QStringLiteral("locale-gen"));
+        connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this, process](int statusCode, QProcess::ExitStatus status) {
+            handleLocaleGen(statusCode, status, process);
+        });
+        process->start();
+        result = true;
+    } else {
+        Q_EMIT error(i18n("Can't locate executable `locale-gen`"));
+    }
+    return result;
+}
+
+void LocaleGenHelper::handleLocaleGen(int statusCode, QProcess::ExitStatus status, QProcess *process)
+{
+    Q_UNUSED(status)
+    if (statusCode == 0) {
+        Q_EMIT success();
+    } else {
+        QString all_error;
+        if (!process) {
+            all_error = i18n("Unknown");
+        } else {
+            all_error.append(process->readAllStandardOutput());
+            all_error.append(QChar('\n'));
+            all_error.append(process->readAllStandardError());
+        }
+        Q_EMIT error(all_error);
+    }
+    exitAfterTimeOut();
+}
+
+void LocaleGenHelper::exitAfterTimeOut()
+{
+    m_timer.start(30s);
+}
+
+void LocaleGenHelper::processLocales(const QStringList &locales)
+{
+    QStringList processedLocales = locales;
+    for (auto &locale : processedLocales) {
+        locale.remove(QStringLiteral(".UTF-8"));
+        if (locale == QStringLiteral("C")) {
+            continue;
+        }
+    }
+    m_locales = std::move(processedLocales);
+}
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+    LocaleGenHelper generator;
+    return app.exec();
+}
diff --git a/kcms/region_language/localegenhelper/localegenhelper.h b/kcms/region_language/localegenhelper/localegenhelper.h
new file mode 100644
index 000000000..7996e7c6a
--- /dev/null
+++ b/kcms/region_language/localegenhelper/localegenhelper.h
@@ -0,0 +1,46 @@
+/*
+    localegenhelper.h
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#pragma once
+#include <PolkitQt1/Authority>
+#include <QCoreApplication>
+#include <QDBusContext>
+#include <QFile>
+#include <QObject>
+#include <QProcess>
+#include <QRegularExpression>
+#include <QTimer>
+
+#include <set>
+
+using namespace std::chrono;
+class LocaleGenHelper : public QObject, protected QDBusContext
+{
+    Q_OBJECT
+    Q_CLASSINFO("D-Bus Interface", "org.kde.localegenhelper.LocaleGenHelper")
+public:
+    LocaleGenHelper();
+    Q_SCRIPTABLE void enableLocales(const QStringList &locales);
+Q_SIGNALS:
+    Q_SCRIPTABLE void success();
+    Q_SCRIPTABLE void error(const QString &msg);
+private Q_SLOTS:
+    void enableLocalesPrivate(PolkitQt1::Authority::Result result);
+
+private:
+    void handleLocaleGen(int statusCode, QProcess::ExitStatus status, QProcess *process);
+    bool editLocaleGen();
+    void exitAfterTimeOut();
+    bool shouldGenerate();
+    void processLocales(const QStringList &locales);
+
+    std::atomic<bool> m_isGenerating = false;
+    bool m_comment = false;
+    std::set<QString> m_alreadyEnabled;
+    PolkitQt1::Authority *m_authority = nullptr;
+    QStringList m_locales;
+    QTimer m_timer;
+};
diff --git a/kcms/region_language/localegenhelper/org.kde.localegenhelper.conf b/kcms/region_language/localegenhelper/org.kde.localegenhelper.conf
new file mode 100644
index 000000000..5e5f3b150
--- /dev/null
+++ b/kcms/region_language/localegenhelper/org.kde.localegenhelper.conf
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+org.kde.localegenhelper.policy
+SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+SPDX-License-Identifier: GPL-2.0-or-later
+-->
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+  <!-- Only user root can own the foo helper -->
+  <policy user="root">
+    <allow own="org.kde.localegenhelper"/>
+  </policy>
+
+  <policy context="default">
+    <allow send_destination="org.kde.localegenhelper"/>
+  </policy>
+</busconfig>
diff --git a/kcms/region_language/localegenhelper/org.kde.localegenhelper.policy b/kcms/region_language/localegenhelper/org.kde.localegenhelper.policy
new file mode 100644
index 000000000..9bc556469
--- /dev/null
+++ b/kcms/region_language/localegenhelper/org.kde.localegenhelper.policy
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+org.kde.localegenhelper.policy
+SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+SPDX-License-Identifier: GPL-2.0-or-later
+-->
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/software/polkit/policyconfig-1.dtd">
+<policyconfig>
+
+  <action id="org.kde.localegenhelper.enableLocales">
+    <message>Authentication is required to generate the system locale</message>
+    <defaults>
+      <allow_active>auth_self</allow_active>
+    </defaults>
+  </action>
+
+</policyconfig>
+
+
diff --git a/kcms/region_language/localegenhelper/org.kde.localegenhelper.service.in b/kcms/region_language/localegenhelper/org.kde.localegenhelper.service.in
new file mode 100644
index 000000000..98e2f7eb9
--- /dev/null
+++ b/kcms/region_language/localegenhelper/org.kde.localegenhelper.service.in
@@ -0,0 +1,8 @@
+#org.kde.localegenhelper.service.in
+#SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+#SPDX-License-Identifier: GPL-2.0-or-later
+
+[D-BUS Service]
+Name=org.kde.localegenhelper
+Exec=@CMAKE_INSTALL_PREFIX@/bin/plasma-localegen-helper
+User=root
diff --git a/kcms/region_language/localelistmodel.cpp b/kcms/region_language/localelistmodel.cpp
new file mode 100644
index 000000000..ef6e9aa15
--- /dev/null
+++ b/kcms/region_language/localelistmodel.cpp
@@ -0,0 +1,158 @@
+/*
+ *  localelistmodel.cpp
+ *  Copyright 2014 Sebastian Kügler <sebas at kde.org>
+ *  Copyright 2021 Han Young <hanyoung at protonmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ */
+
+#include "localelistmodel.h"
+#include "exampleutility.h"
+#include "kcmregionandlang.h"
+
+using namespace KCM_RegionAndLang;
+
+LocaleListModel::LocaleListModel(QObject *parent)
+    : QAbstractListModel(parent)
+{
+    const QList<QLocale> m_locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
+    m_localeData.reserve(m_locales.size() + 1);
+    // we use first QString for title in Unset option
+    m_localeData.push_back(LocaleData{i18n("Default for System"), QString(), QString(), QString(), i18n("Default"), QLocale()});
+    for (auto &locale : m_locales) {
+        m_localeData.push_back(LocaleData{.nativeName = locale.nativeLanguageName(),
+                                          .englishName = QLocale::languageToString(locale.language()),
+                                          .nativeCountryName = locale.nativeCountryName(),
+                                          .englishCountryName = QLocale::countryToString(locale.country()),
+                                          .countryCode = locale.name(),
+                                          .locale = locale});
+    }
+}
+
+int LocaleListModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent)
+    return m_localeData.size();
+}
+
+QVariant LocaleListModel::data(const QModelIndex &index, int role) const
+{
+    int dataIndex = index.row();
+    const auto &data = m_localeData.at(dataIndex);
+    switch (role) {
+    case FlagIcon: {
+        QString countryCode;
+        const QStringList split = data.countryCode.split(QLatin1Char('_'));
+        if (split.size() > 1) {
+            countryCode = split[1].toLower();
+        }
+        auto flagIconPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
+                                                   QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/locale/countries/%1/flag.png").arg(countryCode));
+        return flagIconPath;
+    }
+    case DisplayName: {
+        // 0 is unset option, 1 is locale C
+        if (dataIndex == 1) {
+            return data.countryCode;
+        }
+        const QString clabel = !data.nativeCountryName.isEmpty() ? data.nativeCountryName : data.englishCountryName;
+        QString languageName;
+        if (!data.nativeName.isEmpty()) {
+            languageName = data.nativeName;
+        } else {
+            languageName = data.englishName;
+        }
+        if (dataIndex == 0) {
+            return languageName;
+        }
+        return i18nc("the first is language name, the second is the country name, like 'English (America)'", "%1 (%2)", languageName, clabel);
+    }
+    case LocaleName: {
+        QString cvalue = data.countryCode;
+        if (!cvalue.contains(QLatin1Char('.')) && cvalue != QLatin1Char('C') && cvalue != i18n("Default")) {
+            // explicitly add the encoding,
+            // otherwise Qt doesn't accept dead keys and garbles the output as well
+            cvalue.append(QLatin1Char('.') + QTextCodec::codecForLocale()->name());
+        }
+        return cvalue;
+    }
+    case Example: {
+        switch (m_configType) {
+        case Lang:
+            return {};
+        case Numeric:
+            return Utility::numericExample(data.locale);
+        case Time:
+            return Utility::timeExample(data.locale);
+        case Currency:
+            return Utility::monetaryExample(data.locale);
+        case Measurement:
+            return Utility::measurementExample(data.locale);
+        }
+    }
+    case FilterRole: {
+        return data.englishCountryName.toLower() + data.nativeCountryName.toLower() + data.nativeName.toLower() + data.englishName.toLower()
+            + data.countryCode.toLower();
+    }
+    }
+    Q_UNREACHABLE();
+    return {};
+}
+
+QHash<int, QByteArray> LocaleListModel::roleNames() const
+{
+    return {{LocaleName, QByteArrayLiteral("localeName")},
+            {DisplayName, QByteArrayLiteral("display")},
+            {FlagIcon, QByteArrayLiteral("flag")},
+            {Example, QByteArrayLiteral("example")},
+            {FilterRole, QByteArrayLiteral("filter")}};
+}
+
+int LocaleListModel::selectedConfig() const
+{
+    return m_configType;
+}
+
+void LocaleListModel::setSelectedConfig(int config)
+{
+    if (config != m_configType) {
+        m_configType = static_cast<SettingType>(config);
+    }
+    Q_EMIT selectedConfigChanged();
+    Q_EMIT dataChanged(createIndex(0, 0), createIndex(rowCount(), 0), QVector<int>(1, Example));
+}
+
+void LocaleListModel::setLang(const QString &lang)
+{
+    QString tmpLang = lang;
+    bool isC = false;
+    if (lang.isEmpty()) {
+        tmpLang = qgetenv("LANG");
+        if (tmpLang.isEmpty()) {
+            tmpLang = QStringLiteral("C");
+            isC = true;
+        }
+    }
+
+    LocaleData &data = m_localeData.front();
+    if (isC) {
+        data.nativeName = i18nc("@info:title, meaning the current locale is system default(which is `C`)", "System Default C");
+    } else {
+        data.nativeName =
+            i18nc("@info:title the current locale is the default for %1, %1 is the country name", "Default for %1", QLocale(tmpLang).nativeLanguageName());
+    }
+    data.locale = QLocale(tmpLang);
+
+    Q_EMIT dataChanged(createIndex(0, 0), createIndex(0, 0));
+}
diff --git a/kcms/formats/localelistmodel.h b/kcms/region_language/localelistmodel.h
similarity index 60%
rename from kcms/formats/localelistmodel.h
rename to kcms/region_language/localelistmodel.h
index c53ea2fbe..41c2d5d1b 100644
--- a/kcms/formats/localelistmodel.h
+++ b/kcms/region_language/localelistmodel.h
@@ -16,43 +16,43 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  */
-#ifndef LOCALELISTMODEL_H
-#define LOCALELISTMODEL_H
+#pragma once
+
+#include "settingtype.h"
 
 #include <QAbstractListModel>
 #include <QLocale>
+#include <QSortFilterProxyModel>
+
+struct LocaleData {
+    QString nativeName;
+    QString englishName;
+    QString nativeCountryName;
+    QString englishCountryName;
+    QString countryCode;
+    QLocale locale;
+};
+
 class LocaleListModel : public QAbstractListModel
 {
     Q_OBJECT
-    Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged)
-    Q_PROPERTY(QString selectedConfig READ selectedConfig WRITE setSelectedConfig NOTIFY selectedConfigChanged)
+    Q_PROPERTY(int selectedConfig READ selectedConfig WRITE setSelectedConfig NOTIFY selectedConfigChanged)
 public:
-    enum RoleName { DisplayName = Qt::DisplayRole, LocaleName, FlagIcon, Example };
-    enum ConfigType { Lang, Numeric, Time, Currency, Measurement, Collate };
-    LocaleListModel();
+    enum RoleName { DisplayName = Qt::DisplayRole, LocaleName, FlagIcon, Example, FilterRole };
+    explicit LocaleListModel(QObject *parent = nullptr);
 
     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
     QHash<int, QByteArray> roleNames() const override;
-
-    const QString &filter() const;
-    void setFilter(const QString &filter);
-    QString selectedConfig() const;
-    void setSelectedConfig(const QString &config);
+    int selectedConfig() const;
+    void setSelectedConfig(int config);
+    Q_INVOKABLE void setLang(const QString &lang);
 
 Q_SIGNALS:
-    void filterChanged();
     void selectedConfigChanged();
 
 private:
-    void filterLocale();
     void getExample();
-
-    QString m_filter;
-    std::vector<std::tuple<QString, QString, QString, QLocale>> m_localeTuples; // lang, country, name
-    std::vector<int> m_filteredLocales;
-    bool m_noFilter = true;
-    ConfigType m_configType = Lang;
+    std::vector<LocaleData> m_localeData;
+    KCM_RegionAndLang::SettingType m_configType = KCM_RegionAndLang::Lang;
 };
-
-#endif // LOCALELISTMODEL_H
diff --git a/kcms/region_language/optionsmodel.cpp b/kcms/region_language/optionsmodel.cpp
new file mode 100644
index 000000000..e5dc42f9b
--- /dev/null
+++ b/kcms/region_language/optionsmodel.cpp
@@ -0,0 +1,197 @@
+/*
+    optionsmodel.cpp
+    SPDX-FileCopyrightText: 2021 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <QLocale>
+
+#include <KLocalizedString>
+
+#include "exampleutility.h"
+#include "kcmregionandlang.h"
+#include "optionsmodel.h"
+#include "settingtype.h"
+
+using namespace KCM_RegionAndLang;
+
+OptionsModel::OptionsModel(KCMRegionAndLang *parent)
+    : QAbstractListModel(parent)
+    , m_settings(parent->settings())
+{
+    m_staticNames = {{{i18nc("@info:title", "Language"), SettingType::Lang},
+                      {i18nc("@info:title", "Numbers"), SettingType::Numeric},
+                      {i18nc("@info:title", "Time"), SettingType::Time},
+                      {i18nc("@info:title", "Currency"), SettingType::Currency},
+                      {i18nc("@info:title", "Measurements"), SettingType::Measurement}}};
+    connect(m_settings, &RegionAndLangSettings::langChanged, this, &OptionsModel::handleLangChange);
+    connect(m_settings, &RegionAndLangSettings::numericChanged, this, [this] {
+        QLocale locale = m_settings->LC_LocaleWithLang(SettingType::Numeric);
+        m_numberExample = Utility::numericExample(locale);
+        Q_EMIT dataChanged(createIndex(1, 0), createIndex(1, 0), {Subtitle, Example});
+    });
+    connect(m_settings, &RegionAndLangSettings::timeChanged, this, [this] {
+        QLocale locale = m_settings->LC_LocaleWithLang(SettingType::Time);
+        m_timeExample = Utility::timeExample(locale);
+        Q_EMIT dataChanged(createIndex(2, 0), createIndex(2, 0), {Subtitle, Example});
+    });
+    connect(m_settings, &RegionAndLangSettings::monetaryChanged, this, [this] {
+        QLocale locale = m_settings->LC_LocaleWithLang(SettingType::Currency);
+        m_currencyExample = Utility::monetaryExample(locale);
+        Q_EMIT dataChanged(createIndex(3, 0), createIndex(3, 0), {Subtitle, Example});
+    });
+    connect(m_settings, &RegionAndLangSettings::measurementChanged, this, [this] {
+        QLocale locale = m_settings->LC_LocaleWithLang(SettingType::Measurement);
+        m_measurementExample = Utility::measurementExample(locale);
+        Q_EMIT dataChanged(createIndex(4, 0), createIndex(4, 0), {Subtitle, Example});
+    });
+
+    // initialize examples
+    m_numberExample = Utility::numericExample(m_settings->LC_LocaleWithLang(SettingType::Numeric));
+    m_timeExample = Utility::timeExample(QLocale(m_settings->LC_LocaleWithLang(SettingType::Time)));
+    m_measurementExample = Utility::measurementExample(QLocale(m_settings->LC_LocaleWithLang(SettingType::Measurement)));
+    m_currencyExample = Utility::monetaryExample(QLocale(m_settings->LC_LocaleWithLang(SettingType::Currency)));
+}
+
+int OptionsModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent)
+    return m_staticNames.size();
+}
+
+QVariant OptionsModel::data(const QModelIndex &index, int role) const
+{
+    using namespace KCM_RegionAndLang;
+    const int row = index.row();
+    if (row < 0 || row >= (int)m_staticNames.size()) {
+        return {};
+    }
+
+    switch (role) {
+    case Name:
+        return m_staticNames.at(row).first;
+    case Subtitle: {
+        switch (row) {
+        case Lang:
+            if (m_settings->defaultLangValue().isEmpty() && m_settings->isDefaultSetting(SettingType::Lang)) {
+                // no Lang configured, no $LANG in env
+                return i18nc("@info:title, the current setting is system default", "System Default");
+            } else if (!m_settings->lang().isEmpty()) {
+                // Lang configured and not empty
+                return getNativeName(m_settings->lang());
+            } else {
+                // Lang configured but empty, try to read from $LANG, shouldn't happen on a valid config file
+                return getNativeName(m_settings->defaultLangValue());
+            }
+        case Numeric:
+            if (m_settings->isDefaultSetting(SettingType::Numeric)) {
+                return getNativeName(m_settings->numeric());
+            }
+            break;
+        case Time:
+            if (m_settings->isDefaultSetting(SettingType::Time)) {
+                return getNativeName(m_settings->time());
+            }
+            break;
+        case Currency:
+            if (m_settings->isDefaultSetting(SettingType::Currency)) {
+                return getNativeName(m_settings->monetary());
+            }
+            break;
+        case Measurement:
+            if (m_settings->isDefaultSetting(SettingType::Measurement)) {
+                return getNativeName(m_settings->measurement());
+            }
+            break;
+        }
+        return {}; // implicit locale
+    }
+    case Example: {
+        switch (row) {
+        case Lang:
+            return {};
+        case Numeric: {
+            QString example = m_numberExample;
+            if (m_settings->isDefaultSetting(SettingType::Numeric)) {
+                example += implicitFormatExampleMsg();
+            }
+            return example;
+        }
+        case Time: {
+            QString example = m_timeExample;
+            if (m_settings->isDefaultSetting(SettingType::Time)) {
+                example += implicitFormatExampleMsg();
+            }
+            return example;
+        }
+        case Currency: {
+            QString example = m_currencyExample;
+            if (m_settings->isDefaultSetting(SettingType::Currency)) {
+                example += implicitFormatExampleMsg();
+            }
+            return example;
+        }
+        case Measurement: {
+            QString example = m_measurementExample;
+            if (m_settings->isDefaultSetting(SettingType::Measurement)) {
+                example += implicitFormatExampleMsg();
+            }
+            return example;
+        }
+        }
+    }
+    case Page:
+        return m_staticNames.at(row).second;
+    }
+    return {};
+}
+
+QHash<int, QByteArray> OptionsModel::roleNames() const
+{
+    return {{Name, QByteArrayLiteral("name")},
+            {Subtitle, QByteArrayLiteral("localeName")},
+            {Example, QByteArrayLiteral("example")},
+            {Page, QByteArrayLiteral("page")}};
+}
+
+void OptionsModel::handleLangChange()
+{
+    Q_EMIT dataChanged(createIndex(0, 0), createIndex(0, 0), {Subtitle, Example});
+    QLocale lang = QLocale(m_settings->lang());
+    if (m_settings->isDefaultSetting(SettingType::Numeric)) {
+        m_numberExample = Utility::numericExample(lang);
+        Q_EMIT dataChanged(createIndex(1, 0), createIndex(1, 0), {Subtitle, Example});
+    }
+    if (m_settings->isDefaultSetting(SettingType::Time)) {
+        m_timeExample = Utility::timeExample(lang);
+        Q_EMIT dataChanged(createIndex(2, 0), createIndex(2, 0), {Subtitle, Example});
+    }
+    if (m_settings->isDefaultSetting(SettingType::Currency)) {
+        m_currencyExample = Utility::monetaryExample(lang);
+        Q_EMIT dataChanged(createIndex(3, 0), createIndex(3, 0), {Subtitle, Example});
+    }
+    if (m_settings->isDefaultSetting(SettingType::Measurement)) {
+        m_measurementExample = Utility::measurementExample(lang);
+        Q_EMIT dataChanged(createIndex(4, 0), createIndex(4, 0), {Subtitle, Example});
+    }
+}
+
+QString OptionsModel::implicitFormatExampleMsg() const
+{
+    QString locale;
+
+    if (!m_settings->lang().isEmpty()) {
+        locale = getNativeName(m_settings->lang());
+    } else if (!m_settings->defaultLangValue().isEmpty()) {
+        locale = getNativeName(m_settings->defaultLangValue());
+    } else {
+        locale = i18nc("@info:title, the current setting is system default", "System Default");
+    }
+    return i18nc("as subtitle, remind user that the format used now is inherited from locale %1", " (Standard format for %1)", locale);
+}
+
+QString OptionsModel::getNativeName(const QString &locale) const
+{
+    return QLocale(locale).nativeLanguageName();
+}
diff --git a/kcms/formats/optionsmodel.h b/kcms/region_language/optionsmodel.h
similarity index 54%
rename from kcms/formats/optionsmodel.h
rename to kcms/region_language/optionsmodel.h
index 6319848f7..9a583440a 100644
--- a/kcms/formats/optionsmodel.h
+++ b/kcms/region_language/optionsmodel.h
@@ -4,31 +4,41 @@
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
+
 #pragma once
-#include <QAbstractListModel>
+
 #include <array>
 
-class FormatsSettings;
+#include <QAbstractListModel>
+
+#include "regionandlangsettings.h"
+
+class RegionAndLangSettings;
+class KCMRegionAndLang;
+
 class OptionsModel : public QAbstractListModel
 {
     Q_OBJECT
 public:
     enum Roles { Name = Qt::DisplayRole, Subtitle, Example, Page };
-    OptionsModel(QObject *parent);
+    explicit OptionsModel(KCMRegionAndLang *parent);
     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
     QHash<int, QByteArray> roleNames() const override;
 
-    FormatsSettings *settings() const;
 public Q_SLOTS:
     void handleLangChange();
 
 private:
-    QString numberExample() const;
-    QString timeExample() const;
-    QString currencyExample() const;
-    QString measurementExample() const;
-    QLocale localeWithDefault(const QString &val) const;
-    FormatsSettings *m_settings = nullptr;
-    std::array<std::pair<QString, QString>, 5> m_staticNames; // title, page
+    QString implicitFormatExampleMsg() const;
+    QString getNativeName(const QString &locale) const;
+
+    QString m_numberExample;
+    QString m_timeExample;
+    QString m_currencyExample;
+    QString m_measurementExample;
+
+    std::array<std::pair<QString, KCM_RegionAndLang::SettingType>, 5> m_staticNames; // title, page
+
+    RegionAndLangSettings *m_settings;
 };
diff --git a/kcms/region_language/package/contents/ui/AdvancedLanguageSelectPage.qml b/kcms/region_language/package/contents/ui/AdvancedLanguageSelectPage.qml
new file mode 100644
index 000000000..60353be17
--- /dev/null
+++ b/kcms/region_language/package/contents/ui/AdvancedLanguageSelectPage.qml
@@ -0,0 +1,204 @@
+/*
+  SPDX-FileCopyrightLabel: 2021 Han Young <hanyoung at protonmail.com>
+  SPDX-FileCopyrightText: 2015 Marco Martin <mart at kde.org>
+  SPDX-FileCopyrightText: 2018 Eike Hein <hein at kde.org>
+  SPDX-FileCopyrightText: 2021 Harald Sitter <sitter at kde.org>
+  SPDX-License-Identifier: LGPL-3.0-or-later
+*/
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+
+import org.kde.kirigami 2.15 as Kirigami
+import org.kde.kcm 1.2 as KCM
+import kcmregionandlang 1.0
+
+KCM.ScrollViewKCM {
+    property int replaceLangIndex: -1
+    implicitHeight: languageListView.height
+    id: languageSelectPage
+    title: i18n("Language")
+    LanguageListModel {
+        id: languageListModel
+        Component.onCompleted: {
+            languageListModel.setRegionAndLangSettings(kcm.settings);
+        }
+    }
+    header: ColumnLayout {
+        spacing: Kirigami.Units.largeSpacing
+
+        Kirigami.InlineMessage {
+            text: i18n("Putting American English above other languages will cause undesired behavior in some applications. If you would like your system to use American English, remove all other languages.")
+            Layout.fillWidth: true
+            type: Kirigami.MessageType.Error
+            visible: languageListModel.selectedLanguageModel.shouldWarnMultipleLang
+        }
+
+        Kirigami.InlineMessage {
+            id: unsupportedLanguageMsg
+            text: i18nc("Error message, %1 is the language name", "The language \"%1\" is unsupported", languageListModel.selectedLanguageModel.unsupportedLanguage)
+            Layout.fillWidth: true
+            type: Kirigami.MessageType.Error
+            visible: languageListModel.selectedLanguageModel.unsupportedLanguage.length > 0
+        }
+
+        QQC2.Label {
+            horizontalAlignment: Qt.AlignHCenter
+            wrapMode: Text.Wrap
+            Layout.fillWidth: true
+            text: i18n("Add languages in the order you want to see them in your applications.")
+        }
+    }
+
+    Component {
+        id: languagesListItemComponent
+        Item {
+            width: ListView.view.width
+            height: listItem.implicitHeight
+
+            Kirigami.SwipeListItem {
+                id: listItem
+
+                contentItem: RowLayout {
+                    Kirigami.ListItemDragHandle {
+                        listItem: listItem
+                        listView: languageListView
+                        visible: languageListView.count > 1
+                        onMoveRequested: {
+                            languageListModel.selectedLanguageModel.move(oldIndex, newIndex);
+                        }
+                    }
+
+                    QQC2.Label {
+                        Layout.alignment: Qt.AlignLeft
+                        Layout.fillWidth: true
+                        text: model.display
+
+                        color: listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) ? listItem.activeTextColor : listItem.textColor;
+                    }
+
+                    QQC2.Button {
+                        Layout.alignment: Qt.AlignRight
+                        visible: languageListView.count <= 1
+                        text: i18nc("@info:tooltip", "Change Language")
+                        icon.name: "configure"
+                        onClicked: {
+                            replaceLangIndex = index;
+                            addLanguagesSheet.titleText = i18nc("@title:window", "Change Language");
+                            addLanguagesSheet.open();
+                        }
+                    }
+                }
+
+                actions: [
+                    Kirigami.Action {
+                        enabled: index > 0
+                        visible: languageListView.count > 1
+                        iconName: "go-top"
+                        tooltip: i18nc("@info:tooltip", "Move to top")
+                        onTriggered: languageListModel.selectedLanguageModel.move(index, 0)
+                    },
+                    Kirigami.Action {
+                        visible: languageListView.count > 1
+                        iconName: "edit-delete"
+                        tooltip: i18nc("@info:tooltip", "Remove")
+                        onTriggered: languageListModel.selectedLanguageModel.remove(index);
+                    }]
+            }
+        }
+    }
+    view: ListView {
+        id: languageListView
+        model: languageListModel.selectedLanguageModel
+        delegate: languagesListItemComponent
+        Kirigami.PlaceholderMessage {
+            anchors.centerIn: parent
+            visible: languageListView.count === 0
+            text: i18nc("@info:placeholder", "No Language Configured")
+        }
+    }
+
+    Component {
+        id: addLanguageItemComponent
+
+        Kirigami.BasicListItem  {
+            id: languageItem
+
+            width: availableLanguagesList.width
+            reserveSpaceForIcon: false
+
+            label: model.nativeName
+            action: Kirigami.Action {
+                onTriggered: {
+                    if (replaceLangIndex >= 0) {
+                        languageListModel.selectedLanguageModel.replaceLanguage(replaceLangIndex, model.languageCode);
+                        replaceLangIndex = -1;
+                    } else {
+                        languageListModel.selectedLanguageModel.addLanguage(model.languageCode);
+                    }
+                    addLanguagesSheet.close();
+                }
+            }
+        }
+    }    
+
+    Kirigami.OverlaySheet {
+        id: addLanguagesSheet
+        property string titleText: i18nc("@title:window", "Add Languages")
+        parent: languageSelectPage
+
+        topPadding: 0
+        leftPadding: 0
+        rightPadding: 0
+        bottomPadding: 0
+
+        title: titleText
+
+        ListView {
+            id: availableLanguagesList
+            implicitWidth: 18 * Kirigami.Units.gridUnit
+            model: languageListModel
+            delegate: addLanguageItemComponent
+        }
+        onSheetOpenChanged: {
+            if (!sheetOpen) {
+                titleText = i18nc("@title:window", "Add Languages");
+            }
+        }
+    }
+    footer: ColumnLayout {
+        QQC2.Button {
+            Layout.alignment: Qt.AlignRight
+            enabled: availableLanguagesList.count
+
+            text: i18nc("@action:button", "Add More…")
+
+            onClicked: {
+                addLanguagesSheet.open();
+                replaceLangIndex = -1;
+            }
+
+            checkable: true
+            checked: addLanguagesSheet.sheetOpen
+        }
+        RowLayout {
+            Layout.alignment: Qt.AlignHCenter
+            QQC2.Label {
+                text: languageListModel.numberExample
+            }
+            QQC2.Label {
+                text: languageListModel.currencyExample
+            }
+        }
+
+        QQC2.Label {
+            Layout.alignment: Qt.AlignHCenter
+            text: languageListModel.metric
+        }
+
+        QQC2.Label {
+            Layout.alignment: Qt.AlignHCenter
+            text: languageListModel.timeExample
+        }
+    }
+}
diff --git a/kcms/region_language/package/contents/ui/main.qml b/kcms/region_language/package/contents/ui/main.qml
new file mode 100644
index 000000000..3d698de9f
--- /dev/null
+++ b/kcms/region_language/package/contents/ui/main.qml
@@ -0,0 +1,238 @@
+/*
+  SPDX-FileCopyrightLabel: 2021 Han Young <hanyoung at protonmail.com>
+
+  SPDX-License-Identifier: LGPL-3.0-or-later
+*/
+import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
+
+import org.kde.kirigami 2.15 as Kirigami
+import org.kde.kcm 1.2 as KCM
+import org.kde.kitemmodels 1.0
+import kcmregionandlang 1.0
+
+KCM.ScrollViewKCM {
+    id: root
+    implicitHeight: Kirigami.Units.gridUnit * 40
+    implicitWidth: Kirigami.Units.gridUnit * 20
+    header: ColumnLayout {
+        id: messagesLayout
+
+        spacing: Kirigami.Units.largeSpacing
+
+        Kirigami.InlineMessage {
+            id: takeEffectNextTimeMsg
+            Layout.fillWidth: true
+
+            type: Kirigami.MessageType.Information
+
+            text: i18nc("@info", "Changes will take effect the next time you log in.")
+
+            actions: [
+                Kirigami.Action {
+                    icon.name: "system-reboot"
+                    visible: takeEffectNextTimeMsg.visible && !dontShutdownMsg.visible
+                    text: i18n("Restart now")
+                    onTriggered: {
+                        kcm.reboot()
+                    }
+                }
+            ]
+        }
+
+        Kirigami.InlineMessage {
+            id: dontShutdownMsg
+            Layout.fillWidth: true
+
+            type: Kirigami.MessageType.Warning
+
+            text:  i18nc("@info", "Generating locale and language support files; don't turn off the computer yet.")
+        }
+
+        Kirigami.InlineMessage {
+            id: installFontMsg
+            Layout.fillWidth: true
+
+            type: Kirigami.MessageType.Information
+
+            text: i18nc("@info", "Locale and language support files have been generated, but language-specific fonts could not be automatically installed. If your language requires specialized fonts to be displayed properly, you will need to discover what they are and install them yourself.")
+        }
+        Kirigami.InlineMessage {
+            id: manualInstallMsg
+            Layout.fillWidth: true
+
+            type: Kirigami.MessageType.Error
+        }
+        Kirigami.InlineMessage {
+            id: installSuccessMsg
+            Layout.fillWidth: true
+
+            type: Kirigami.MessageType.Positive
+
+            text: i18nc("@info", "Necessary locale and language support files have been installed. It is now safe to turn off the computer.")
+        }
+    }
+
+    Connections {
+        target: kcm
+        function onStartGenerateLocale() {
+            dontShutdownMsg.visible = true;
+        }
+        function onRequireInstallFont() {
+            dontShutdownMsg.visible = false;
+            installFontMsg.visible = true;
+        }
+        function onUserHasToGenerateManually(reason) {
+            manualInstallMsg.text = reason;
+            dontShutdownMsg.visible = false;
+            manualInstallMsg.visible = true;
+        }
+        function onGenerateFinished() {
+            dontShutdownMsg.visible = false;
+            installSuccessMsg.visible = true;
+        }
+        function onSaveClicked() {
+            // return to first page on save action since all messages are here
+            while (kcm.depth !== 1) {
+                kcm.takeLast();
+            }
+        }
+        function onTakeEffectNextTime() {
+            takeEffectNextTimeMsg.visible = true;
+        }
+    }
+
+    view: ListView {
+        model: kcm.optionsModel
+        delegate: Kirigami.BasicListItem {
+            highlighted: false
+            hoverEnabled: false
+
+            text: model.name
+            subtitle: {
+                if (model.page === SettingType.Lang) {
+                    return model.localeName;
+                }
+                return model.example;
+            }
+            reserveSpaceForSubtitle: true
+            trailing: Item {
+                implicitWidth: changeButton.implicitWidth
+                QQC2.Button {
+                    id: changeButton
+                    anchors.centerIn: parent
+                    text: i18nc("Button for change the locale used", "Change it…")
+                    onClicked: {
+                        if (model.page === SettingType.Lang) {
+                            languageSelectPage.active = true;
+                            kcm.push(languageSelectPage.item);
+                        } else {
+                            localeListPage.active = true;
+                            localeListPage.item.setting = model.page;
+                            localeListPage.item.filterText = '';
+                            kcm.push(localeListPage.item);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    Loader {
+        id: languageSelectPage
+        active: false
+        source: "AdvancedLanguageSelectPage.qml"
+    }
+
+    Loader {
+        id: localeListPage
+        active: false
+        sourceComponent: KCM.ScrollViewKCM {
+            property int setting: SettingType.Lang
+            property alias filterText: searchField.text
+            title: {
+                localeListView.currentIndex = -1;
+                localeListModel.selectedConfig = setting;
+                switch (setting) {
+                case SettingType.Numeric:
+                    return i18n("Numbers");
+                case SettingType.Time:
+                    return i18n("Time");
+                case SettingType.Currency:
+                    return i18n("Currency");
+                case SettingType.Measurement:
+                    return i18n("Measurements");
+                }
+                console.warn("Invalid setting passed: ", setting);
+                return "Invalid"; // guard
+            }
+
+            LocaleListModel {
+                id: localeListModel
+                Component.onCompleted: {
+                    localeListModel.setLang(kcm.settings.lang);
+                }
+            }
+
+            KSortFilterProxyModel {
+                id: filterModel
+                sourceModel: localeListModel
+                filterRole: "filter"
+            }
+
+            Connections {
+                target: kcm.settings
+                function onLangChanged() {
+                    localeListModel.setLang(kcm.settings.lang);
+                }
+            }
+
+            header: Kirigami.SearchField {
+                id: searchField
+                onTextChanged: filterModel.filterString = text.toLowerCase()
+            }
+
+            view: ListView {
+                id: localeListView
+                clip: true
+                model: filterModel
+                delegate: Kirigami.BasicListItem {
+                    icon: model.flag
+                    text: model.display
+                    subtitle: model.example ? model.example : ''
+                    trailing: QQC2.Label {
+                        color: Kirigami.Theme.disabledTextColor
+                        text: model.localeName
+                    }
+
+                    onClicked: {
+                        if (model.localeName !== i18n("Default")) {
+                            switch (setting) {
+                            case SettingType.Lang:
+                                kcm.settings.lang = localeName;
+                                break;
+                            case SettingType.Numeric:
+                                kcm.settings.numeric = localeName;
+                                break;
+                            case SettingType.Time:
+                                kcm.settings.time = localeName;
+                                break;
+                            case SettingType.Currency:
+                                kcm.settings.monetary = localeName;
+                                break;
+                            case SettingType.Measurement:
+                                kcm.settings.measurement = localeName;
+                                break;
+                            }
+                        } else {
+                            kcm.unset(setting);
+                        }
+
+                        kcm.takeLast();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/kcms/region_language/regionandlangsettings.cpp b/kcms/region_language/regionandlangsettings.cpp
new file mode 100644
index 000000000..554988be4
--- /dev/null
+++ b/kcms/region_language/regionandlangsettings.cpp
@@ -0,0 +1,64 @@
+/*
+    regionandlangsettings.cpp
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "regionandlangsettings.h"
+
+#include "kcm_regionandlang_debug.h"
+
+using KCM_RegionAndLang::SettingType;
+
+bool RegionAndLangSettings::isDefaultSetting(SettingType setting) const
+{
+    switch (setting) {
+    case SettingType::Lang:
+        return lang() == defaultLangValue();
+    case SettingType::Numeric:
+        return numeric() == defaultNumericValue();
+    case SettingType::Time:
+        return time() == defaultTimeValue();
+    case SettingType::Currency:
+        return monetary() == defaultMonetaryValue();
+    case SettingType::Measurement:
+        return measurement() == defaultMeasurementValue();
+    }
+
+    return false;
+}
+
+QString RegionAndLangSettings::langWithFallback() const
+{
+    const QString lang = RegionAndLangSettings::lang();
+    if (!(isDefaultSetting(SettingType::Lang) && lang.isEmpty())) {
+        if (const QString envLang = qEnvironmentVariable("LANG"); !envLang.isEmpty()) {
+            return envLang;
+        }
+        return QLocale::system().name();
+    }
+    return lang;
+}
+
+QString RegionAndLangSettings::LC_LocaleWithLang(SettingType setting) const
+{
+    if (isDefaultSetting(setting)) {
+        return langWithFallback();
+    }
+
+    switch (setting) {
+    case SettingType::Numeric:
+        return numeric();
+    case SettingType::Time:
+        return time();
+    case SettingType::Currency:
+        return monetary();
+    case SettingType::Measurement:
+        return measurement();
+    case SettingType::Lang:
+        Q_UNREACHABLE();
+    }
+
+    return langWithFallback();
+}
diff --git a/kcms/region_language/regionandlangsettings.h b/kcms/region_language/regionandlangsettings.h
new file mode 100644
index 000000000..a3b3436a5
--- /dev/null
+++ b/kcms/region_language/regionandlangsettings.h
@@ -0,0 +1,21 @@
+/*
+    regionandlangsettings.h
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include "regionandlangsettingsbase.h"
+#include "settingtype.h"
+
+class RegionAndLangSettings : public RegionAndLangSettingsBase
+{
+    Q_OBJECT
+public:
+    using RegionAndLangSettingsBase::RegionAndLangSettingsBase;
+    bool isDefaultSetting(KCM_RegionAndLang::SettingType setting) const;
+    QString langWithFallback() const;
+    QString LC_LocaleWithLang(KCM_RegionAndLang::SettingType setting) const;
+};
diff --git a/kcms/region_language/regionandlangsettingsbase.kcfg b/kcms/region_language/regionandlangsettingsbase.kcfg
new file mode 100644
index 000000000..8e2a4814b
--- /dev/null
+++ b/kcms/region_language/regionandlangsettingsbase.kcfg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+      http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+  <include>QtGlobal</include>
+  <include>KLocalizedString</include>
+  <include>QLocale</include>
+  <kcfgfile name="plasma-localerc" />
+  <group name="Formats">
+    <entry key="LANG" name="lang" type="String">
+        <code>
+            QString lang = QString::fromLocal8Bit(qgetenv("LANG"));
+        </code>
+        <default code="true">lang</default>
+    </entry>
+    <entry key="LC_NUMERIC" name="numeric" type="String">
+        <default code="true">i18n("Inherit Language")</default>
+    </entry>
+    <entry key="LC_TIME" name="time" type="String">
+        <default code="true">i18n("Inherit Language")</default>
+    </entry>
+    <entry key="LC_MONETARY" name="monetary" type="String">
+        <default code="true">i18n("Inherit Language")</default>
+    </entry>
+    <entry key="LC_MEASUREMENT" name="measurement" type="String">
+        <default code="true">i18n("Inherit Language")</default>
+    </entry>
+    <entry key="LC_CTYPE" name="ctype" type="String">
+        <default code="true">i18n("Inherit Language")</default>
+    </entry>
+   </group>
+   <group name="Translations">
+    <entry key="LANGUAGE" name="language" type="String">
+    </entry>
+  </group>
+</kcfg>
diff --git a/kcms/formats/formatssettings.kcfgc b/kcms/region_language/regionandlangsettingsbase.kcfgc
similarity index 55%
rename from kcms/formats/formatssettings.kcfgc
rename to kcms/region_language/regionandlangsettingsbase.kcfgc
index 6a9d2e27b..94f9d1aa6 100644
--- a/kcms/formats/formatssettings.kcfgc
+++ b/kcms/region_language/regionandlangsettingsbase.kcfgc
@@ -1,5 +1,5 @@
-File=formatssettings.kcfg
-ClassName=FormatsSettings
+File=regionandlangsettingsbase.kcfg
+ClassName=RegionAndLangSettingsBase
 Mutators=true
 DefaultValueGetters=true
 GenerateProperties=true
diff --git a/kcms/region_language/settingtype.h b/kcms/region_language/settingtype.h
new file mode 100644
index 000000000..59770e0cc
--- /dev/null
+++ b/kcms/region_language/settingtype.h
@@ -0,0 +1,17 @@
+/*
+    settingtype.h
+    SPDX-FileCopyrightText: 2022 Han Young <hanyoung at protonmail.com>
+
+    SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <qobjectdefs.h>
+
+namespace KCM_RegionAndLang
+{
+Q_NAMESPACE_EXPORT()
+enum SettingType { Lang, Numeric, Time, Currency, Measurement };
+Q_ENUM_NS(SettingType)
+} // namespace KCM_RegionAndLang
diff --git a/kcms/translations/CMakeLists.txt b/kcms/translations/CMakeLists.txt
deleted file mode 100644
index cd3192915..000000000
--- a/kcms/translations/CMakeLists.txt
+++ /dev/null
@@ -1,49 +0,0 @@
-ecm_find_qmlmodule(org.kde.plasma.core 2.0)
-
-# KI18N Translation Domain for this library.
-add_definitions(-DTRANSLATION_DOMAIN=\"kcm_translations\")
-
-########### next target ###############
-
-set(kcm_translations_PART_SRCS translations.cpp translationsmodel.cpp translationssettings.cpp language.cpp)
-
-kcmutils_generate_module_data(
-    kcm_translations_PART_SRCS
-    MODULE_DATA_HEADER translationsdata.h
-    MODULE_DATA_CLASS_NAME TranslationsData
-    SETTINGS_HEADERS translationssettings.h
-    SETTINGS_CLASSES TranslationsSettings
-)
-
-kconfig_add_kcfg_files(kcm_translations_PART_SRCS translationssettingsbase.kcfgc GENERATE_MOC)
-
-ecm_qt_declare_logging_category(
-    kcm_translations_PART_SRCS
-        HEADER debug.h
-        IDENTIFIER KCM_TRANSLATIONS
-        CATEGORY_NAME org.kde.kcm_translations
-        DESCRIPTION "Translations KCM"
-        EXPORT kcm_translations
-)
-
-ecm_qt_install_logging_categories(
-    EXPORT kcm_translations
-    DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
-)
-
-kcoreaddons_add_plugin(kcm_translations SOURCES ${kcm_translations_PART_SRCS} INSTALL_NAMESPACE "plasma/kcms/systemsettings")
-
-target_link_libraries(kcm_translations
-    KF5::I18n
-    KF5::KCMUtils
-    KF5::QuickAddons
-)
-
-if(HAVE_PACKAGEKIT)
-    target_link_libraries(kcm_translations PK::packagekitqt5)
-endif()
-
-########### install files ###############
-install(FILES kcm_translations.desktop  DESTINATION  ${KDE_INSTALL_APPDIR})
-kpackage_install_package(package kcm_translations kcms)
-
diff --git a/kcms/translations/Messages.sh b/kcms/translations/Messages.sh
deleted file mode 100644
index 96d449a97..000000000
--- a/kcms/translations/Messages.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#! /usr/bin/env bash
-$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcm_translations.pot
diff --git a/kcms/translations/language.cpp b/kcms/translations/language.cpp
deleted file mode 100644
index 8d06bf058..000000000
--- a/kcms/translations/language.cpp
+++ /dev/null
@@ -1,191 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-// SPDX-FileCopyrightText: 2021 Harald Sitter <sitter at kde.org>
-
-#include "language.h"
-
-#include <KOSRelease>
-
-#include <QProcess>
-
-#include "config-workspace.h"
-#include "debug.h"
-
-#ifdef HAVE_PACKAGEKIT
-#include <PackageKit/Daemon>
-#include <utility>
-class LanguageCompleter : public QObject
-{
-    Q_OBJECT
-public:
-    explicit LanguageCompleter(const QStringList &packages, QObject *parent = nullptr)
-        : QObject(parent)
-        , m_packages(packages)
-    {
-    }
-
-    void start()
-    {
-        auto transaction = PackageKit::Daemon::resolve(m_packages, PackageKit::Transaction::FilterNotInstalled | PackageKit::Transaction::FilterArch);
-        connect(transaction,
-                &PackageKit::Transaction::package,
-                this,
-                [this](PackageKit::Transaction::Info info, const QString &packageID, const QString &summary) {
-                    Q_UNUSED(info);
-                    Q_UNUSED(summary);
-                    m_packageIDs << packageID;
-                });
-        connect(transaction, &PackageKit::Transaction::errorCode, this, [](PackageKit::Transaction::Error error, const QString &details) {
-            qCDebug(KCM_TRANSLATIONS) << "resolve error" << error << details;
-        });
-        connect(transaction, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status, uint code) {
-            qCDebug(KCM_TRANSLATIONS) << "resolve finished" << status << code << m_packageIDs;
-            if (m_packageIDs.size() != m_packages.size()) {
-                qCWarning(KCM_TRANSLATIONS) << "Not all missing packages managed to resolve!" << m_packages << m_packageIDs;
-            }
-            install();
-        });
-    }
-
-Q_SIGNALS:
-    void complete();
-
-private:
-    void install()
-    {
-        auto transaction = PackageKit::Daemon::installPackages(m_packageIDs);
-        connect(transaction, &PackageKit::Transaction::errorCode, this, [](PackageKit::Transaction::Error error, const QString &details) {
-            qCDebug(KCM_TRANSLATIONS) << "install error:" << error << details;
-        });
-        connect(transaction, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status, uint code) {
-            qCDebug(KCM_TRANSLATIONS) << "install finished:" << status << code;
-            Q_EMIT complete();
-        });
-    }
-
-    const QStringList m_packages;
-    QStringList m_packageIDs;
-};
-#endif
-
-class CompletionCheck : public QObject
-{
-    Q_OBJECT
-public:
-    enum class Result { Error, Incomplete, Complete };
-
-    template<typename... Args>
-    static CompletionCheck *create(Args &&..._args);
-    ~CompletionCheck() override = default;
-
-    virtual void start() = 0;
-
-Q_SIGNALS:
-    void finished(Result result, QStringList missingPackages);
-
-protected:
-    explicit CompletionCheck(const QString &languageCode, QObject *parent = nullptr)
-        : QObject(parent)
-        , m_languageCode(languageCode)
-    {
-    }
-
-    const QString m_languageCode;
-
-private:
-    Q_DISABLE_COPY_MOVE(CompletionCheck);
-};
-
-class UbuntuCompletionCheck : public CompletionCheck
-{
-public:
-    using CompletionCheck::CompletionCheck;
-    void start() override
-    {
-        proc.setProgram("/usr/bin/check-language-support");
-        proc.setArguments({"--language", m_languageCode.left(m_languageCode.indexOf(QLatin1Char('@')))});
-        connect(&proc, &QProcess::finished, this, [this] {
-            const QString output = QString::fromUtf8(proc.readAllStandardOutput().simplified());
-            // Whenever we don't get packages back simply pretend the language is complete as we can't
-            // give any useful information on what's wrong anyway.
-            Q_EMIT finished(output.isEmpty() ? Result::Complete : Result::Incomplete, output.split(QLatin1Char(' ')));
-        });
-        proc.start();
-    }
-
-private:
-    QProcess proc;
-};
-
-template<typename... Args>
-CompletionCheck *CompletionCheck::create(Args &&..._args)
-{
-    KOSRelease os;
-    if (os.id() == QLatin1String("ubuntu") || os.idLike().contains(QLatin1String("ubuntu"))) {
-        return new UbuntuCompletionCheck(std::forward<Args>(_args)...);
-    }
-    return nullptr;
-}
-
-void Language::reloadCompleteness()
-{
-    auto *check = CompletionCheck::create(code, this);
-    if (!check) {
-        return; // no checking support - default to assume complete
-    }
-    connect(check, &CompletionCheck::finished, this, [this, check](CompletionCheck::Result result, const QStringList &missingPackages) {
-        check->deleteLater();
-
-        switch (result) {
-        case CompletionCheck::Result::Error:
-            qCWarning(KCM_TRANSLATIONS) << "Failed to get completion status for" << code;
-            return;
-        case CompletionCheck::Result::Incomplete: {
-            // Cache this, we need to modify the data before marking the change on the model.
-            const bool changed = (packages != missingPackages);
-            state = Language::State::Incomplete;
-            packages = missingPackages;
-            if (changed) {
-                Q_EMIT stateChanged();
-            }
-            return;
-        }
-        case CompletionCheck::Result::Complete:
-            if (state != Language::State::Complete) {
-                state = Language::State::Complete;
-                packages.clear();
-                Q_EMIT stateChanged();
-            }
-            return;
-        }
-    });
-    check->start();
-}
-
-void Language::complete()
-{
-#ifdef HAVE_PACKAGEKIT
-    auto completer = new LanguageCompleter(packages, this);
-    connect(completer, &LanguageCompleter::complete, this, [completer, this] {
-        completer->deleteLater();
-        reloadCompleteness();
-    });
-    state = Language::State::Installing;
-    packages.clear();
-    completer->start();
-    Q_EMIT stateChanged();
-#endif
-}
-
-Language::Language(const QString &code_, State state_, QObject *parent)
-    : QObject(parent)
-    , code(code_)
-    , state(state_)
-{
-}
-
-Language::Language(const QString &code_, QObject *parent)
-    : Language(code_, State::Complete, parent)
-{
-}
-
-#include "language.moc"
diff --git a/kcms/translations/language.h b/kcms/translations/language.h
deleted file mode 100644
index 6150deacb..000000000
--- a/kcms/translations/language.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-// SPDX-FileCopyrightText: 2021 Harald Sitter <sitter at kde.org>
-
-#pragma once
-
-#include <QObject>
-
-class Language : public QObject
-{
-    Q_OBJECT
-    Q_PROPERTY(State state MEMBER state NOTIFY stateChanged)
-public:
-    enum class State {
-        Complete, // already complete, mustn't call complete()
-        Incomplete, // the language is installed but lacks packages to be considered complete
-        Installing, // the language is currently installing associated packages to complete itself
-    };
-    Q_ENUM(State)
-
-    explicit Language(const QString &code_, QObject *parent = nullptr);
-    explicit Language(const QString &code_, State state_, QObject *parent = nullptr);
-
-    const QString code;
-    State state = State::Complete;
-    QStringList packages = {};
-
-    void reloadCompleteness(); // only used by model, not invokable from qml
-    Q_INVOKABLE void complete(); // called by qml when completion is requested
-
-Q_SIGNALS:
-    void stateChanged();
-};
diff --git a/kcms/translations/package/contents/ui/main.qml b/kcms/translations/package/contents/ui/main.qml
deleted file mode 100644
index 32136954a..000000000
--- a/kcms/translations/package/contents/ui/main.qml
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
-    SPDX-FileCopyrightText: 2015 Marco Martin <mart at kde.org>
-    SPDX-FileCopyrightText: 2018 Eike Hein <hein at kde.org>
-    SPDX-FileCopyrightText: 2021 Harald Sitter <sitter at kde.org>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-*/
-
-import QtQuick 2.1
-import QtQuick.Layouts 1.1
-import QtQuick.Controls 2.3 as QtControls
-import org.kde.kirigami 2.5 as Kirigami
-import org.kde.plasma.core 2.1 as PlasmaCore
-import org.kde.kcm 1.2
-import org.kde.kitemmodels 1.0 as KItemModels
-
-ScrollViewKCM {
-    id: root
-
-    ConfigModule.quickHelp: i18n("Language")
-
-    implicitWidth: Kirigami.Units.gridUnit * 25
-    implicitHeight: Kirigami.Units.gridUnit * 25
-
-    KItemModels.KSortFilterProxyModel {
-        id: selectedTranslationsModel
-        sourceModel: kcm.translationsModel
-        filterRole: "IsSelected"
-        filterString: "true"
-        sortRole: "SelectionPreference"
-    }
-
-    KItemModels.KSortFilterProxyModel {
-        id: availableTranslationsModel
-        sourceModel: kcm.translationsModel
-        filterRole: "IsSelected"
-        filterString: "false"
-        sortRole: "DisplayRole"
-        sortCaseSensitivity: Qt.CaseInsensitive
-    }
-
-    Component {
-        id: addLanguageItemComponent
-
-        Kirigami.BasicListItem {
-            id: languageItem
-
-            property string languageCode: model ? model.LanguageCode : null
-
-            width: availableLanguagesList.width
-            reserveSpaceForIcon: false
-
-            label: model.display
-
-            checkable: true
-            onCheckedChanged: {
-                if (checked) {
-                    addLanguagesSheet.selectedLanguages.push(model.LanguageCode);
-
-                    // There's no property change notification for pushing to an array
-                    // in a var prop, so we can't bind selectedLanguages.length to
-                    // addLanguagesButton.enabled.
-                    addLanguagesButton.enabled = true;
-                } else {
-                    addLanguagesSheet.selectedLanguages = addLanguagesSheet.selectedLanguages.filter(function(item) { return item !== model.LanguageCode });
-
-                    // There's no property change notification for pushing to an array
-                    // in a var prop, so we can't bind selectedLanguages.length to
-                    // addLanguagesButton.enabled.
-                    if (addLanguagesSheet.selectedLanguages.length > 0) {
-                        addLanguagesButton.enabled = false;
-                    }
-                }
-            }
-
-            data: [Connections {
-                target: addLanguagesSheet
-
-                function onSheetOpenChanged() {
-                    languageItem.checked = false
-                }
-            }]
-        }
-    }
-
-    Kirigami.OverlaySheet {
-        id: addLanguagesSheet
-
-        parent: root.parent
-
-        topPadding: 0
-        leftPadding: 0
-        rightPadding: 0
-        bottomPadding: 0
-
-        title: i18nc("@title:window", "Add Languages")
-
-        property var selectedLanguages: []
-
-        onSheetOpenChanged: selectedLanguages = []
-
-        ListView {
-            id: availableLanguagesList
-
-            implicitWidth: 18 * Kirigami.Units.gridUnit
-            model: availableTranslationsModel
-            delegate: addLanguageItemComponent
-        }
-
-        footer: RowLayout {
-            QtControls.Button {
-                id: addLanguagesButton
-
-                Layout.alignment: Qt.AlignHCenter
-
-                text: i18nc("@action:button", "Add")
-
-                enabled: false
-
-                onClicked: {
-                    var langs = kcm.translationsModel.selectedLanguages.slice();
-                    langs = langs.concat(addLanguagesSheet.selectedLanguages);
-
-                    kcm.translationsModel.selectedLanguages = langs;
-
-                    addLanguagesSheet.sheetOpen = false;
-                }
-            }
-        }
-    }
-
-    header: ColumnLayout {
-        id: messagesLayout
-
-        spacing: Kirigami.Units.largeSpacing
-
-        Kirigami.InlineMessage {
-            Layout.fillWidth: true
-
-            type: Kirigami.MessageType.Error
-
-            text: i18nc("@info", "There are no additional languages available on this system.")
-
-            visible: !availableLanguagesList.count
-        }
-
-        Kirigami.InlineMessage {
-            Layout.fillWidth: true
-
-            type: kcm.everSaved ? Kirigami.MessageType.Positive : Kirigami.MessageType.Information
-
-            text: (kcm.everSaved ? i18nc("@info", "Your changes will take effect the next time you log in.")
-                : i18nc("@info", "There are currently no preferred languages configured."))
-
-            visible: !languagesList.count || kcm.everSaved
-        }
-
-        Kirigami.InlineMessage {
-            Layout.fillWidth: true
-
-            type: Kirigami.MessageType.Error
-
-            text: {
-                // Don't eval the i18ncp call when we have no missing languages. It causes unnecesssary warnings
-                // as %2 will be "" and thus considered missing.
-                if (!visible) {
-                    return ""
-                }
-                i18ncp("@info %2 is the language code",
-                    "The translation files for the language with the code '%2' could not be found. The language will be removed from your configuration. If you want to add it back, please install the localization files for it and add the language again.",
-                    "The translation files for the languages with the codes '%2' could not be found. These languages will be removed from your configuration. If you want to add them back, please install the localization files for it and add the languages again.",
-                    kcm.translationsModel.missingLanguages.length,
-                    kcm.translationsModel.missingLanguages.join(i18nc("@info separator in list of language codes", "', '")))
-            }
-
-            visible: kcm.translationsModel.missingLanguages.length > 0
-        }
-
-        QtControls.Label {
-            Layout.fillWidth: true
-
-            visible: languagesList.count
-
-            text: i18n("The language at the top of this list is the one you want to see and use most often.")
-            wrapMode: Text.WordWrap
-        }
-    }
-
-    Component {
-        id: languagesListItemComponent
-
-        Item {
-            width: ListView.view.width
-            height: listItem.implicitHeight
-
-            Kirigami.SwipeListItem {
-                id: listItem
-
-                contentItem: RowLayout {
-                    Kirigami.ListItemDragHandle {
-                        listItem: listItem
-                        listView: languagesList
-                        onMoveRequested: kcm.translationsModel.move(oldIndex, newIndex)
-                    }
-
-                    QtControls.BusyIndicator {
-                        visible: model.IsInstalling
-                        running: visible
-                        // the control style (e.g. org.kde.desktop) may force a padding that will shrink the indicator way down. ignore it.
-                        padding: 0
-
-                        Layout.alignment: Qt.AlignVCenter
-
-                        implicitWidth: Kirigami.Units.iconSizes.small
-                        implicitHeight: implicitWidth
-
-                        QtControls.ToolTip {
-                            text: xi18nc('@info:tooltip/rich',
-                                         'Installing missing packages to complete this translation.')
-                        }
-
-                    }
-
-                    Kirigami.Icon {
-                        visible: model.IsIncomplete
-
-                        Layout.alignment: Qt.AlignVCenter
-
-                        implicitWidth: Kirigami.Units.iconSizes.small
-                        implicitHeight: implicitWidth
-
-                        source: "data-warning"
-                        color: Kirigami.Theme.negativeTextColor
-                        MouseArea {
-                            id: area
-                            anchors.fill: parent
-                            hoverEnabled: true
-                        }
-                        QtControls.ToolTip {
-                            visible: area.containsMouse
-                            text: xi18nc('@info:tooltip/rich',
-                                         `Not all translations for this language are installed.
-                                          Use the <interface>Install Missing Packages</interface> button to download
-                                          and install all missing packages.`)
-                        }
-
-                    }
-
-                    QtControls.Label {
-                        Layout.fillWidth: true
-                        Layout.alignment: Qt.AlignVCenter
-
-                        text: switch(index){
-                            // Don't assing undefind to string if the index is invalid.
-                            case -1: ""; break;
-                            case 0: i18nc("@item:inlistbox 1 = Language name", "%1 (Default)", model.display); break;
-                            default: model.display; break;
-                        }
-
-                        color: (listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate)) ? listItem.activeTextColor : listItem.textColor
-                    }
-                }
-
-            actions: [
-                Kirigami.Action {
-                    visible: model.IsIncomplete
-                    iconName: "install"
-                    tooltip: i18nc("@info:tooltip", "Install Missing Packages")
-                    onTriggered: model.Object.complete()
-                },
-                Kirigami.Action {
-                    enabled: index > 0
-                    visible: languagesList.count > 1
-                    iconName: "go-top"
-                    tooltip: i18nc("@info:tooltip", "Promote to default")
-                    onTriggered: kcm.translationsModel.move(index, 0)
-                },
-                Kirigami.Action {
-                    iconName: "edit-delete"
-                    visible: languagesList.count > 1
-                    tooltip: i18nc("@info:tooltip", "Remove")
-                    onTriggered: kcm.translationsModel.remove(model.LanguageCode);
-                }]
-            }
-        }
-    }
-
-    view: ListView {
-        id: languagesList
-
-        model: selectedTranslationsModel
-        delegate: languagesListItemComponent
-    }
-
-    footer: RowLayout {
-        id: footerLayout
-
-        QtControls.Button {
-            Layout.alignment: Qt.AlignRight
-
-            enabled: availableLanguagesList.count
-
-            text: i18nc("@action:button", "Add languages…")
-
-            onClicked: addLanguagesSheet.sheetOpen = !addLanguagesSheet.sheetOpen
-
-            checkable: true
-            checked: addLanguagesSheet.sheetOpen
-        }
-    }
-}
-
diff --git a/kcms/translations/translations.cpp b/kcms/translations/translations.cpp
deleted file mode 100644
index b4a682c45..000000000
--- a/kcms/translations/translations.cpp
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-    SPDX-FileCopyrightText: 2014 John Layt <john at layt.net>
-    SPDX-FileCopyrightText: 2018 Eike Hein <hein at kde.org>
-    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens at enioka.com>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-*/
-
-#include "translations.h"
-#include "translationsdata.h"
-#include "translationsmodel.h"
-#include "translationssettings.h"
-
-#include <KLocalizedString>
-#include <KPluginFactory>
-#include <KSharedConfig>
-
-K_PLUGIN_FACTORY_WITH_JSON(TranslationsFactory, "kcm_translations.json", registerPlugin<Translations>(); registerPlugin<TranslationsData>();)
-
-Translations::Translations(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
-    : KQuickAddons::ManagedConfigModule(parent, data, args)
-    , m_data(new TranslationsData(this))
-    , m_translationsModel(new TranslationsModel(this))
-    , m_everSaved(false)
-{
-    setButtons(Apply | Default);
-
-    connect(m_translationsModel, &TranslationsModel::selectedLanguagesChanged, this, &Translations::selectedLanguagesChanged);
-}
-
-Translations::~Translations()
-{
-}
-
-QAbstractItemModel *Translations::translationsModel() const
-{
-    return m_translationsModel;
-}
-
-bool Translations::everSaved() const
-{
-    return m_everSaved;
-}
-
-void Translations::load()
-{
-    KQuickAddons::ManagedConfigModule::load();
-    m_translationsModel->setSelectedLanguages(settings()->configuredLanguages());
-}
-
-void Translations::save()
-{
-    m_everSaved = true;
-    Q_EMIT everSavedChanged();
-    KQuickAddons::ManagedConfigModule::save();
-}
-
-void Translations::defaults()
-{
-    KQuickAddons::ManagedConfigModule::defaults();
-    m_translationsModel->setSelectedLanguages(settings()->configuredLanguages());
-}
-
-void Translations::selectedLanguagesChanged()
-{
-    auto configuredLanguages = m_translationsModel->selectedLanguages();
-
-    const auto missingLanguages = m_translationsModel->missingLanguages();
-    for (const auto &lang : missingLanguages) {
-        configuredLanguages.removeOne(lang);
-    }
-
-    settings()->setConfiguredLanguages(configuredLanguages);
-}
-
-TranslationsSettings *Translations::settings() const
-{
-    return m_data->settings();
-}
-
-bool Translations::isSaveNeeded() const
-{
-    return !m_translationsModel->missingLanguages().isEmpty();
-}
-
-#include "translations.moc"
diff --git a/kcms/translations/translations.h b/kcms/translations/translations.h
deleted file mode 100644
index 8910de105..000000000
--- a/kcms/translations/translations.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-    SPDX-FileCopyrightText: 2014 John Layt <john at layt.net>
-    SPDX-FileCopyrightText: 2018 Eike Hein <hein at kde.org>
-    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens at enioka.com>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-*/
-
-#pragma once
-
-#include <KQuickAddons/ManagedConfigModule>
-
-class AvailableTranslationsModel;
-class SelectedTranslationsModel;
-class TranslationsModel;
-class TranslationsSettings;
-class TranslationsData;
-
-class Translations : public KQuickAddons::ManagedConfigModule
-{
-    Q_OBJECT
-
-    Q_PROPERTY(QAbstractItemModel *translationsModel READ translationsModel CONSTANT)
-    Q_PROPERTY(bool everSaved READ everSaved NOTIFY everSavedChanged)
-
-public:
-    explicit Translations(QObject *parent, const KPluginMetaData &data, const QVariantList &list = QVariantList());
-    ~Translations() override;
-
-    QAbstractItemModel *translationsModel() const;
-
-    bool everSaved() const;
-    TranslationsSettings *settings() const;
-
-public Q_SLOTS:
-    void load() override;
-    void save() override;
-    void defaults() override;
-
-Q_SIGNALS:
-    void everSavedChanged() const;
-
-private Q_SLOTS:
-    void selectedLanguagesChanged();
-
-private:
-    bool isSaveNeeded() const override;
-
-    TranslationsData *m_data;
-    TranslationsModel *m_translationsModel;
-
-    bool m_everSaved;
-};
diff --git a/kcms/translations/translationsmodel.cpp b/kcms/translations/translationsmodel.cpp
deleted file mode 100644
index 756ccf3ec..000000000
--- a/kcms/translations/translationsmodel.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
-    SPDX-FileCopyrightText: 2014 John Layt <john at layt.net>
-    SPDX-FileCopyrightText: 2018 Eike Hein <hein at kde.org>
-    SPDX-FileCopyrightText: 2021 Harald Sitter <sitter at kde.org>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-*/
-
-#include "translationsmodel.h"
-
-#include <QDebug>
-#include <QLocale>
-#include <QMetaEnum>
-#include <QMetaObject>
-
-#include "config-workspace.h"
-#include "debug.h"
-
-#ifdef HAVE_PACKAGEKIT
-    // Ubuntu completion depends on packagekit. When packagekit is not available there's no point supporting
-    // completion checking as we'll have no way to complete the language if it is incomplete.
-#endif
-QHash<int, QByteArray> TranslationsModel::roleNames() const
-{
-    QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
-
-    const auto e = QMetaEnum::fromType<AdditionalRoles>();
-    for (int i = 0; i < e.keyCount(); ++i) {
-        roles.insert(e.value(i), e.key(i));
-    }
-
-    return roles;
-}
-
-QVariant TranslationsModel::data(const QModelIndex &index, int role) const
-{
-    if (!index.isValid() || index.row() < 0 || index.row() >= m_languages.count()) {
-        return QVariant();
-    }
-
-    Language *const lang = m_languages.at(index.row());
-    const QString &code = lang->code;
-
-    if (role == Qt::DisplayRole) {
-        return languageCodeToName(code);
-    }
-
-    switch (static_cast<AdditionalRoles>(role)) {
-    case Object:
-        return QVariant::fromValue(lang);
-    case LanguageCode:
-        return code;
-    case IsIncomplete:
-        return lang->state == Language::State::Incomplete;
-    case IsInstalling:
-        return lang->state == Language::State::Installing;
-    case IsSelected:
-        return m_selectedLanguages.contains(code);
-    case SelectionPreference:
-        return m_selectedLanguages.indexOf(code);
-    }
-
-    return {};
-}
-
-int TranslationsModel::rowCount(const QModelIndex &parent) const
-{
-    if (parent.isValid()) {
-        return 0;
-    }
-
-    return m_languages.count();
-}
-
-QString TranslationsModel::languageCodeToName(const QString &languageCode) const
-{
-    const QLocale locale(languageCode);
-    const QString &languageName = locale.nativeLanguageName();
-
-    if (languageName.isEmpty()) {
-        return languageCode;
-    }
-
-    if (languageCode.contains(QLatin1Char('@'))) {
-        return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode);
-    }
-
-    if (locale.name() != languageCode && m_languageCodes.contains(locale.name())) {
-        // KDE languageCode got translated by QLocale to a locale code we also have on
-        // the list. Currently this only happens with pt that gets translated to pt_BR.
-        if (languageCode == QLatin1String("pt")) {
-            return QLocale(QStringLiteral("pt_PT")).nativeLanguageName();
-        }
-
-        qCWarning(KCM_TRANSLATIONS) << "Language code morphed into another existing language code, please report!" << languageCode << locale.name();
-        return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode);
-    }
-
-    return languageName;
-}
-
-QStringList TranslationsModel::selectedLanguages() const
-{
-    return m_selectedLanguages;
-}
-
-void TranslationsModel::setSelectedLanguages(const QStringList &languages)
-{
-    if (m_selectedLanguages == languages) {
-        return;
-    }
-
-    m_selectedLanguages = languages;
-    Q_EMIT selectedLanguagesChanged(languages);
-
-    QStringList missing;
-    for (const QString &code : languages) {
-        if (const int index = m_languageCodes.indexOf(code); index >= 0) {
-            Language *const lang = m_languages.at(index);
-            lang->reloadCompleteness();
-            const QModelIndex modelIndex = createIndex(index, 0);
-            Q_EMIT dataChanged(modelIndex, modelIndex, {IsSelected, SelectionPreference});
-        } else {
-            missing << code;
-        }
-    }
-
-    missing.sort();
-    if (m_missingLanguages != missing) {
-        m_missingLanguages = missing;
-        Q_EMIT missingLanguagesChanged();
-    }
-}
-
-QStringList TranslationsModel::missingLanguages() const
-{
-    return m_missingLanguages;
-}
-
-void TranslationsModel::move(int from, int to)
-{
-    if (from >= m_selectedLanguages.count() || to >= m_selectedLanguages.count()) {
-        return;
-    }
-
-    if (from == to) {
-        return;
-    }
-
-    // Reset the entire model. Figuring out what moved where is hardly worth the effort as
-    // only very few languages will be selected and thus visible when a move occurs.
-    beginResetModel();
-    m_selectedLanguages.move(from, to);
-    Q_EMIT selectedLanguagesChanged(m_selectedLanguages);
-    endResetModel();
-}
-
-void TranslationsModel::remove(const QString &languageCode)
-{
-    if (languageCode.isEmpty()) {
-        return;
-    }
-
-    const int index = m_languageCodes.indexOf(languageCode);
-    if (index < 0 || m_selectedLanguages.count() < 2) {
-        return;
-    }
-    const QModelIndex modelIndex = createIndex(index, 0);
-
-    m_selectedLanguages.removeAll(languageCode);
-    Q_EMIT selectedLanguagesChanged(m_selectedLanguages);
-    Q_EMIT dataChanged(modelIndex, modelIndex, {IsSelected, SelectionPreference});
-}
-
-LanguageVector TranslationsModel::makeLanguages(const QStringList &codes)
-{
-    LanguageVector ret;
-    for (const auto &code : codes) {
-        auto lang = new Language(code, this);
-        connect(lang, &Language::stateChanged, this, [this, lang] {
-            const int index = m_languages.indexOf(lang);
-            if (index < 0) {
-                qCWarning(KCM_TRANSLATIONS) << "Failed to find index for " << lang->code;
-                return;
-            }
-
-            const QModelIndex modelIndex = createIndex(index, 0);
-            Q_EMIT dataChanged(modelIndex, modelIndex, {IsInstalling, IsIncomplete});
-        });
-        ret.push_back(lang);
-    }
-    return ret;
-}
diff --git a/kcms/translations/translationsmodel.h b/kcms/translations/translationsmodel.h
deleted file mode 100644
index 8739e5250..000000000
--- a/kcms/translations/translationsmodel.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
-    SPDX-FileCopyrightText: 2014 John Layt <john at layt.net>
-    SPDX-FileCopyrightText: 2018 Eike Hein <hein at kde.org>
-    SPDX-FileCopyrightText: 2021 Harald Sitter <sitter at kde.org>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-*/
-
-#pragma once
-
-#include <QAbstractListModel>
-
-#include <KLocalizedString>
-
-#include "language.h"
-
-using LanguageVector = QVector<Language *>;
-
-class TranslationsModel : public QAbstractListModel
-{
-    Q_OBJECT
-    Q_PROPERTY(QStringList selectedLanguages READ selectedLanguages WRITE setSelectedLanguages NOTIFY selectedLanguagesChanged)
-    Q_PROPERTY(QStringList missingLanguages READ missingLanguages NOTIFY missingLanguagesChanged)
-public:
-    enum AdditionalRoles {
-        Object = Qt::UserRole + 1,
-        LanguageCode,
-        IsSelected, // mutable whether the language is marked for use
-        SelectionPreference, // integer index of selection preference (establishes the order of selections)
-        IsIncomplete, // whether the language is missing distro packages (not relevant when not IsSelected)
-        IsInstalling, // only true when the language was incomplete and is on the way to completion
-    };
-    Q_ENUM(AdditionalRoles)
-
-    using QAbstractListModel::QAbstractListModel;
-
-    QHash<int, QByteArray> roleNames() const override;
-
-    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
-    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
-
-    QStringList selectedLanguages() const;
-    void setSelectedLanguages(const QStringList &languages);
-
-    QStringList missingLanguages() const;
-
-    Q_INVOKABLE void move(int from, int to);
-    Q_INVOKABLE void remove(const QString &languageCode);
-
-Q_SIGNALS:
-    void selectedLanguagesChanged(const QStringList &languages) const;
-    void missingLanguagesChanged() const;
-
-private:
-    QString languageCodeToName(const QString &languageCode) const;
-    LanguageVector makeLanguages(const QStringList &codes);
-
-    // The indices of these two must always be the same. This is to ensure that we can map between them.
-    // This is the overarching data set of all known languages. This is a strict super set of m_selectedLanguages.
-    // Could have used a hash here but it'd a bit clunky since we most of the time need to access an index so
-    // we'd constantly have to .values() and .keys().
-    // The list of "known" languages cannot change at runtime really. They are all expected to be present all the time.
-    const QStringList m_languageCodes = KLocalizedString::availableDomainTranslations("plasmashell").values();
-    const LanguageVector m_languages = makeLanguages(m_languageCodes);
-
-    // This tracks the selection and order of selected languages. It's a bit like an additional layer of model data.
-    QStringList m_selectedLanguages;
-
-    // Languages that were configured but are indeed missing from our model. These are only language codes.
-    // We intentionally do not model this properly to keep things simple!
-    QStringList m_missingLanguages;
-};
diff --git a/kcms/translations/translationssettings.cpp b/kcms/translations/translationssettings.cpp
deleted file mode 100644
index 5ddac9084..000000000
--- a/kcms/translations/translationssettings.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens at enioka.com>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-*/
-
-#include "translationssettings.h"
-
-TranslationsSettings::TranslationsSettings(QObject *parent)
-    : TranslationsSettingsBase(parent)
-{
-    connect(this, &TranslationsSettingsBase::languageStringChanged, this, &TranslationsSettings::configuredLanguagesChanged);
-}
-
-TranslationsSettings::~TranslationsSettings()
-{
-}
-
-QStringList TranslationsSettings::configuredLanguages() const
-{
-    return languageString().split(QLatin1Char(':'), Qt::SkipEmptyParts);
-}
-
-void TranslationsSettings::setConfiguredLanguages(const QStringList &langs)
-{
-    setLanguageString(langs.join(QLatin1Char(':')));
-}
diff --git a/kcms/translations/translationssettings.h b/kcms/translations/translationssettings.h
deleted file mode 100644
index 6c34b788f..000000000
--- a/kcms/translations/translationssettings.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-    SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens at enioka.com>
-
-    SPDX-License-Identifier: LGPL-2.0-or-later
-*/
-
-#pragma once
-
-#include "translationssettingsbase.h"
-
-class TranslationsSettings : public TranslationsSettingsBase
-{
-    Q_OBJECT
-    Q_PROPERTY(QStringList configuredLanguages READ configuredLanguages WRITE setConfiguredLanguages NOTIFY configuredLanguagesChanged)
-public:
-    TranslationsSettings(QObject *parent = nullptr);
-    ~TranslationsSettings() override;
-
-    QStringList configuredLanguages() const;
-    void setConfiguredLanguages(const QStringList &langs);
-
-Q_SIGNALS:
-    void configuredLanguagesChanged();
-};
diff --git a/kcms/translations/translationssettingsbase.kcfg b/kcms/translations/translationssettingsbase.kcfg
deleted file mode 100644
index b3c85acdf..000000000
--- a/kcms/translations/translationssettingsbase.kcfg
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
-      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
-                           http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
-  <kcfgfile name="plasma-localerc" />
-  <include>KLocalizedString</include>
-  <group name="Translations">
-    <entry name="languageString" key="LANGUAGE" type="String">
-      <code>
-      KConfigGroup formatsConfig = KConfigGroup(KSharedConfig::openConfig("plasma-localerc"), "Formats");
-
-      QString lang = formatsConfig.readEntry("LANG", QString());
-
-      if (lang.isEmpty()
-          || !KLocalizedString::availableDomainTranslations("plasmashell").contains(lang)) {
-          lang = QLocale::system().name();
-      }
-
-      if (!KLocalizedString::availableDomainTranslations("plasmashell").contains(lang)) {
-          lang = QStringLiteral("en_US");
-      }
-      </code>
-      <default code="true">lang</default>
-      <label>Configured languages</label>
-    </entry>
-  </group>
-</kcfg>
diff --git a/kcms/translations/translationssettingsbase.kcfgc b/kcms/translations/translationssettingsbase.kcfgc
deleted file mode 100644
index 0f26f5f7d..000000000
--- a/kcms/translations/translationssettingsbase.kcfgc
+++ /dev/null
@@ -1,6 +0,0 @@
-File=translationssettingsbase.kcfg
-ClassName=TranslationsSettingsBase
-Mutators=true
-DefaultValueGetters=true
-GenerateProperties=true
-ParentInConstructor=true


More information about the kde-doc-english mailing list