gac 1.14.0__tar.gz → 1.15.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of gac might be problematic. Click here for more details.

Files changed (42) hide show
  1. {gac-1.14.0 → gac-1.15.0}/PKG-INFO +10 -1
  2. {gac-1.14.0 → gac-1.15.0}/README.md +9 -0
  3. {gac-1.14.0 → gac-1.15.0}/src/gac/__version__.py +1 -1
  4. {gac-1.14.0 → gac-1.15.0}/src/gac/cli.py +12 -1
  5. {gac-1.14.0 → gac-1.15.0}/src/gac/config.py +2 -0
  6. {gac-1.14.0 → gac-1.15.0}/src/gac/constants.py +87 -0
  7. {gac-1.14.0 → gac-1.15.0}/src/gac/init_cli.py +75 -0
  8. gac-1.15.0/src/gac/language_cli.py +111 -0
  9. {gac-1.14.0 → gac-1.15.0}/src/gac/main.py +13 -4
  10. {gac-1.14.0 → gac-1.15.0}/src/gac/prompt.py +40 -7
  11. {gac-1.14.0 → gac-1.15.0}/.gitignore +0 -0
  12. {gac-1.14.0 → gac-1.15.0}/LICENSE +0 -0
  13. {gac-1.14.0 → gac-1.15.0}/pyproject.toml +0 -0
  14. {gac-1.14.0 → gac-1.15.0}/src/gac/__init__.py +0 -0
  15. {gac-1.14.0 → gac-1.15.0}/src/gac/ai.py +0 -0
  16. {gac-1.14.0 → gac-1.15.0}/src/gac/ai_utils.py +0 -0
  17. {gac-1.14.0 → gac-1.15.0}/src/gac/config_cli.py +0 -0
  18. {gac-1.14.0 → gac-1.15.0}/src/gac/diff_cli.py +0 -0
  19. {gac-1.14.0 → gac-1.15.0}/src/gac/errors.py +0 -0
  20. {gac-1.14.0 → gac-1.15.0}/src/gac/git.py +0 -0
  21. {gac-1.14.0 → gac-1.15.0}/src/gac/preprocess.py +0 -0
  22. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/__init__.py +0 -0
  23. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/anthropic.py +0 -0
  24. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/cerebras.py +0 -0
  25. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/chutes.py +0 -0
  26. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/custom_anthropic.py +0 -0
  27. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/custom_openai.py +0 -0
  28. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/deepseek.py +0 -0
  29. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/fireworks.py +0 -0
  30. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/gemini.py +0 -0
  31. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/groq.py +0 -0
  32. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/lmstudio.py +0 -0
  33. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/minimax.py +0 -0
  34. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/ollama.py +0 -0
  35. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/openai.py +0 -0
  36. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/openrouter.py +0 -0
  37. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/streamlake.py +0 -0
  38. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/synthetic.py +0 -0
  39. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/together.py +0 -0
  40. {gac-1.14.0 → gac-1.15.0}/src/gac/providers/zai.py +0 -0
  41. {gac-1.14.0 → gac-1.15.0}/src/gac/security.py +0 -0
  42. {gac-1.14.0 → gac-1.15.0}/src/gac/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gac
3
- Version: 1.14.0
3
+ Version: 1.15.0
4
4
  Summary: LLM-powered Git commit message generator with multi-provider support
5
5
  Project-URL: Homepage, https://github.com/cellwebb/gac
6
6
  Project-URL: Documentation, https://github.com/cellwebb/gac#readme
@@ -108,6 +108,13 @@ gac
108
108
  - **Standard** (default): Summary with bullet points explaining implementation details
109
109
  - **Verbose** (-v flag): Comprehensive explanations including motivation, technical approach, and impact analysis
110
110
 
111
+ ### 🌍 **Multilingual Support**
112
+
113
+ - **25+ languages**: Generate commit messages in English, Chinese, Japanese, Korean, Spanish, French, German, and 20+ more languages
114
+ - **Flexible translation**: Choose to keep conventional commit prefixes in English for tool compatibility, or fully translate them
115
+ - **Multiple workflows**: Set a default language with `gac language`, or use `-l <language>` flag for one-time overrides
116
+ - **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Arabic, and more
117
+
111
118
  ### 💻 **Developer Experience**
