11#!/usr/bin/env python3
2- """Generate Python type stubs for fluentogram translations.
2+ """Generate Python types file for fluentogram translations.
33
4- This script parses .ftl files and generates a .pyi stub file
5- that provides IDE autocomplete for translation keys.
4+ This script parses .ftl files and generates a types.py file
5+ that provides IDE autocomplete for translation keys with runtime
6+ compatibility via TYPE_CHECKING guard.
67"""
78
89import re
@@ -65,39 +66,53 @@ def extract_ftl_keys(ftl_content: str) -> list[tuple[str, list[str]]]:
6566 return keys
6667
6768
68- def generate_stub_content (all_keys : dict [str , list [str ]]) -> str :
69- """Generate .pyi stub content for FluentTranslator."""
69+ def generate_types_content (all_keys : dict [str , list [str ]]) -> str :
70+ """Generate types.py content with TYPE_CHECKING guard for FluentTranslator."""
7071 lines = [
71- '"""Auto-generated type stubs for fluentogram translations.' ,
72+ '"""Auto-generated types file for fluentogram translations.' ,
7273 "" ,
7374 "Generated by scripts/generate_i18n_stubs.py" ,
74- "Do not edit manually - regenerate with: python scripts/generate_i18n_stubs.py" ,
75+ "Do not edit manually - regenerate with: uv run python scripts/generate_i18n_stubs.py" ,
7576 '"""' ,
7677 "" ,
78+ "from typing import TYPE_CHECKING, TypeVar" ,
7779 "" ,
78- "class TranslatorRunner:" ,
79- ' """Type stubs for FluentTranslator with all available translation keys."""' ,
80+ "if TYPE_CHECKING:" ,
81+ ' _I18nArg = TypeVar("_I18nArg", str, int)' ,
82+ "" ,
83+ " class TranslatorRunner:" ,
84+ ' """Type stubs for FluentTranslator with all available translation keys."""' ,
85+ "" ,
86+ " def get(self, key: str, **kwargs: _I18nArg) -> str: ..." ,
8087 "" ,
8188 ]
8289
8390 for key , params in sorted (all_keys .items ()):
8491 # Convert kebab-case to snake_case for method name
8592 method_name = key .replace ("-" , "_" )
8693 if params :
87- param_str = ", " .join (f"{ p } : str | int " for p in params )
88- lines .append (f" def { method_name } (self, *, { param_str } ) -> str: ..." )
94+ param_str = ", " .join (f"{ p } : _I18nArg " for p in params )
95+ lines .append (f" def { method_name } (self, *, { param_str } ) -> str: ..." )
8996 else :
90- lines .append (f" def { method_name } (self) -> str: ..." )
97+ lines .append (f" def { method_name } (self) -> str: ..." )
98+
99+ lines .extend (
100+ [
101+ "" ,
102+ "else:" ,
103+ " from fluentogram import TranslatorRunner # noqa: F401" ,
104+ "" ,
105+ ]
106+ )
91107
92- lines .append ("" )
93108 return "\n " .join (lines )
94109
95110
96111def main () -> None :
97- """Generate stubs from locale files."""
112+ """Generate types file from locale files."""
98113 project_root = Path (__file__ ).parent .parent
99114 locales_dir = project_root / "locales"
100- output_file = project_root / "src" / "infrastructure" / "i18n" / "stubs.pyi "
115+ output_file = project_root / "src" / "infrastructure" / "i18n" / "types.py "
101116
102117 # Use English as the reference locale
103118 en_dir = locales_dir / "en"
@@ -110,7 +125,7 @@ def main() -> None:
110125
111126 # Parse all .ftl files in the English locale
112127 for ftl_file in en_dir .glob ("*.ftl" ):
113- content = ftl_file .read_text ()
128+ content = ftl_file .read_text (encoding = "utf-8" )
114129 keys = extract_ftl_keys (content )
115130 for key , params in keys :
116131 all_keys [key ] = params
@@ -120,12 +135,12 @@ def main() -> None:
120135 print ("Error: No translation keys found" )
121136 return
122137
123- # Generate stub content
124- stub_content = generate_stub_content (all_keys )
138+ # Generate types content
139+ types_content = generate_types_content (all_keys )
125140
126- # Write stub file
141+ # Write types file
127142 output_file .parent .mkdir (parents = True , exist_ok = True )
128- output_file .write_text (stub_content )
143+ output_file .write_text (types_content )
129144 print (f"\n Generated { output_file } with { len (all_keys )} translation methods" )
130145
131146
0 commit comments