Skip to content

Commit

Permalink
Rework system locale
Browse files Browse the repository at this point in the history
  • Loading branch information
codefiles committed Aug 18, 2022
1 parent b1ab5ba commit f0b2921
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 9 deletions.
120 changes: 112 additions & 8 deletions archinstall/lib/installer.py
Expand Up @@ -12,7 +12,7 @@
from .disk import get_partitions_in_use, Partition
from .general import SysCommand, generate_password
from .hardware import has_uefi, is_vm, cpu_vendor
from .locale_helpers import verify_keyboard_layout, verify_x11_keyboard_layout
from .locale_helpers import list_locales, verify_keyboard_layout, verify_x11_keyboard_layout
from .disk.helpers import findmnt
from .mirrors import use_mirrors
from .plugins import plugins
Expand Down Expand Up @@ -440,10 +440,14 @@ def set_hostname(self, hostname: str, *args :str, **kwargs :str) -> None:
fh.write(hostname + '\n')

def set_locale(self, locale :str, encoding :str = 'UTF-8', *args :str, **kwargs :str) -> bool:
self.log(f"Setting locale '{locale}' with encoding '{encoding}'.", level=logging.INFO)
if not len(locale):
return True

modifier = ''
# Get locale entries from /etc/locale.gen
entries = list_locales()

entry = ''

# This is a temporary patch to fix #1200
if '.' in locale:
Expand All @@ -453,19 +457,119 @@ def set_locale(self, locale :str, encoding :str = 'UTF-8', *args :str, **kwargs
# and the "found" encoding differs.
if encoding == 'UTF-8' and encoding != potential_encoding:
encoding = potential_encoding
# - End patch

# Validate the locale with entries from /etc/locale.gen
for row in entries:
entry_locale_name, entry_encoding = row.split()
if '.' in entry_locale_name:
entry_locale = entry_locale_name.split('.', 1)[0]
else:
entry_locale = entry_locale_name
if locale == entry_locale and encoding == entry_encoding:
entry = row
locale_name = entry_locale_name
locale = entry_locale
break
else:
self.log(f"Locale '{locale}' with encoding '{encoding}' not found in /etc/locale.gen.", fg="red", level=logging.ERROR)
return False

# A user could modify /etc/locale.gen in the install environment and add an invalid entry before launching the installer.
# Verify the locale did not validate against an invalid entry due to a user modified /etc/locale.gen.
if not os.path.exists(f'{self.target}/usr/share/i18n/locales/{locale}'):
self.log(f'Invalid locale: {locale}.', fg="red", level=logging.ERROR)
return False

# Check if the locale is already installed using the output of localedef.
# Encodings are formatted differently in the output of localedef (no dashes and lowercase).
formatted_encoding = encoding.replace('-','').lower()
locale_formatted_encoding = f'{locale}.{formatted_encoding}'

installed = []

# Get installed locales from the output of localedef.
for line in SysCommand(f'/usr/bin/arch-chroot {self.target} localedef --list-archive').decode().split():
installed.append(line)

# Install the locale if it is not already installed.
if locale_formatted_encoding not in installed:
# Before installing the locale check if the locale archive already exists and remove it if it does.
try:
os.remove(f'{self.target}/usr/lib/locale/locale-archive')
except FileNotFoundError:
pass

# Use localdef rather than local-gen since local-gen is a wrapper of localdef for user convenience
# and all necessary parameters for localedef are avaliable.
command = f'/usr/bin/arch-chroot {self.target} localedef -i {locale} -c -f {encoding} -A /usr/share/locale/locale.alias {locale_name}'

if not SysCommand(command).exit_code == 0:
self.log(f"Failed to install locale '{locale}' with encoding '{encoding}'.", fg="red", level=logging.ERROR)
return False

modifier = ''

# Make sure we extract the modifier, that way we can put it in if needed.
if '@' in locale:
locale, modifier = locale.split('@', 1)
modifier = f"@{modifier}"
# - End patch

with open(f'{self.target}/etc/locale.gen', 'a') as fh:
fh.write(f'{locale}.{encoding}{modifier} {encoding}\n')
with open(f'{self.target}/etc/locale.conf', 'w') as fh:
fh.write(f'LANG={locale}.{encoding}{modifier}\n')
# A format of locale to check and set the system locale with.
formatted_locale = f'{locale}.{encoding}{modifier}'

locale_conf = f'{self.target}/etc/locale.conf'

found = ''

return True if SysCommand(f'/usr/bin/arch-chroot {self.target} locale-gen').exit_code == 0 else False
# Check if the system locale is already set correctly.
try:
with open(locale_conf, 'r') as fh:
# Set up a regular expression pattern of a line beginning with 'LANG=' followed by a locale
pattern = re.compile(rf'^LANG="?({locale_name}|{formatted_locale})"?$')

for line in fh:
found = pattern.search(line)
if found:
found = found.group(1)
break
except FileNotFoundError:
pass

# Change the system locale if it is not set or set incorrectly.
if not found:
with open(locale_conf, 'w') as fh:
fh.write(f'LANG={formatted_locale}\n')

locale_gen = f'{self.target}/etc/locale.gen'

# Update /etc/locale.gen if necessary so that locale-gen will function properly.
# Check if the locale is the only uncommented entry.
with open(locale_gen, 'r') as fh:
uncommented = []
for line in fh:
if line[0] != '#':
uncommented.append(line.strip())

if len(uncommented) == 1 and entry in uncommented:
return True

# Uncomment the entry for the locale and comment all other uncommented entries.
with open(locale_gen, 'r') as fh:
contents = fh.readlines()

index = 0
for index, line in enumerate(contents):
uncommented_line = line.replace('#', '')
if uncommented_line.rstrip() == entry:
contents[index] = uncommented_line
continue
if line[0] != '#':
contents[index] = f'#{contents[index]}'

# Open the file again in write mode, to replace the contents
with open(locale_gen, 'w') as fh:
return True if fh.writelines(contents) else False

def set_timezone(self, zone :str, *args :str, **kwargs :str) -> bool:
if not zone:
Expand Down
2 changes: 1 addition & 1 deletion archinstall/lib/user_interaction/locale_conf.py
Expand Up @@ -12,7 +12,7 @@

def select_locale_lang(preset: str = None) -> str:
locales = list_locales()
locale_lang = set([locale.split()[0] for locale in locales])
locale_lang = set([locale.split('.', 1)[0] if '.' in locale else locale.split()[0] for locale in locales])

selected_locale = Menu(
_('Choose which locale language to use'),
Expand Down

0 comments on commit f0b2921

Please sign in to comment.