112
119
 
113
120
  - **Interactive feedback**: Regenerate messages with specific requests like `r "make it shorter"` or `r "focus on the bug fix"`
@@ -197,6 +204,8 @@ ANTHROPIC_API_KEY=your_key_here
197
204
 
198
205
  See `.gac.env.example` for all available options.
199
206
 
207
+ **Want commit messages in another language?** Run `gac language` to select from 25+ languages including Español, Français, 日本語, and more.
208
+
200
209
  **Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
201
210
 
202
211
  ---
@@ -67,6 +67,13 @@ gac
67
67
  - **Standard** (default): Summary with bullet points explaining implementation details
68
68
  - **Verbose** (-v flag): Comprehensive explanations including motivation, technical approach, and impact analysis
69
69
 
70
+ ### 🌍 **Multilingual Support**
71
+
72
+ - **25+ languages**: Generate commit messages in English, Chinese, Japanese, Korean, Spanish, French, German, and 20+ more languages
73
+ - **Flexible translation**: Choose to keep conventional commit prefixes in English for tool compatibility, or fully translate them
74
+ - **Multiple workflows**: Set a default language with `gac language`, or use `-l <language>` flag for one-time overrides
75
+ - **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Arabic, and more
76
+
70
77
  ### 💻 **Developer Experience**
71
78
 
72
79
  - **Interactive feedback**: Regenerate messages with specific requests like `r "make it shorter"` or `r "focus on the bug fix"`
@@ -156,6 +163,8 @@ ANTHROPIC_API_KEY=your_key_here
156
163
 
157
164
  See `.gac.env.example` for all available options.
158
165
 
166
+ **Want commit messages in another language?** Run `gac language` to select from 25+ languages including Español, Français, 日本語, and more.
167
+
159
168
  **Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
160
169
 
161
170
  ---
@@ -1,3 +1,3 @@
1
1
  """Version information for gac package."""
2
2
 
3
- __version__ = "1.14.0"
3
+ __version__ = "1.15.0"
@@ -13,10 +13,11 @@ import click
13
13
  from gac import __version__
14
14
  from gac.config import load_config
15
15
  from gac.config_cli import config as config_cli
16
- from gac.constants import Logging
16
+ from gac.constants import Languages, Logging
17
17
  from gac.diff_cli import diff as diff_cli
18
18
  from gac.errors import handle_error
19
19
  from gac.init_cli import init as init_cli
20
+ from gac.language_cli import language as language_cli
20
21
  from gac.main import main
21
22
  from gac.utils import setup_logging
22
23
 
@@ -43,6 +44,9 @@ logger = logging.getLogger(__name__)
43
44
  @click.option("--hint", "-h", default="", help="Additional context to include in the prompt")
44
45
  # Model options
45
46
  @click.option("--model", "-m", help="Override the default model (format: 'provider:model_name')")
47
+ @click.option(
48
+ "--language", "-l", help="Override the language for commit messages (e.g., 'Spanish', 'es', 'zh-CN', 'ja')"
49
+ )
46
50
  # Output options
47
51
  @click.option("--quiet", "-q", is_flag=True, help="Suppress non-error output")
