Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions archinstall/lib/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def from_config(cls, args_config: dict[str, Any], args: Arguments) -> Self:

if archinstall_lang := args_config.get('archinstall-language', None):
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
translation_handler.activate(arch_config.archinstall_language, set_font=False)

if disk_config := args_config.get('disk_config', {}):
enc_password = args_config.get('encryption_password', '')
Expand Down
6 changes: 3 additions & 3 deletions archinstall/lib/general/general_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ async def select_archinstall_language(languages: list[Language], preset: Languag
group = MenuItemGroup(items, sort_items=True)
group.set_focus_by_value(preset)

title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n'
title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n'
title = 'NOTE: Console font will be set automatically for supported languages.\n'
title += 'For other languages, fonts can be found in "/usr/share/kbd/consolefonts"\n'
title += 'and set manually with: setfont <fontname>\n'

result = await Selection[Language](
header=title,
Expand Down
99 changes: 97 additions & 2 deletions archinstall/lib/translationhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
import gettext
import json
import os
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import override

from archinstall.lib.command import SysCommand
from archinstall.lib.exceptions import SysCallError
from archinstall.lib.output import debug


@dataclass
class Language:
Expand All @@ -14,6 +19,7 @@ class Language:
translation: gettext.NullTranslations
translation_percent: int
translated_lang: str | None
console_font: str | None = None

@property
def display_name(self) -> str:
Expand All @@ -31,10 +37,68 @@ def json(self) -> str:
return self.name_en


_DEFAULT_FONT = 'default8x16'
_ENV_FONT = os.environ.get('FONT')


def _set_console_font(font_name: str | None) -> bool:
"""
Set the console font via setfont.
If font_name is None, sets default8x16.
On failure, keeps the current font unchanged.
Returns True on success, False on failure.
"""
target = font_name or _DEFAULT_FONT

try:
SysCommand(f'setfont {target}')
return True
except SysCallError as err:
debug(f'Failed to set console font {target}: {err}')
return False


def save_console_font() -> None:
"""Save the current console font (with unicode map) and console map to temp files."""
try:
font_fd, font_path = tempfile.mkstemp(prefix='archinstall_font_')
cmap_fd, cmap_path = tempfile.mkstemp(prefix='archinstall_cmap_')
os.close(font_fd)
os.close(cmap_fd)
translation_handler._font_backup = Path(font_path)
translation_handler._cmap_backup = Path(cmap_path)
SysCommand(f'setfont -O {translation_handler._font_backup} -om {translation_handler._cmap_backup}')
except SysCallError as err:
debug(f'Failed to save console font: {err}')
translation_handler._font_backup = None
translation_handler._cmap_backup = None


def restore_console_font() -> None:
"""Restore console font (with unicode map) and console map from backup."""
if translation_handler._font_backup is None or not translation_handler._font_backup.exists():
return

args = str(translation_handler._font_backup)
if translation_handler._cmap_backup is not None and translation_handler._cmap_backup.exists():
args += f' -m {translation_handler._cmap_backup}'
_set_console_font(args)

translation_handler._font_backup.unlink(missing_ok=True)
translation_handler._font_backup = None
if translation_handler._cmap_backup is not None:
translation_handler._cmap_backup.unlink(missing_ok=True)
translation_handler._cmap_backup = None


class TranslationHandler:
def __init__(self) -> None:
self._base_pot = 'base.pot'
self._languages = 'languages.json'
self._active_language: Language | None = None
self._font_backup: Path | None = None
self._cmap_backup: Path | None = None
self._using_env_font: bool = False

self._total_messages = self._get_total_active_messages()
self._translated_languages = self._get_translations()
Expand All @@ -43,6 +107,12 @@ def __init__(self) -> None:
def translated_languages(self) -> list[Language]:
return self._translated_languages

@property
def active_font(self) -> str | None:
if self._active_language is not None:
return self._active_language.console_font
return None

def _get_translations(self) -> list[Language]:
"""
Load all translated languages and return a list of such
Expand All @@ -57,6 +127,7 @@ def _get_translations(self) -> list[Language]:
abbr = mapping_entry['abbr']
lang = mapping_entry['lang']
translated_lang = mapping_entry.get('translated_lang', None)
console_font = mapping_entry.get('console_font', None)

try:
# get a translation for a specific language
Expand All @@ -71,7 +142,7 @@ def _get_translations(self) -> list[Language]:
# prevent cases where the .pot file is out of date and the percentage is above 100
percent = min(100, percent)

language = Language(abbr, lang, translation, percent, translated_lang)
language = Language(abbr, lang, translation, percent, translated_lang, console_font)
languages.append(language)
except FileNotFoundError as err:
raise FileNotFoundError(f"Could not locate language file for '{lang}': {err}")
Expand Down Expand Up @@ -127,12 +198,36 @@ def get_language_by_abbr(self, abbr: str) -> Language:
except Exception:
raise ValueError(f'No language with abbreviation "{abbr}" found')

def activate(self, language: Language) -> None:
def activate(self, language: Language, set_font: bool = True) -> None:
"""
Set the provided language as the current translation
"""
# The install() call has the side effect of assigning GNUTranslations.gettext to builtins._
language.translation.install()
self._active_language = language

if set_font and not self._using_env_font:
_set_console_font(language.console_font)

def apply_console_font(self) -> None:
"""Apply console font from FONT env var or active language mapping.

If FONT env var is set and valid, use it and skip language mapping.
If FONT is set but invalid, fall back to language font.
If FONT is not set, use active language font.
"""
if _ENV_FONT:
if _set_console_font(_ENV_FONT):
self._using_env_font = True
debug(f'Console font set from FONT env var: {_ENV_FONT}')
else:
debug(f'FONT={_ENV_FONT} could not be set, falling back to language font mapping')
if self.active_font:
_set_console_font(self.active_font)
debug(f'Console font set from language mapping: {self.active_font}')
elif self.active_font:
_set_console_font(self.active_font)
debug(f'Console font set from language mapping: {self.active_font}')

def _get_locales_dir(self) -> Path:
"""
Expand Down
2 changes: 1 addition & 1 deletion archinstall/locales/languages.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
{"abbr": "tr", "lang": "Turkish", "translated_lang" : "Türkçe"},
{"abbr": "tw", "lang": "Twi"},
{"abbr": "ug", "lang": "Uighur"},
{"abbr": "uk", "lang": "Ukrainian"},
{"abbr": "uk", "lang": "Ukrainian", "console_font": "UniCyr_8x16"},
{"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو"},
{"abbr": "uz", "lang": "Uzbek", "translated_lang": "O'zbek"},
{"abbr": "ve", "lang": "Venda"},
Expand Down
6 changes: 5 additions & 1 deletion archinstall/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from archinstall.lib.output import debug, error, info, warn
from archinstall.lib.packages.util import check_version_upgrade
from archinstall.lib.pacman.pacman import Pacman
from archinstall.lib.translationhandler import tr
from archinstall.lib.translationhandler import restore_console_font, save_console_font, tr
from archinstall.lib.utils.util import running_from_iso
from archinstall.tui.ui.components import tui

Expand Down Expand Up @@ -95,6 +95,8 @@ def run() -> int:
print(tr('Archinstall requires root privileges to run. See --help for more.'))
return 1

save_console_font()

_log_sys_info()

if not arch_config_handler.args.offline:
Expand Down Expand Up @@ -159,6 +161,8 @@ def main() -> int:
_error_message(exc)
rc = 1

restore_console_font()

return rc


Expand Down
10 changes: 10 additions & 0 deletions archinstall/tui/ui/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,13 @@ def __init__(self, main: InstanceRunnable[ValueT] | Callable[[], Awaitable[Value
super().__init__(ansi_color=True)
self._main = main

@override
async def _on_exit_app(self) -> None:
from archinstall.lib.translationhandler import restore_console_font

restore_console_font()
await super()._on_exit_app()

def action_trigger_help(self) -> None:
from textual.widgets import HelpPanel

Expand All @@ -1275,6 +1282,9 @@ def action_trigger_help(self) -> None:
_ = self.screen.mount(HelpPanel())

def on_mount(self) -> None:
from archinstall.lib.translationhandler import translation_handler

translation_handler.apply_console_font()
_translate_bindings(self._merged_bindings, self._bindings)
self._run_worker()

Expand Down
Loading