Source code for i18n_check.cli.generate_config_file

# SPDX-License-Identifier: GPL-3.0-or-later
"""
Functionality to generate a configuration file for i18n-check.
"""

import os
from pathlib import Path
from typing import Any

from rich import print as rprint
from yaml import safe_load

from i18n_check.cli.generate_test_frontends import generate_test_frontends

EXTERNAL_TEST_FRONTENDS_DIR_PATH = Path.cwd() / "i18n_check_test_frontends"

# Note: Repeat from utils to avoid circular import.
PATH_SEPARATOR = "\\" if os.name == "nt" else "/"


[docs] def config_file_is_valid() -> bool: """ Check that the configuration file for i18n-check is not empty and has the necessary keys. Returns ------- bool True if the i18n-check configuration file is valid. False otherwise. """ from i18n_check.utils import YAML_CONFIG_FILE_PATH with open(YAML_CONFIG_FILE_PATH, "r", encoding="utf-8") as file: config = safe_load(file) if config is not None: config_keys = config.keys() if "src-dir" not in config_keys or not isinstance(config["src-dir"], str): rprint( "[red]The i18n-check 'src-dir' argument has not been defined properly. Please check the configuration file and try again.[/red]" ) return False if "i18n-dir" not in config_keys or not isinstance(config["i18n-dir"], str): rprint( "[red]The i18n-check 'i18n-dir' argument has not been defined properly. Please check the configuration file and try again.[/red]" ) return False if "i18n-src" not in config_keys or not isinstance(config["i18n-src"], str): rprint( "[red]The i18n-check 'i18n-src' argument has not been defined properly. Please check the configuration file and try again.[/red]" ) return False if "file-types-to-check" not in config_keys or not isinstance( config["file-types-to-check"], list ): rprint( "[red]The i18n-check 'file-types-to-check' argument has not been defined properly. Please check the configuration file and try again.[/red]" ) return False if ( "checks" not in config_keys or not isinstance(config["checks"], dict) # No checks including global have been found in the config file. or len( set(config["checks"].keys()) & set( [ "global", "key-formatting", "key-naming", "nonexistent-keys", "unused-keys", "non-source-keys", "repeat-keys", "repeat-values", "sorted-keys", "nested-files", "missing-keys", "aria-labels", "alt-texts", ] ) ) == 0 ): rprint( "[red]The i18n-check 'checks' argument has not been defined properly. Please check the configuration file and try again.[/red]" ) return False return True else: rprint( "[red]The i18n-check configuration file is empty. Please regenerate your config file with i18n-check -gcf.[/red]" ) return False
[docs] def write_to_file( src_dir: str, i18n_dir: str, i18n_src_file: str, file_types_to_check: list[str] | None, checks: dict[str, dict], ) -> None: """ Writing to the i18n source JSON file. Parameters ---------- src_dir : str Input src dir directory. i18n_dir : str Input i18n-dir directory. i18n_src_file : str Input i18n-dir-src directory. file_types_to_check : list[str] Input file extensions for checks. checks : dict The boolean values for checks being enabled or not. """ # Import here to avoid circular import. from i18n_check.utils import get_config_file_path config_file_path = get_config_file_path() with open(config_file_path, "w", encoding="utf-8") as file: checks_str = "" for c in checks: checks_str += f" {c}:\n active: {checks[c]['active']}\n" if "directories-to-skip" in checks[c]: checks_str += f" directories-to-skip: [{', '.join(checks[c]['directories-to-skip'])}]\n" if "files-to-skip" in checks[c]: checks_str += ( f" files-to-skip: [{', '.join(checks[c]['files-to-skip'])}]\n" ) if "keys-to-ignore" in checks[c]: if isinstance(checks[c]["keys-to-ignore"], list): keys_list = ", ".join( f'"{key}"' for key in checks[c]["keys-to-ignore"] ) checks_str += f" keys-to-ignore: [{keys_list}]\n" else: checks_str += ( f' keys-to-ignore: "{checks[c]["keys-to-ignore"]}"\n' ) if "locales-to-check" in checks[c]: checks_str += f" locales-to-check: [{', '.join(checks[c]['locales-to-check'])}]\n" if "search-dirs" in checks[c]: checks_str += ( f" search-dirs: [{', '.join(checks[c]['search-dirs'])}]\n" ) file_types_to_check_str = ( ", ".join(file_types_to_check) if file_types_to_check else "" ) config_string = f"""# Configuration file for i18n-check validation. # See https://github.com/activist-org/i18n-check for details. src-dir: {src_dir} i18n-dir: {i18n_dir} i18n-src: {i18n_src_file} file-types-to-check: [{file_types_to_check_str}] checks: # Global configurations are applied to all checks. {checks_str} """ file.write(config_string)
[docs] def receive_data() -> None: """ Interact with user to configure a .yml file. """ src_dir = input("Enter src dir [frontend]: ").strip() or "frontend" i18n_dir = ( input(f"Enter i18n-dir [frontend{PATH_SEPARATOR}i18n]: ").strip() or f"frontend{PATH_SEPARATOR}i18n" ) i18n_src_file = ( input( f"Enter i18n-src file [frontend{PATH_SEPARATOR}i18n{PATH_SEPARATOR}en.json]: " ).strip() or f"frontend{PATH_SEPARATOR}i18n{PATH_SEPARATOR}en.json" ) file_types_to_check = input( "Enter the file extension types to check [.ts, .js]: " ).split() or [".ts", ".js"] print("Answer using y or n to select your required checks.") checks: dict[str, dict[str, Any]] = { "global": { "title": "all checks", "active": False, "directories-to-skip": [], "files-to-skip": [], }, "invalid_keys": { "title": "invalid keys", "active": False, "directories-to-skip": [], "files-to-skip": [], "keys-to-ignore": [], }, "nonexistent_keys": { "title": "non existent keys", "active": False, "directories-to-skip": [], "files-to-skip": [], "search-dirs": [], }, "unused_keys": { "title": "unused keys", "active": False, "directories-to-skip": [], "files-to-skip": [], }, "non_source_keys": {"title": "non source keys", "active": False}, "repeat_keys": {"title": "repeat keys", "active": False}, "repeat_values": {"title": "repeat values", "active": False}, "sorted_keys": {"title": "sorted keys", "active": False}, "nested_files": {"title": "nested keys", "active": False}, "missing_keys": { "title": "missing keys", "active": False, "locales-to-check": [], }, } for c, v in checks.items(): if not checks["global"]["active"]: check_prompt = input( f"{str(checks[c]['title']).capitalize()} check [y]: " ).lower() if checks["global"]["active"] or check_prompt in ["y", ""]: checks[c]["active"] = True if "directories-to-skip" in v: if c == "global": directories_to_skip = input( f"Directories to skip for {checks[c]['title']} [frontend{PATH_SEPARATOR}node_modules]: " ).lower() checks[c]["directories-to-skip"] = ( directories_to_skip if directories_to_skip != "" else [f"frontend{PATH_SEPARATOR}node_modules"] ) else: directories_to_skip = input( f"Directories to skip for {checks[c]['title']} [None]: " ).lower() checks[c]["directories-to-skip"] = ( directories_to_skip if directories_to_skip != "" else [] ) if "files-to-skip" in checks[c]: files_to_skip = input( f"Files to skip for {checks[c]['title']} [None]: " ).lower() checks[c]["files-to-skip"] = files_to_skip if files_to_skip != "" else [] if "keys-to-ignore" in checks[c]: keys_to_ignore_input = input( f"Keys to ignore for {checks[c]['title']} (comma-separated regex patterns) [None]: " ) if keys_to_ignore_input.strip(): patterns = [ pattern.strip() for pattern in keys_to_ignore_input.split(",") ] # Filter out empty patterns. checks[c]["keys-to-ignore"] = [p for p in patterns if p] else: checks[c]["keys-to-ignore"] = [] if "locales-to-check" in checks[c]: locales_to_check = input( f"Locales to check for {checks[c]['title']} (comma-separated, e.g., fr, de) [All]: " ) if locales_to_check.strip(): checks[c]["locales-to-check"] = [ locale.strip() for locale in locales_to_check.split(",") ] else: checks[c]["locales-to-check"] = [] if "search-dirs" in checks[c]: search_dirs = input( f"Additional search directories for {checks[c]['title']} (comma-separated, e.g., frontend/test, frontend/test-e2e) [None]: " ) if search_dirs.strip(): checks[c]["search-dirs"] = [ dir.strip() for dir in search_dirs.split(",") ] else: checks[c]["search-dirs"] = [] write_to_file( src_dir=src_dir, i18n_dir=i18n_dir, i18n_src_file=i18n_src_file, file_types_to_check=file_types_to_check, checks=checks, )
[docs] def generate_config_file() -> None: """ Generate a configuration file for i18n-check based on user inputs. """ # Import here to avoid circular import. from i18n_check.utils import get_config_file_path config_path = get_config_file_path() if config_path.is_file(): config_file_name = config_path.name print( f"An i18n-check configuration file already exists. Would you like to re-configure your {config_file_name} file?" ) reconfigure_choice = input("Press y or n to continue [y]: ").lower() if reconfigure_choice in ["y", ""]: print("Configuring...") receive_data() print(f"Your {config_file_name} file has been generated successfully.") if not Path(EXTERNAL_TEST_FRONTENDS_DIR_PATH).is_dir(): test_frontend_choice = input( "\nWould you like to generate test pseudocode frontends to experiment with i18n-check?" "\nPress y to generate an i18n_check_test_frontends directory [y]: " ).lower() if test_frontend_choice in ["y", ""]: generate_test_frontends() else: print("Exiting.") else: print("Exiting.") else: print( "You do not have an i18n-check configuration file. Follow the commands below to generate a .i18n-check.yaml configuration file..." ) receive_data() config_path = get_config_file_path() print(f"Your {config_path.name} file has been generated successfully.")