48
52
  @click.option(
@@ -75,6 +79,7 @@ def cli(
75
79
  yes: bool = False,
76
80
  hint: str = "",
77
81
  model: str | None = None,
82
+ language: str | None = None,
78
83
  version: bool = False,
79
84
  dry_run: bool = False,
80
85
  verbose: bool = False,
@@ -98,6 +103,9 @@ def cli(
98
103
  # Determine if verbose mode should be enabled based on -v flag or verbose config setting
99
104
  use_verbose = bool(verbose or config.get("verbose", False))
100
105
 
106
+ # Resolve language code to full name if provided
107
+ resolved_language = Languages.resolve_code(language) if language else None
108
+
101
109
  try:
102
110
  main(
103
111
  stage_all=add_all,
@@ -113,6 +121,7 @@ def cli(
113
121
  verbose=use_verbose,
114
122
  no_verify=no_verify,
115
123
  skip_secret_scan=skip_secret_scan or bool(config.get("skip_secret_scan", False)),
124
+ language=resolved_language,
116
125
  )
117
126
  except Exception as e:
118
127
  handle_error(e, exit_program=True)
@@ -131,6 +140,7 @@ def cli(
131
140
  "yes": yes,
132
141
  "hint": hint,
133
142
  "model": model,
143
+ "language": language,
134
144
  "version": version,
135
145
  "dry_run": dry_run,
136
146
  "verbose": verbose,
@@ -141,6 +151,7 @@ def cli(
141
151
 
142
152
  cli.add_command(config_cli)
143
153
  cli.add_command(init_cli)
154
+ cli.add_command(language_cli)
144
155
  cli.add_command(diff_cli)
145
156
 
146
157
  if __name__ == "__main__":
@@ -39,6 +39,8 @@ def load_config() -> dict[str, str | int | float | bool | None]:
39
39
  in ("true", "1", "yes", "on"),
40
40
  "verbose": os.getenv("GAC_VERBOSE", str(EnvDefaults.VERBOSE)).lower() in ("true", "1", "yes", "on"),
41
41
  "system_prompt_path": os.getenv("GAC_SYSTEM_PROMPT_PATH"),
42
+ "language": os.getenv("GAC_LANGUAGE"),
43
+ "translate_prefixes": os.getenv("GAC_TRANSLATE_PREFIXES", "false").lower() in ("true", "1", "yes", "on"),
42
44
  }
43
45
 
44
46
  return config
@@ -152,6 +152,93 @@ class CodePatternImportance:
152
152
  }
153
153
 
154
154
 
155
+ class Languages:
156
+ """Language code mappings and utilities."""
157
+
158
+ # Language code to full name mapping
159
+ # Supports ISO 639-1 codes and common variants
160
+ CODE_MAP: dict[str, str] = {
161
+ # English
162
+ "en": "English",
163
+ # Chinese
164
+ "zh": "Simplified Chinese",
165
+ "zh-cn": "Simplified Chinese",
166
+ "zh-hans": "Simplified Chinese",
167
+ "zh-tw": "Traditional Chinese",
168
+ "zh-hant": "Traditional Chinese",
169
+ # Japanese
170
+ "ja": "Japanese",
171
+ # Korean
172
+ "ko": "Korean",
173
+ # Spanish
174
+ "es": "Spanish",
175
+ # Portuguese
176
+ "pt": "Portuguese",
177
+ # French
178
+ "fr": "French",
179
+ # German
180
+ "de": "German",
181
+ # Russian
182
+ "ru": "Russian",
183
+ # Hindi
184
+ "hi": "Hindi",
185
+ # Italian
186
+ "it": "Italian",
187
+ # Polish
188
+ "pl": "Polish",
189
+ # Turkish
190
+ "tr": "Turkish",
191
+ # Dutch
192
+ "nl": "Dutch",
193
+ # Vietnamese
194
+ "vi": "Vietnamese",
195
+ # Thai
196
+ "th": "Thai",
197
+ # Indonesian
198
+ "id": "Indonesian",
199
+ # Swedish
200
+ "sv": "Swedish",
201
+ # Arabic
202
+ "ar": "Arabic",
203
+ # Hebrew
204
+ "he": "Hebrew",
205
+ # Greek
206
+ "el": "Greek",
207
+ # Danish
208
+ "da": "Danish",
209
+ # Norwegian
210
+ "no": "Norwegian",
211
+ "nb": "Norwegian",
212
+ "nn": "Norwegian",
213
+ # Finnish
214
+ "fi": "Finnish",
215
+ }
216
+
217
+ @staticmethod
218
+ def resolve_code(language: str) -> str:
219
+ """Resolve a language code to its full name.
220
+
221
+ Args:
222
+ language: Language name or code (e.g., 'Spanish', 'es', 'zh-CN')
223
+
224
+ Returns:
225
+ Full language name (e.g., 'Spanish', 'Simplified Chinese')
226
+
227
+ If the input is already a full language name, it's returned as-is.
228
+ If it's a recognized code, it's converted to the full name.
229
+ Otherwise, the input is returned unchanged (for custom languages).
230
+ """
231
+ # Normalize the code to lowercase for lookup
232
+ code_lower = language.lower().strip()
233
+
234
+ # Check if it's a recognized code
235
+ if code_lower in Languages.CODE_MAP:
236
+ return Languages.CODE_MAP[code_lower]
237
+
238
+ # Return as-is (could be a full name or custom language)
239
+ return language
240
+
241
+
155
242
  class CommitMessageConstants:
156
243
  """Constants for commit message generation and cleaning."""
157
244
 
@@ -151,4 +151,79 @@ def init() -> None:
151
151
  elif is_ollama or is_lmstudio:
152
152
  click.echo("Skipping API key. You can add one later if needed.")
153
153
 
154
+ # Language selection
155
+ click.echo("\n")
156
+ languages = [
157
+ ("English", "English"),
158
+ ("简体中文", "Simplified Chinese"),
159
+ ("繁體中文", "Traditional Chinese"),
160
+ ("日本語", "Japanese"),
161
+ ("한국어", "Korean"),
162
+ ("Español", "Spanish"),
163
+ ("Português", "Portuguese"),
164
+ ("Français", "French"),
165
+ ("Deutsch", "German"),
166
+ ("Русский", "Russian"),
167
+ ("हिन्दी", "Hindi"),
168
+ ("Italiano", "Italian"),
169
+ ("Polski", "Polish"),
170
+ ("Türkçe", "Turkish"),
171
+ ("Nederlands", "Dutch"),
172
+ ("Tiếng Việt", "Vietnamese"),
173
+ ("ไทย", "Thai"),
174
+ ("Bahasa Indonesia", "Indonesian"),
175
+ ("Svenska", "Swedish"),
176
+ ("العربية", "Arabic"),
177
+ ("עברית", "Hebrew"),
178
+ ("Ελληνικά", "Greek"),
179
+ ("Dansk", "Danish"),
180
+ ("Norsk", "Norwegian"),
181
+ ("Suomi", "Finnish"),
182
+ ("Custom", "Custom"),
183
+ ]
184
+
185
+ display_names = [lang[0] for lang in languages]
186
+ language_selection = questionary.select(
187
+ "Select a language for commit messages:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
188
+ ).ask()
189
+
190
+ if not language_selection:
191
+ click.echo("Language selection cancelled. Using English (default).")
192
+ elif language_selection == "English":
193
+ click.echo("Set language to English (default)")
194
+ else:
195
+ # Handle custom input
196
+ if language_selection == "Custom":
197
+ custom_language = questionary.text("Enter the language name (e.g., 'Spanish', 'Français', '日本語'):").ask()
198
+ if not custom_language or not custom_language.strip():
199
+ click.echo("No language entered. Using English (default).")
200
+ language_value = None
201
+ else:
202
+ language_value = custom_language.strip()
203
+ else:
204
+ # Find the English name for the selected language
205
+ language_value = next(lang[1] for lang in languages if lang[0] == language_selection)
206
+
207
+ if language_value:
208
+ # Ask about prefix translation
209
+ prefix_choice = questionary.select(
210
+ "How should conventional commit prefixes be handled?",
211
+ choices=[
212
+ "Keep prefixes in English (feat:, fix:, etc.)",
213
+ f"Translate prefixes into {language_value}",
214
+ ],
215
+ ).ask()
216
+
217
+ if not prefix_choice:
218
+ click.echo("Prefix translation selection cancelled. Using English prefixes.")
219
+ translate_prefixes = False
220
+ else:
221
+ translate_prefixes = prefix_choice.startswith("Translate prefixes")
222
+
223
+ # Set the language and prefix translation preference
224
+ set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
225
+ set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
226
+ click.echo(f"Set GAC_LANGUAGE={language_value}")
227
+ click.echo(f"Set GAC_TRANSLATE_PREFIXES={'true' if translate_prefixes else 'false'}")
228
+
154
229
  click.echo(f"\ngac environment setup complete. You can edit {GAC_ENV_PATH} to update values later.")
@@ -0,0 +1,111 @@
1
+ """CLI for selecting commit message language interactively."""
2
+
3
+ from pathlib import Path
4
+
5
+ import click
6
+ import questionary
7
+ from dotenv import set_key, unset_key
8
+
9
+ GAC_ENV_PATH = Path.home() / ".gac.env"
10
+
11
+
12
+ @click.command()
13
+ def language() -> None:
14
+ """Set the language for commit messages interactively."""
15
+ click.echo("Select a language for your commit messages:\n")
16
+
17
+ # Languages sorted by programmer population likelihood
18
+ # Based on GitHub statistics and global developer demographics
19
+ languages = [
20
+ ("English", "English"),
21
+ ("简体中文", "Simplified Chinese"),
22
+ ("繁體中文", "Traditional Chinese"),
23
+ ("日本語", "Japanese"),
24
+ ("한국어", "Korean"),
25
+ ("Español", "Spanish"),
26
+ ("Português", "Portuguese"),
27
+ ("Français", "French"),
28
+ ("Deutsch", "German"),
29
+ ("Русский", "Russian"),
30
+ ("हिन्दी", "Hindi"),
31
+ ("Italiano", "Italian"),
32
+ ("Polski", "Polish"),
33
+ ("Türkçe", "Turkish"),
34
+ ("Nederlands", "Dutch"),
35
+ ("Tiếng Việt", "Vietnamese"),
36
+ ("ไทย", "Thai"),
37
+ ("Bahasa Indonesia", "Indonesian"),
38
+ ("Svenska", "Swedish"),
39
+ ("العربية", "Arabic"),
40
+ ("עברית", "Hebrew"),
41
+ ("Ελληνικά", "Greek"),
42
+ ("Dansk", "Danish"),
43
+ ("Norsk", "Norwegian"),
44
+ ("Suomi", "Finnish"),
45
+ ("Custom", "Custom"),
46
+ ]
47
+
48
+ display_names = [lang[0] for lang in languages]
49
+ selection = questionary.select(
50
+ "Choose your language:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
51
+ ).ask()
52
+
53
+ if not selection:
54
+ click.echo("Language selection cancelled.")
55
+ return
56
+
57
+ # Ensure .gac.env exists
58
+ if not GAC_ENV_PATH.exists():
59
+ GAC_ENV_PATH.touch()
60
+ click.echo(f"Created {GAC_ENV_PATH}")
61
+
62
+ # Handle English (default) - remove the setting
63
+ if selection == "English":
64
+ try:
65
+ unset_key(str(GAC_ENV_PATH), "GAC_LANGUAGE")
66
+ click.echo("✓ Set language to English (default)")
67
+ click.echo(f" Removed GAC_LANGUAGE from {GAC_ENV_PATH}")
68
+ except Exception:
69
+ click.echo("✓ Set language to English (default)")
70
+ return
71
+
72
+ # Handle custom input
73
+ if selection == "Custom":
74
+ custom_language = questionary.text("Enter the language name (e.g., 'Spanish', 'Français', '日本語'):").ask()
75
+ if not custom_language or not custom_language.strip():
76
+ click.echo("No language entered. Cancelled.")
77
+ return
78
+ language_value = custom_language.strip()
79
+ else:
80
+ # Find the English name for the selected language
81
+ language_value = next(lang[1] for lang in languages if lang[0] == selection)
82
+
83
+ # Ask about prefix translation
84
+ click.echo() # Blank line for spacing
85
+ prefix_choice = questionary.select(
86
+ "How should conventional commit prefixes be handled?",
87
+ choices=[
88
+ "Keep prefixes in English (feat:, fix:, etc.)",
89
+ f"Translate prefixes into {language_value}",
90
+ ],
91
+ ).ask()
92
+
93
+ if not prefix_choice:
94
+ click.echo("Prefix translation selection cancelled.")
95
+ return
96
+
97
+ translate_prefixes = prefix_choice.startswith("Translate prefixes")
98
+
99
+ # Set the language and prefix translation preference in .gac.env
100
+ set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
101
+ set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
102
+
103
+ click.echo(f"\n✓ Set language to {selection}")
104
+ click.echo(f" GAC_LANGUAGE={language_value}")
105
+ if translate_prefixes:
106
+ click.echo(" GAC_TRANSLATE_PREFIXES=true")
107
+ click.echo("\n Prefixes will be translated (e.g., 'corrección:' instead of 'fix:')")
108
+ else:
109
+ click.echo(" GAC_TRANSLATE_PREFIXES=false")
110
+ click.echo(f"\n Prefixes will remain in English (e.g., 'fix: <{language_value} description>')")
111
+ click.echo(f"\n Configuration saved to {GAC_ENV_PATH}")
@@ -46,6 +46,7 @@ def main(
46
46
  verbose: bool = False,
47
47
  no_verify: bool = False,
48
48
  skip_secret_scan: bool = False,
49
+ language: str | None = None,
49
50
  ) -> None:
50
51
  """Main application logic for gac."""
51
52
  try:
@@ -186,6 +187,14 @@ def main(
186
187
  system_template_path_value if isinstance(system_template_path_value, str) else None
187
188
  )
188
189
 
190
+ # Use language parameter if provided, otherwise fall back to config
191
+ if language is None:
192
+ language_value = config.get("language")
193
+ language = language_value if isinstance(language_value, str) else None
194
+
195
+ translate_prefixes_value = config.get("translate_prefixes")
196
+ translate_prefixes: bool = bool(translate_prefixes_value) if isinstance(translate_prefixes_value, bool) else False
197
+
189
198
  system_prompt, user_prompt = build_prompt(
190
199
  status=status,
191
200
  processed_diff=processed_diff,
@@ -195,6 +204,8 @@ def main(
195
204
  infer_scope=infer_scope,
196
205
  verbose=verbose,
197
206
  system_template_path=system_template_path,
207
+ language=language,
208
+ translate_prefixes=translate_prefixes,
198
209
  )
199
210
 
200
211
  if show_prompt:
@@ -244,10 +255,8 @@ def main(
244
255
  max_retries=max_retries,
245
256
  quiet=quiet,
246
257
  )
247
- # Don't enforce conventional commits when using custom system prompts
248
- commit_message = clean_commit_message(
249
- raw_commit_message, enforce_conventional_commits=(system_template_path is None)
250
- )
258
+ # Clean the commit message (no automatic prefix enforcement)
259
+ commit_message = clean_commit_message(raw_commit_message)
251
260
 
252
261
  logger.info("Generated commit message:")
253
262
  logger.info(commit_message)
@@ -263,6 +263,12 @@ IMMEDIATELY AFTER ANALYZING THE CHANGES, RESPOND WITH ONLY THE COMMIT MESSAGE.
263
263
  DO NOT include any preamble, reasoning, explanations or anything other than the commit message itself.
264
264
  DO NOT use markdown formatting, headers, or code blocks.
265
265
  The entire response will be passed directly to 'git commit -m'.
266
+
267
+ <language>
268
+ IMPORTANT: You MUST write the entire commit message in <language_name></language_name>.
269
+ All text in the commit message, including the summary line and body, must be in <language_name></language_name>.
270
+ <prefix_instruction></prefix_instruction>
271
+ </language>
266
272
  </instructions>"""
267
273
 
268
274
 
@@ -449,6 +455,8 @@ def build_prompt(
449
455
  hint: str = "",
450
456
  verbose: bool = False,
451
457
  system_template_path: str | None = None,
458
+ language: str | None = None,
459
+ translate_prefixes: bool = False,
452
460
  ) -> tuple[str, str]:
453
461
  """Build system and user prompts for the AI model using the provided templates and git information.
454
462
 
@@ -461,6 +469,8 @@ def build_prompt(
461
469
  hint: Optional hint to guide the AI
462
470
  verbose: Whether to generate detailed commit messages with motivation, architecture, and impact sections
463
471
  system_template_path: Optional path to custom system template
472
+ language: Optional language for commit messages (e.g., "Spanish", "French", "Japanese")
473
+ translate_prefixes: Whether to translate conventional commit prefixes (default: False keeps them in English)
464
474
 
465
475
  Returns:
466
476
  Tuple of (system_prompt, user_prompt) ready to be sent to an AI model
@@ -484,6 +494,33 @@ def build_prompt(
484
494
  user_template = _remove_template_section(user_template, "hint")
485
495
  logger.debug("No hint provided")
486
496
 
497
+ if language:
498
+ user_template = user_template.replace("<language_name></language_name>", language)
499
+
500
+ # Set prefix instruction based on translate_prefixes setting
501
+ if translate_prefixes:
502
+ prefix_instruction = f"""CRITICAL: You MUST translate the conventional commit prefix into {language}.
503
+ DO NOT use English prefixes like 'feat:', 'fix:', 'docs:', etc.
504
+ Instead, translate them into {language} equivalents.
505
+ Examples:
506
+ - 'feat:' → translate to {language} word for 'feature' or 'add'
507
+ - 'fix:' → translate to {language} word for 'fix' or 'correct'
508
+ - 'docs:' → translate to {language} word for 'documentation'
509
+ The ENTIRE commit message, including the prefix, must be in {language}."""
510
+ logger.debug(f"Set commit message language to: {language} (with prefix translation)")
511
+ else:
512
+ prefix_instruction = (
513
+ "The conventional commit prefix (feat:, fix:, etc.) should remain in English, but everything after the prefix must be in "
514
+ + language
515
+ + "."
516
+ )
517
+ logger.debug(f"Set commit message language to: {language} (English prefixes)")
518
+
519
+ user_template = user_template.replace("<prefix_instruction></prefix_instruction>", prefix_instruction)
520
+ else:
521
+ user_template = _remove_template_section(user_template, "language")
522
+ logger.debug("Using default language (English)")
523
+
487
524
  user_template = re.sub(r"\n(?:[ \t]*\n){2,}", "\n\n", user_template)
488
525
 
489
526
  return system_template.strip(), user_template.strip()
@@ -636,20 +673,18 @@ def _normalize_whitespace(message: str) -> str:
636
673
  # ============================================================================
637
674
 
638
675
 
639
- def clean_commit_message(message: str, enforce_conventional_commits: bool = True) -> str:
676
+ def clean_commit_message(message: str) -> str:
640
677
  """Clean up a commit message generated by an AI model.
641
678
 
642
679
  This function:
643
680
  1. Removes any preamble or reasoning text
644
681
  2. Removes code block markers and formatting
645
682
  3. Removes XML tags that might have leaked into the response
646
- 4. Ensures the message starts with a conventional commit prefix (if enforce_conventional_commits is True)
647
- 5. Fixes double type prefix issues (e.g., "chore: feat(scope):")
683
+ 4. Fixes double type prefix issues (e.g., "chore: feat(scope):")
684
+ 5. Normalizes whitespace
648
685
 
649
686
  Args:
650
687
  message: Raw commit message from AI
651
- enforce_conventional_commits: If True, ensures message has conventional commit prefix.
652
- Set to False when using custom system prompts.
653
688
 
654
689
  Returns:
655
690
  Cleaned commit message ready for use
@@ -660,7 +695,5 @@ def clean_commit_message(message: str, enforce_conventional_commits: bool = True
660
695
  message = _extract_commit_from_reasoning(message)
661
696
  message = _remove_xml_tags(message)
662
697
  message = _fix_double_prefix(message)
663
- if enforce_conventional_commits:
664
- message = _ensure_conventional_prefix(message)
665
698
  message = _normalize_whitespace(message)
666
699
  return message
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes