diff --git a/cdist/conf/type/__locale/deprecated b/cdist/conf/type/__locale/deprecated
new file mode 100644
index 00000000..5a06b28e
--- /dev/null
+++ b/cdist/conf/type/__locale/deprecated
@@ -0,0 +1 @@
+This type is deprecated. Please use __localedef instead.
diff --git a/cdist/conf/type/__localedef/explorer/state b/cdist/conf/type/__localedef/explorer/state
new file mode 100755
index 00000000..3ba57661
--- /dev/null
+++ b/cdist/conf/type/__localedef/explorer/state
@@ -0,0 +1,100 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist 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 3 of the License, or
+# (at your option) any later version.
+#
+# cdist 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 cdist. If not, see .
+#
+# This explorer determines if the locale is defined on the target system.
+# Will print nothing on error.
+#
+# Possible output:
+# present:
+# the main locale (and possibly aliases) is present
+# absent:
+# neither the main locale nor any aliases are present
+# alias-present:
+# the main locale is absent, but at least one of its aliases is present
+#
+
+# Hardcoded, create a pull request in case it is at another location for
+# some other distro. (cf. gencode-remote)
+aliasfile='/usr/share/locale/locale.alias'
+
+command -v locale >/dev/null 2>&1 || exit 0
+
+locales=$(locale -a)
+
+parse_locale() {
+ # This function will split locales into their parts. Locale strings are
+ # usually of the form: [language[_territory][.codeset][@modifier]]
+ # For simplicity, language and territory are not separated by this function.
+ # Old Linux systems were also using "english" or "german" as locale strings.
+ # Usage: parse_locale locale_str lang_var codeset_var modifier_var
+ eval "${2:?}"="$(expr "$1" : '\([^.@]*\)')"
+ eval "${3:?}"="$(expr "$1" : '[^.]*\.\([^@]*\)')"
+ eval "${4:?}"="$(expr "$1" : '.*@\(.*\)$')"
+}
+
+format_locale() {
+ # Usage: format_locale language codeset modifier
+ printf '%s' "$1"
+ test -z "$2" || printf '.%s' "$2"
+ test -z "$3" || printf '@%s' "$3"
+ printf '\n'
+}
+
+gnu_normalize_codeset() {
+ # reimplementation of glibc/locale/programs/localedef.c normalize_codeset()
+ echo "$*" | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]'
+}
+
+locale_available() (
+ echo "${locales}" | grep -qxF "$1" || {
+ # glibc uses "normalized" locale names in archives.
+ # If a locale is stored in an archive, the normalized name will be
+ # printed by locale, so that needs to be checked, too.
+ localename=$(
+ parse_locale "$1" _lang _codeset _modifier \
+ && format_locale "${_lang:?}" "$(gnu_normalize_codeset "${_codeset?}")" \
+ "${_modifier?}")
+ echo "${locales}" | grep -qxF "${localename}"
+ }
+)
+
+if locale_available "${__object_id:?}"
+then
+ echo present
+else
+ # NOTE: locale.alias can be symlinked.
+ if test -e "${aliasfile}"
+ then
+ # Check if one of the aliases of the locale is defined
+ baselocale=$(
+ parse_locale "${__object_id:?}" _lang _codeset _modifiers \
+ && format_locale "${_lang}" "${_codeset}")
+ while read -r _alias _localename
+ do
+ if test "${_localename}" = "${baselocale}" \
+ && echo "${locales}" | grep -qxF "${_alias}"
+ then
+ echo alias-present
+ exit 0
+ fi
+ done <"${aliasfile}"
+ fi
+
+ echo absent
+fi
diff --git a/cdist/conf/type/__localedef/files/lib/glibc.sh b/cdist/conf/type/__localedef/files/lib/glibc.sh
new file mode 100644
index 00000000..6ace80d4
--- /dev/null
+++ b/cdist/conf/type/__localedef/files/lib/glibc.sh
@@ -0,0 +1,5 @@
+# -*- mode: sh; indent-tabs-mode: t -*-
+
+gnu_normalize_codeset() {
+ echo "$*" | tr -cd '[:alnum:]' | tr '[:upper:]' '[:lower:]'
+}
diff --git a/cdist/conf/type/__localedef/files/lib/locale.sh b/cdist/conf/type/__localedef/files/lib/locale.sh
new file mode 100644
index 00000000..b5e61374
--- /dev/null
+++ b/cdist/conf/type/__localedef/files/lib/locale.sh
@@ -0,0 +1,20 @@
+# -*- mode: sh; indent-tabs-mode:t -*-
+
+parse_locale() {
+ # This function will split locales into their parts. Locale strings are
+ # usually of the form: [language[_territory][.codeset][@modifier]]
+ # For simplicity, language and territory are not separated by this function.
+ # Old Linux systems were also using "english" or "german" as locale strings.
+ # Usage: parse_locale locale_str lang_var codeset_var modifier_var
+ eval "${2:?}"="$(expr "$1" : '\([^.@]*\)')"
+ eval "${3:?}"="$(expr "$1" : '[^.]*\.\([^@]*\)')"
+ eval "${4:?}"="$(expr "$1" : '.*@\(.*\)$')"
+}
+
+format_locale() {
+ # Usage: format_locale language codeset modifier
+ printf '%s' "$1"
+ test -z "$2" || printf '.%s' "$2"
+ test -z "$3" || printf '@%s' "$3"
+ printf '\n'
+}
diff --git a/cdist/conf/type/__localedef/gencode-remote b/cdist/conf/type/__localedef/gencode-remote
new file mode 100755
index 00000000..4538151f
--- /dev/null
+++ b/cdist/conf/type/__localedef/gencode-remote
@@ -0,0 +1,136 @@
+#!/bin/sh -e
+#
+# 2013-2019 Nico Schottelius (nico-cdist at schottelius.org)
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist 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 3 of the License, or
+# (at your option) any later version.
+#
+# cdist 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 cdist. If not, see .
+#
+# Manage system locales using localedef(1).
+#
+
+# shellcheck source=cdist/conf/type/__localedef/files/lib/locale.sh
+. "${__type:?}/files/lib/locale.sh"
+# shellcheck source=cdist/conf/type/__localedef/files/lib/glibc.sh
+. "${__type:?}/files/lib/glibc.sh"
+
+state_is=$(cat "${__object:?}/explorer/state")
+state_should=$(cat "${__object:?}/parameter/state")
+
+test "${state_should}" = 'present' -o "${state_should}" = 'absent' || {
+ printf 'Invalid state: %s\n' "${state_should}" >&2
+ exit 1
+}
+
+# NOTE: If state explorer fails (e.g. locale(1) missing), the following check
+# will always fail and let definition/removal run.
+if test "${state_is}" = "${state_should}"
+then
+ exit 0
+fi
+
+locale=${__object_id:?}
+os=$(cat "${__global:?}/explorer/os")
+
+if expr "${locale}" : '.*/' >/dev/null
+then
+ printf 'Paths as locales are not supported.\n' >&2
+ printf '__object_id is: %s\n' "${locale}" >&2
+ exit 1
+fi
+
+: "${lang=}" "${codeset=}" "${modifier=}" # declare variables for shellcheck
+parse_locale "${locale}" lang codeset modifier
+
+
+case ${os}
+in
+ (alpine|openwrt)
+ printf '%s does not support locales.\n' "${os}" >&2
+ exit 1
+ ;;
+ (archlinux|debian|devuan|ubuntu|suse|centos|fedora|redhat|scientific)
+ # FIXME: The code below only works for glibc-based installations.
+
+ # NOTE: Hardcoded, create a pull request in case it is at another
+ # location for some opther distro.
+ # NOTE: locale.alias can be symlinked (e.g. Debian)
+ aliasfile='/usr/share/locale/locale.alias'
+
+ case ${state_should}
+ in
+ (present)
+ input=$(format_locale "${lang}" '' "${modifier}")
+ cat <<-EOF
+ set --
+ if test -e '${aliasfile}'
+ then
+ set -- -A '${aliasfile}'
+ fi
+
+ localedef -i '${input}' -f '${codeset}' "\$@" '${locale}'
+ EOF
+ ;;
+ (absent)
+ main_localename=$(format_locale "${lang}" "$(gnu_normalize_codeset "${codeset}")" "${modifier}")
+
+ cat <<-EOF
+ while read -r _alias _localename
+ do
+ if test "\${_localename}" = '$(format_locale "${lang}" "${codeset}")'
+ then
+ localedef --delete-from-archive "\${_alias}"
+ fi
+ done <'${aliasfile}'
+ EOF
+
+ if test "${state_is}" = present
+ then
+ printf "localedef --delete-from-archive '%s'\n" "${main_localename}"
+ fi
+ ;;
+ esac
+ ;;
+ (freebsd)
+ case ${state_should}
+ in
+ (present)
+ if expr "$(grep -oe '^[0-9]*' "${__global:?}/explorer/os_version")" '>=' 11 >/dev/null
+ then
+ # localedef(1) is available with FreeBSD >= 11
+ printf "localedef -i '%s' -f '%s' '%s'\n" "${input}" "${codeset}" "${locale}"
+ else
+ printf 'localedef(1) was added to FreeBSD starting with version 11.\n' >&2
+ printf 'Please upgrade your FreeBSD installation to use %s.\n' "${__type##*/}" >&2
+ exit 1
+ fi
+ ;;
+ (absent)
+ printf "rm -R '/usr/share/locale/%s'\n" "${locale}"
+ ;;
+ esac
+ ;;
+ (netbsd|openbsd)
+ # NetBSD/OpenBSD are missing localedef(1).
+ # We also do not delete defined locales because they can't be recreated.
+ echo "${os} is lacking localedef(1). Locale management unavailable." >&2
+ exit 1
+ ;;
+ (*)
+ echo "Your operating system (${os}) is currently not supported by this type (${__type##*/})." >&2
+ echo "Please contribute an implementation for it if you can." >&2
+ exit 1
+ ;;
+esac
diff --git a/cdist/conf/type/__localedef/man.rst b/cdist/conf/type/__localedef/man.rst
new file mode 100644
index 00000000..454ce9d1
--- /dev/null
+++ b/cdist/conf/type/__localedef/man.rst
@@ -0,0 +1,60 @@
+cdist-type__localedef(7)
+========================
+
+NAME
+----
+cdist-type__localedef - Define and remove system locales
+
+
+DESCRIPTION
+-----------
+This cdist type allows you to define locales on the system using
+:strong:`localedef`\ (1) or remove them.
+On systems that don't support definition of new locales, the type will raise an
+error.
+
+**NB:** This type respects the glibc ``locale.alias`` file,
+i.e. it defines alias locales or deletes aliases of a locale when it is removed.
+It is not possible, however, to use alias names to define locales or only remove
+certain aliases of a locale.
+
+
+OPTIONAL PARAMETERS
+-------------------
+state
+ ``present`` or ``absent``. Defaults to ``present``.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # Add locale de_CH.UTF-8
+ __localedef de_CH.UTF-8
+
+ # Same as above, but more explicit
+ __localedef de_CH.UTF-8 --state present
+
+ # Remove colourful British English
+ __localedef en_GB.UTF-8 --state absent
+
+
+SEE ALSO
+--------
+:strong:`locale`\ (1),
+:strong:`localedef`\ (1),
+:strong:`cdist-type__locale_system`\ (7)
+
+
+AUTHORS
+-------
+| Dennis Camera
+| Nico Schottelius
+
+
+COPYING
+-------
+Copyright \(C) 2013-2019 Nico Schottelius, 2020 Dennis Camera. Free use of this
+software is granted under the terms of the GNU General Public License version 3
+or later (GPLv3+).
diff --git a/cdist/conf/type/__localedef/manifest b/cdist/conf/type/__localedef/manifest
new file mode 100755
index 00000000..3ab3ad8c
--- /dev/null
+++ b/cdist/conf/type/__localedef/manifest
@@ -0,0 +1,30 @@
+#!/bin/sh -e
+#
+# 2013-2019 Nico Schottelius (nico-cdist at schottelius.org)
+# 2015 David Hürlimann (david at ungleich.ch)
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist 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 3 of the License, or
+# (at your option) any later version.
+#
+# cdist 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 cdist. If not, see .
+#
+# Install required packages.
+#
+
+case $(cat "${__global:?}/explorer/os")
+in
+ (debian|devuan)
+ __package_apt locales --state present
+ ;;
+esac
diff --git a/cdist/conf/type/__localedef/parameter/default/state b/cdist/conf/type/__localedef/parameter/default/state
new file mode 100644
index 00000000..e7f6134f
--- /dev/null
+++ b/cdist/conf/type/__localedef/parameter/default/state
@@ -0,0 +1 @@
+present
diff --git a/cdist/conf/type/__localedef/parameter/optional b/cdist/conf/type/__localedef/parameter/optional
new file mode 100644
index 00000000..ff72b5c7
--- /dev/null
+++ b/cdist/conf/type/__localedef/parameter/optional
@@ -0,0 +1 @@
+state