Skip to content

Commit c2057a6

Browse files
feat: add Military Resume Translator with AI-powered translation │
│ │ │ Implement comprehensive military-to-civilian resume translation tool to help veterans translate their military experience into │ │ civilian-friendly language. Features include: │ │ │ │ - AI-powered translation using @xenova/transformers │ │ - Military terminology and job title mappings │ │ - Professional summary generation │ │ - Smart suggestions for resume improvement │ │ - Download translated resume functionality │ │ - Auth-protected page with user-friendly UI │ │ - Added to navigation menu under About section
1 parent 9e257fb commit c2057a6

File tree

8 files changed

+2157
-17
lines changed

8 files changed

+2157
-17
lines changed

package-lock.json

Lines changed: 704 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@radix-ui/themes": "^1.1.0",
3636
"@types/express": "^4.17.17",
3737
"@vercel/analytics": "^1.4.1",
38+
"@xenova/transformers": "^2.17.2",
3839
"ace-builds": "^1.33.1",
3940
"ai": "^5.0.93",
4041
"axios": "^1.4.0",

src/components/translator/ResumeTranslator.tsx

Lines changed: 358 additions & 0 deletions
Large diffs are not rendered by default.

src/data/menu.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ const navigation: NavigationItem[] = [
5050
label: "Team",
5151
path: "/team",
5252
},
53+
{
54+
id: 107,
55+
label: "Military Resume Translator",
56+
path: "/resume-translator",
57+
status: "new",
58+
},
5359
],
5460
},
5561
{

src/lib/military-translator.ts

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
import { pipeline, env } from '@xenova/transformers';
2+
3+
// Disable local model loading in browser environments
4+
env.allowLocalModels = false;
5+
6+
/**
7+
* Military-to-civilian terminology mappings
8+
*/
9+
const MILITARY_TERMINOLOGY: Record<string, string> = {
10+
// Ranks & Leadership
11+
'squad leader': 'team supervisor',
12+
'platoon sergeant': 'operations manager',
13+
'first sergeant': 'senior operations manager',
14+
'sergeant major': 'executive operations manager',
15+
'commanding officer': 'chief executive',
16+
'executive officer': 'deputy director',
17+
'NCO': 'supervisor',
18+
'NCOIC': 'operations supervisor',
19+
'OIC': 'program manager',
20+
21+
// Skills & Activities
22+
'conducted': 'performed',
23+
'executed': 'completed',
24+
'deployed': 'traveled',
25+
'mission': 'objective',
26+
'operations': 'activities',
27+
'tactical': 'strategic',
28+
'reconnaissance': 'research',
29+
'surveillance': 'monitoring',
30+
'logistics': 'supply chain management',
31+
'ordnance': 'equipment',
32+
33+
// Military Branches & Units
34+
'battalion': 'large organization',
35+
'company': 'mid-size team',
36+
'platoon': 'team',
37+
'squad': 'small team',
38+
'unit': 'department',
39+
40+
// Common Military Terms
41+
'personnel': 'employees',
42+
'enlisted': 'staff members',
43+
'subordinates': 'team members',
44+
'superior': 'manager',
45+
'briefed': 'presented to',
46+
'debriefed': 'reviewed with',
47+
'orders': 'directives',
48+
'regulations': 'policies',
49+
'standard operating procedure': 'company policy',
50+
'SOP': 'policy',
51+
'ROE': 'guidelines',
52+
};
53+
54+
/**
55+
* Common military job titles to civilian equivalents
56+
*/
57+
const JOB_TITLE_MAPPINGS: Record<string, string> = {
58+
// Infantry & Combat
59+
'infantryman': 'team member',
60+
'infantry squad leader': 'operations team lead',
61+
'fire team leader': 'team supervisor',
62+
63+
// Medical
64+
'combat medic': 'emergency medical technician',
65+
'field medic': 'paramedic',
66+
'hospital corpsman': 'medical assistant',
67+
68+
// Intelligence
69+
'intelligence analyst': 'data analyst',
70+
'signals intelligence analyst': 'communications analyst',
71+
72+
// Administration
73+
'personnel specialist': 'human resources specialist',
74+
'administrative specialist': 'administrative coordinator',
75+
76+
// Technical
77+
'information technology specialist': 'IT specialist',
78+
'network administrator': 'network administrator',
79+
'communications specialist': 'telecommunications specialist',
80+
81+
// Logistics
82+
'supply specialist': 'inventory manager',
83+
'logistics specialist': 'supply chain coordinator',
84+
'quartermaster': 'logistics manager',
85+
86+
// Vehicle & Equipment
87+
'motor transport operator': 'truck driver',
88+
'aircraft mechanic': 'aviation technician',
89+
'wheeled vehicle mechanic': 'automotive technician',
90+
};
91+
92+
export interface TranslationResult {
93+
original: string;
94+
translated: string;
95+
suggestions: string[];
96+
confidence: number;
97+
}
98+
99+
export interface MilitaryProfile {
100+
jobTitle?: string;
101+
rank?: string;
102+
branch?: string;
103+
duties?: string;
104+
achievements?: string;
105+
}
106+
107+
export interface TranslatedProfile {
108+
jobTitle: string;
109+
summary: string;
110+
keyResponsibilities: string[];
111+
achievements: string[];
112+
}
113+
114+
/**
115+
* Initialize the transformer pipeline for text generation
116+
*/
117+
let translatorPipeline: any = null;
118+
119+
async function getTranslatorPipeline() {
120+
if (!translatorPipeline) {
121+
try {
122+
// Use a smaller, faster model for paraphrasing/translation
123+
translatorPipeline = await pipeline(
124+
'text2text-generation',
125+
'Xenova/LaMini-Flan-T5-783M'
126+
);
127+
} catch (error) {
128+
console.error('Failed to load translator pipeline:', error);
129+
throw new Error('Failed to initialize military translator');
130+
}
131+
}
132+
return translatorPipeline;
133+
}
134+
135+
/**
136+
* Replace military terminology with civilian equivalents
137+
*/
138+
function replaceTerminology(text: string): string {
139+
let result = text;
140+
141+
// Sort by length (longest first) to avoid partial replacements
142+
const sortedTerms = Object.entries(MILITARY_TERMINOLOGY).sort(
143+
([a], [b]) => b.length - a.length
144+
);
145+
146+
for (const [military, civilian] of sortedTerms) {
147+
// Case-insensitive replacement
148+
const regex = new RegExp(`\\b${military}\\b`, 'gi');
149+
result = result.replace(regex, civilian);
150+
}
151+
152+
return result;
153+
}
154+
155+
/**
156+
* Translate military job title to civilian equivalent
157+
*/
158+
export function translateJobTitle(militaryTitle: string): string {
159+
const normalized = militaryTitle.toLowerCase().trim();
160+
161+
// Check for exact match
162+
if (JOB_TITLE_MAPPINGS[normalized]) {
163+
return JOB_TITLE_MAPPINGS[normalized];
164+
}
165+
166+
// Check for partial match
167+
for (const [military, civilian] of Object.entries(JOB_TITLE_MAPPINGS)) {
168+
if (normalized.includes(military)) {
169+
return civilian;
170+
}
171+
}
172+
173+
// Fallback: apply terminology replacement
174+
return replaceTerminology(militaryTitle);
175+
}
176+
177+
/**
178+
* Translate a single military duty/responsibility to civilian language
179+
*/
180+
export async function translateDuty(duty: string): Promise<TranslationResult> {
181+
try {
182+
// First, apply terminology replacement
183+
const preliminaryTranslation = replaceTerminology(duty);
184+
185+
// Generate alternative phrasings using the AI model
186+
const pipeline = await getTranslatorPipeline();
187+
188+
const prompt = `Rewrite this military duty in civilian professional language: "${preliminaryTranslation}"`;
189+
190+
const result = await pipeline(prompt, {
191+
max_length: 100,
192+
num_return_sequences: 3,
193+
temperature: 0.7,
194+
});
195+
196+
const suggestions = result.map((r: any) => r.generated_text);
197+
198+
return {
199+
original: duty,
200+
translated: preliminaryTranslation,
201+
suggestions: suggestions,
202+
confidence: 0.85,
203+
};
204+
} catch (error) {
205+
console.error('Translation error:', error);
206+
207+
// Fallback to terminology replacement only
208+
return {
209+
original: duty,
210+
translated: replaceTerminology(duty),
211+
suggestions: [],
212+
confidence: 0.6,
213+
};
214+
}
215+
}
216+
217+
/**
218+
* Translate entire military profile to civilian resume format
219+
*/
220+
export async function translateMilitaryProfile(
221+
profile: MilitaryProfile
222+
): Promise<TranslatedProfile> {
223+
try {
224+
// Translate job title
225+
const civilianTitle = profile.jobTitle
226+
? translateJobTitle(profile.jobTitle)
227+
: 'Professional';
228+
229+
// Create professional summary
230+
const summaryParts: string[] = [];
231+
if (profile.rank) {
232+
summaryParts.push(`Experienced professional with ${profile.rank} level responsibilities`);
233+
}
234+
if (profile.branch) {
235+
summaryParts.push(`in ${replaceTerminology(profile.branch)}`);
236+
}
237+
238+
const summary = summaryParts.length > 0
239+
? summaryParts.join(' ')
240+
: 'Dedicated professional with proven leadership and operational experience';
241+
242+
// Translate duties/responsibilities
243+
const duties = profile.duties
244+
? profile.duties.split('\n').filter((d) => d.trim())
245+
: [];
246+
247+
const translatedDuties = duties.map((duty) => replaceTerminology(duty));
248+
249+
// Translate achievements
250+
const achievements = profile.achievements
251+
? profile.achievements.split('\n').filter((a) => a.trim())
252+
: [];
253+
254+
const translatedAchievements = achievements.map((achievement) =>
255+
replaceTerminology(achievement)
256+
);
257+
258+
return {
259+
jobTitle: civilianTitle,
260+
summary,
261+
keyResponsibilities: translatedDuties,
262+
achievements: translatedAchievements,
263+
};
264+
} catch (error) {
265+
console.error('Profile translation error:', error);
266+
throw new Error('Failed to translate military profile');
267+
}
268+
}
269+
270+
/**
271+
* Batch translate multiple duties
272+
*/
273+
export async function translateDuties(duties: string[]): Promise<TranslationResult[]> {
274+
const results: TranslationResult[] = [];
275+
276+
for (const duty of duties) {
277+
if (duty.trim()) {
278+
const result = await translateDuty(duty);
279+
results.push(result);
280+
}
281+
}
282+
283+
return results;
284+
}
285+
286+
/**
287+
* Get suggestions for improving a translated duty
288+
*/
289+
export function getSuggestions(translatedDuty: string): string[] {
290+
const suggestions: string[] = [];
291+
292+
// Suggest adding metrics
293+
if (!/\d+/.test(translatedDuty)) {
294+
suggestions.push('Consider adding specific numbers or metrics to quantify your impact');
295+
}
296+
297+
// Suggest using action verbs
298+
const actionVerbs = ['led', 'managed', 'developed', 'implemented', 'coordinated'];
299+
const startsWithActionVerb = actionVerbs.some((verb) =>
300+
translatedDuty.toLowerCase().startsWith(verb)
301+
);
302+
303+
if (!startsWithActionVerb) {
304+
suggestions.push('Start with a strong action verb (e.g., Led, Managed, Developed)');
305+
}
306+
307+
// Suggest adding outcomes
308+
if (!translatedDuty.includes('result') && !translatedDuty.includes('improve')) {
309+
suggestions.push('Include the result or outcome of your work');
310+
}
311+
312+
return suggestions;
313+
}
314+
315+
/**
316+
* Format translated profile for download/export
317+
*/
318+
export function formatForResume(profile: TranslatedProfile): string {
319+
let resume = '';
320+
321+
resume += `JOB TITLE: ${profile.jobTitle}\n\n`;
322+
resume += `PROFESSIONAL SUMMARY:\n${profile.summary}\n\n`;
323+
324+
if (profile.keyResponsibilities.length > 0) {
325+
resume += `KEY RESPONSIBILITIES:\n`;
326+
profile.keyResponsibilities.forEach((resp) => {
327+
resume += `• ${resp}\n`;
328+
});
329+
resume += '\n';
330+
}
331+
332+
if (profile.achievements.length > 0) {
333+
resume += `ACHIEVEMENTS:\n`;
334+
profile.achievements.forEach((achievement) => {
335+
resume += `• ${achievement}\n`;
336+
});
337+
}
338+
339+
return resume;
340+
}

0 commit comments

Comments
 (0)