-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(i18n): add Portuguese Brazilian (pt-BR) localization #1957
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
8d4e637
83c762f
b104447
93c3700
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -243,7 +243,14 @@ export function injectContext(promptTemplate: string, context: PromptContext): s | |
| ); | ||
| } | ||
|
|
||
| // 5. Base prompt | ||
| // 5. Language instruction | ||
| if (context.language && context.language !== 'en') { | ||
| sections.push( | ||
| `## OUTPUT LANGUAGE\n\nYou MUST write ALL human-readable text content in **${context.language}**. This includes: titles, descriptions, rationale, acceptance criteria, user stories, phase names, status messages, and any text shown to the user. Keep JSON keys, code, file paths, technical identifiers, and enum values in English.\n\n---\n\n` | ||
| ); | ||
|
Comment on lines
+248
to
+250
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For better readability and maintainability, you could use a single multi-line template literal here instead of concatenating multiple strings with sections.push(`## OUTPUT LANGUAGE
You MUST write ALL human-readable text content in **${context.language}**. This includes: titles, descriptions, rationale, acceptance criteria, user stories, phase names, status messages, and any text shown to the user. Keep JSON keys, code, file paths, technical identifiers, and enum values in English.
---
`); |
||
| } | ||
|
|
||
| // 6. Base prompt | ||
| sections.push(promptTemplate); | ||
|
|
||
| return sections.join(''); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,9 +10,11 @@ | |
| */ | ||
|
|
||
| import { streamText, stepCountIs } from 'ai'; | ||
| import { randomUUID } from 'node:crypto'; | ||
| import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs'; | ||
| import { join } from 'node:path'; | ||
|
|
||
| import { withFileLock } from '../../utils/file-lock'; | ||
| import { createSimpleClient } from '../client/factory'; | ||
| import type { SimpleClientResult } from '../client/types'; | ||
| import { buildToolRegistry } from '../tools/build-registry'; | ||
|
|
@@ -51,6 +53,8 @@ export interface RoadmapConfig { | |
| enableCompetitorAnalysis?: boolean; | ||
| /** Abort signal for cancellation */ | ||
| abortSignal?: AbortSignal; | ||
| /** Language code for generated content (e.g., 'en', 'pt-BR') */ | ||
| language?: string; | ||
| } | ||
|
|
||
| /** Result of a roadmap phase */ | ||
|
|
@@ -103,6 +107,7 @@ async function runDiscoveryPhase( | |
| client: SimpleClientResult, | ||
| abortSignal?: AbortSignal, | ||
| onStream?: RoadmapStreamCallback, | ||
| language?: string, | ||
| ): Promise<RoadmapPhaseResult> { | ||
| const discoveryFile = join(outputDir, 'roadmap_discovery.json'); | ||
| const projectIndexFile = join(outputDir, 'project_index.json'); | ||
|
|
@@ -121,7 +126,10 @@ async function runDiscoveryPhase( | |
| const loadedDiscoveryPrompt = tryLoadPrompt('roadmap_discovery'); | ||
|
|
||
| for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { | ||
| const contextBlock = `\n\n---\n\n## CONTEXT (injected by runner)\n\n**Project Directory**: ${projectDir}\n**Project Index**: ${projectIndexFile}\n**Output Directory**: ${outputDir}\n**Output File**: ${discoveryFile}\n\nUse the paths above when reading input files and writing output.`; | ||
| const languageInstruction = language && language !== 'en' | ||
| ? `\n\n**LANGUAGE**: You MUST write ALL human-readable text content in ${language}. This includes: project descriptions, pain points, goals, vision statements, value propositions, success metrics, feature names, known gaps, differentiators, market position, and constraints. Keep JSON keys, technical terms (framework names, language names), and file paths in English.` | ||
| : ''; | ||
| const contextBlock = `\n\n---\n\n## CONTEXT (injected by runner)\n\n**Project Directory**: ${projectDir}\n**Project Index**: ${projectIndexFile}\n**Output Directory**: ${outputDir}\n**Output File**: ${discoveryFile}\n\nUse the paths above when reading input files and writing output.${languageInstruction}`; | ||
|
Comment on lines
+129
to
+132
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider extracting duplicated language instruction string. The language instruction text is nearly identical between discovery and features phases. While the enum value lists differ slightly, the core pattern is duplicated. Consider extracting to a helper function for maintainability. ♻️ Suggested refactorfunction buildLanguageInstruction(language: string | undefined, enumExamples: string): string {
if (!language || language === 'en') return '';
return `\n\n**LANGUAGE**: You MUST write ALL human-readable text content in ${language}. This includes: ${enumExamples}. Keep JSON keys, technical terms (framework names, language names), file paths, and enum values in English.`;
}
// Usage in discovery:
const languageInstruction = buildLanguageInstruction(
language,
'project descriptions, pain points, goals, vision statements, value propositions, success metrics, feature names, known gaps, differentiators, market position, and constraints'
);
// Usage in features:
const featuresLanguageInstruction = buildLanguageInstruction(
language,
'feature titles, descriptions, rationales, acceptance criteria, user stories, phase names, phase descriptions, milestone titles, milestone descriptions, vision statement, and audience descriptions'
);Also applies to: 265-268 🤖 Prompt for AI Agents |
||
|
|
||
| const prompt = loadedDiscoveryPrompt | ||
| ? loadedDiscoveryPrompt + contextBlock | ||
|
|
@@ -140,7 +148,7 @@ Your task: | |
|
|
||
| The JSON must contain at minimum: project_name, target_audience, product_vision, key_features, technical_stack, and constraints. | ||
|
|
||
| Do NOT ask questions. Make educated inferences and create the file.`; | ||
| Do NOT ask questions. Make educated inferences and create the file.${languageInstruction}`; | ||
|
|
||
| const discoveryUserPrompt = 'Analyze the project and create the discovery document. Use the available tools to explore the codebase, then write your findings as JSON to the output file specified in the context above.'; | ||
|
|
||
|
|
@@ -217,6 +225,7 @@ async function runFeaturesPhase( | |
| client: SimpleClientResult, | ||
| abortSignal?: AbortSignal, | ||
| onStream?: RoadmapStreamCallback, | ||
| language?: string, | ||
| ): Promise<RoadmapPhaseResult> { | ||
| const roadmapFile = join(outputDir, 'roadmap.json'); | ||
| const discoveryFile = join(outputDir, 'roadmap_discovery.json'); | ||
|
|
@@ -253,7 +262,10 @@ The following ${preservedFeatures.length} features already exist and will be pre | |
| Generate NEW features that complement these, do not duplicate them: | ||
| ${preservedInfo}\n`; | ||
| } | ||
| const featuresContextBlock = `\n\n---\n\n## CONTEXT (injected by runner)\n\n**Discovery File**: ${discoveryFile}\n**Project Index**: ${projectIndexFile}\n**Output File**: ${roadmapFile}\n${preservedSection}\nUse the paths above when reading input files and writing output. Write the complete roadmap JSON to the Output File path.`; | ||
| const featuresLanguageInstruction = language && language !== 'en' | ||
| ? `\n\n**LANGUAGE**: You MUST write ALL human-readable text content in ${language}. This includes: feature titles, descriptions, rationales, acceptance criteria, user stories, phase names, phase descriptions, milestone titles, milestone descriptions, vision statement, and audience descriptions. Keep JSON keys, technical terms, file paths, and enum values (must/should/could/wont, low/medium/high, idea/planned) in English.` | ||
| : ''; | ||
| const featuresContextBlock = `\n\n---\n\n## CONTEXT (injected by runner)\n\n**Discovery File**: ${discoveryFile}\n**Project Index**: ${projectIndexFile}\n**Output File**: ${roadmapFile}\n${preservedSection}\nUse the paths above when reading input files and writing output. Write the complete roadmap JSON to the Output File path.${featuresLanguageInstruction}`; | ||
|
|
||
| const prompt = loadedFeaturesPrompt | ||
| ? loadedFeaturesPrompt + featuresContextBlock | ||
|
|
@@ -272,7 +284,7 @@ Based on the discovery data: | |
| 6. Map dependencies | ||
|
|
||
| Output the complete roadmap as valid JSON to ${roadmapFile}. | ||
| The JSON must contain: vision, target_audience (object with "primary" key), phases (array), and features (array with at least 3 items each with id, title, description, priority, complexity, impact, phase_id, status, acceptance_criteria, and user_stories).`; | ||
| The JSON must contain: vision, target_audience (object with "primary" key), phases (array), and features (array with at least 3 items each with id, title, description, priority, complexity, impact, phase_id, status, acceptance_criteria, and user_stories).${featuresLanguageInstruction}`; | ||
|
|
||
| const featuresUserPrompt = 'Read the discovery data and generate a complete roadmap with prioritized features. Write the roadmap JSON to the output file specified in the context above.'; | ||
|
|
||
|
|
@@ -330,13 +342,15 @@ The JSON must contain: vision, target_audience (object with "primary" key), phas | |
| } | ||
|
|
||
| if (missing.length === 0 && featureCount >= 3) { | ||
| // Merge preserved features — atomic write via temp file + rename | ||
| // Merge preserved features — atomic write with file lock to prevent races | ||
| if (preservedFeatures.length > 0) { | ||
| data.features = mergeFeatures(data.features as Record<string, unknown>[], preservedFeatures); | ||
| const merged = JSON.stringify(data, null, 2); | ||
| const tmpFile = `${roadmapFile}.tmp.${process.pid}`; | ||
| writeFileSync(tmpFile, merged, 'utf-8'); | ||
| renameSync(tmpFile, roadmapFile); | ||
| await withFileLock(roadmapFile, async () => { | ||
| const tmpFile = `${roadmapFile}.tmp.${process.pid}.${randomUUID()}`; | ||
| writeFileSync(tmpFile, merged, 'utf-8'); | ||
| renameSync(tmpFile, roadmapFile); | ||
| }); | ||
| } | ||
| return { phase: 'features', success: true, outputs: [roadmapFile], errors: [] }; | ||
| } | ||
|
|
@@ -441,6 +455,7 @@ export async function runRoadmapGeneration( | |
| thinkingLevel = 'medium', | ||
| refresh = false, | ||
| abortSignal, | ||
| language = 'en', | ||
| } = config; | ||
|
|
||
| const outputDir = config.outputDir ?? join(projectDir, '.auto-claude', 'roadmap'); | ||
|
|
@@ -475,7 +490,7 @@ export async function runRoadmapGeneration( | |
| // Phase 1: Discovery | ||
| onStream?.({ type: 'phase-start', phase: 'discovery' }); | ||
| const discoveryResult = await runDiscoveryPhase( | ||
| projectDir, outputDir, refresh, client, abortSignal, onStream, | ||
| projectDir, outputDir, refresh, client, abortSignal, onStream, language, | ||
| ); | ||
| phases.push(discoveryResult); | ||
| onStream?.({ type: 'phase-complete', phase: 'discovery', success: discoveryResult.success }); | ||
|
|
@@ -491,7 +506,7 @@ export async function runRoadmapGeneration( | |
| // Phase 2: Feature Generation | ||
| onStream?.({ type: 'phase-start', phase: 'features' }); | ||
| const featuresResult = await runFeaturesPhase( | ||
| projectDir, outputDir, refresh, client, abortSignal, onStream, | ||
| projectDir, outputDir, refresh, client, abortSignal, onStream, language, | ||
| ); | ||
| phases.push(featuresResult); | ||
| onStream?.({ type: 'phase-complete', phase: 'features', success: featuresResult.success }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -88,7 +88,7 @@ export function SortableFeatureCard({ | |||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||
| className={cn('text-[10px] px-1.5 py-0', ROADMAP_PRIORITY_COLORS[feature.priority])} | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| {ROADMAP_PRIORITY_LABELS[feature.priority]} | ||||||||||||||||||||||||||||
| {t(ROADMAP_PRIORITY_LABELS[feature.priority])} | ||||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||
| {phaseName && ( | ||||||||||||||||||||||||||||
| <Tooltip> | ||||||||||||||||||||||||||||
|
|
@@ -102,7 +102,7 @@ export function SortableFeatureCard({ | |||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||
| </TooltipTrigger> | ||||||||||||||||||||||||||||
| <TooltipContent> | ||||||||||||||||||||||||||||
| Phase: {phaseName} | ||||||||||||||||||||||||||||
| {t('roadmap.phaseLabel', { phase: phaseName })} | ||||||||||||||||||||||||||||
| </TooltipContent> | ||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||
|
|
@@ -117,7 +117,7 @@ export function SortableFeatureCard({ | |||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||
| </TooltipTrigger> | ||||||||||||||||||||||||||||
| <TooltipContent> | ||||||||||||||||||||||||||||
| This feature addresses competitor pain points | ||||||||||||||||||||||||||||
| {t('roadmap.competitorInsight.tooltip')} | ||||||||||||||||||||||||||||
| </TooltipContent> | ||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||
|
|
@@ -191,13 +191,13 @@ export function SortableFeatureCard({ | |||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||
| className={cn('text-[10px] px-1.5 py-0', ROADMAP_COMPLEXITY_COLORS[feature.complexity])} | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| {feature.complexity} | ||||||||||||||||||||||||||||
| {t(`roadmap.complexity.${feature.complexity}`)} | ||||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||
| <Badge | ||||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||
| className={cn('text-[10px] px-1.5 py-0', ROADMAP_IMPACT_COLORS[feature.impact])} | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| {feature.impact} | ||||||||||||||||||||||||||||
| {t(`roadmap.impact.${feature.impact}`)} | ||||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||
|
Comment on lines
196
to
201
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Impact badge lost context and can be confused with complexity. Rendering only 🎯 Suggested patch- {t(`roadmap.impact.${feature.impact}`)}
+ {t('roadmap.impact.label', { impact: t(`roadmap.impact.${feature.impact}`) })}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| {/* Show vote count if from external source */} | ||||||||||||||||||||||||||||
| {feature.votes !== undefined && feature.votes > 0 && ( | ||||||||||||||||||||||||||||
|
|
@@ -212,7 +212,7 @@ export function SortableFeatureCard({ | |||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||
| </TooltipTrigger> | ||||||||||||||||||||||||||||
| <TooltipContent> | ||||||||||||||||||||||||||||
| {feature.votes} votes from user feedback | ||||||||||||||||||||||||||||
| {t('roadmap.votesFromFeedback', { count: feature.votes })} | ||||||||||||||||||||||||||||
| </TooltipContent> | ||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||
|
|
@@ -224,11 +224,11 @@ export function SortableFeatureCard({ | |||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||
| className="text-[10px] px-1.5 py-0 text-orange-500 border-orange-500/30" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| {feature.source?.provider === 'canny' ? 'Canny' : 'External'} | ||||||||||||||||||||||||||||
| {feature.source?.provider === 'canny' ? 'Canny' : t('roadmap.externalSource')} | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Hardcoded brand name 'Canny' bypasses localization. While 'Canny' is a proper noun (company name), the conditional logic shows inconsistent treatment—other external sources use ♻️ Suggested fix- {feature.source?.provider === 'canny' ? 'Canny' : t('roadmap.externalSource')}
+ {t(`roadmap.providers.${feature.source?.provider}`, { defaultValue: t('roadmap.externalSource') })}Then add to locale files: "roadmap": {
"providers": {
"canny": "Canny"
}
}As per coding guidelines: "All frontend user-facing text must use 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||
| </TooltipTrigger> | ||||||||||||||||||||||||||||
| <TooltipContent> | ||||||||||||||||||||||||||||
| Imported from {feature.source?.provider} | ||||||||||||||||||||||||||||
| {t('roadmap.importedFrom', { provider: feature.source?.provider })} | ||||||||||||||||||||||||||||
| </TooltipContent> | ||||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
This comment was marked as outdated.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.