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
100 changes: 98 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,18 @@ def json(self) -> str:
return self.name_en


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


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 +57,63 @@ 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 _set_font(self, font_name: str | None) -> bool:
"""Set the console font via setfont. Only runs on ISO. Returns True on success."""
from archinstall.lib.utils.util import running_from_iso

if not running_from_iso():
return False

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(self) -> None:
"""Save the current console font (with unicode map) and console map to temp files."""
from archinstall.lib.utils.util import running_from_iso

if not running_from_iso():
return

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)
self._font_backup = Path(font_path)
self._cmap_backup = Path(cmap_path)
SysCommand(f'setfont -O {self._font_backup} -om {self._cmap_backup}')
except SysCallError as err:
debug(f'Failed to save console font: {err}')
self._font_backup = None
self._cmap_backup = None

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

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

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

def _get_translations(self) -> list[Language]:
"""
Load all translated languages and return a list of such
Expand All @@ -57,6 +128,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 +143,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 +199,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:
self._set_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 self._set_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:
self._set_font(self.active_font)
debug(f'Console font set from language mapping: {self.active_font}')
elif self.active_font:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not want this to be set on my actual host but only on the ISO, so can we check with

def running_from_iso() -> bool:

self._set_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 tr, translation_handler
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

translation_handler.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

translation_handler.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 translation_handler

translation_handler.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