feat: add complete chrono-node language support (12 languages)

Implement comprehensive NLP support for all chrono-node supported languages:

## 🌍 Complete Language Coverage
- **5 new languages**: Italian (it), Dutch (nl), Portuguese (pt), Swedish (sv), Ukrainian (uk)
- **12 total languages**: EN, ES, FR, DE, RU, ZH, JA, IT, NL, PT, SV, UK
- All languages match complete chrono-node locale support

## 📝 Language Configurations
- Researched accurate native translations for all patterns
- Cultural appropriateness for status and priority terms
- Grammar considerations (gender, plurals, cases where applicable)
- Comprehensive coverage of date triggers, recurrence, time estimates

## 🔍 Enhanced Pattern Matching
- Added Ukrainian (Cyrillic) to non-ASCII boundary handling
- Proper handling for all script types (Latin, Cyrillic, Chinese, Japanese)
- Multiple variations and synonyms for better recognition

##  Comprehensive Testing
- **51 multi-language tests** covering all 12 languages
- **19 language configuration tests** validating structure and registry
- All tests passing with proper pattern validation
- Fixed test cases to match actual language configurations

## 🏗️ Technical Improvements
- Updated language registry with all new imports
- Enhanced TypeScript interfaces and type safety
- Maintained backward compatibility
- Zero compilation errors

This completes full international NLP support matching all available
chrono-node locales for comprehensive global usability.
This commit is contained in:
callumalpass 2025-09-08 00:28:19 +10:00
parent ef1086778b
commit 5efce0d36c
9 changed files with 633 additions and 8 deletions

View file

@ -6,6 +6,11 @@ import { deConfig } from './de';
import { ruConfig } from './ru';
import { zhConfig } from './zh';
import { jaConfig } from './ja';
import { itConfig } from './it';
import { nlConfig } from './nl';
import { ptConfig } from './pt';
import { svConfig } from './sv';
import { ukConfig } from './uk';
/**
* Registry of all available language configurations
@ -17,7 +22,12 @@ export const languageRegistry: LanguageRegistry = {
de: deConfig,
ru: ruConfig,
zh: zhConfig,
ja: jaConfig
ja: jaConfig,
it: itConfig,
nl: nlConfig,
pt: ptConfig,
sv: svConfig,
uk: ukConfig
};
/**

83
src/locales/it.ts Normal file
View file

@ -0,0 +1,83 @@
import { NLPLanguageConfig } from './types';
/**
* Italian language configuration for Natural Language Processing
* Translated patterns for Italian-speaking users
*/
export const itConfig: NLPLanguageConfig = {
code: 'it',
name: 'Italiano',
chronoLocale: 'it',
dateTriggers: {
due: ['scadenza', 'entro', 'entro il', 'deve essere fatto entro', 'per il', 'termine'],
scheduled: ['programmato per', 'programmato il', 'iniziare il', 'lavorare su', 'il', 'per']
},
recurrence: {
frequencies: {
daily: ['giornaliero', 'giornaliera', 'quotidiano', 'quotidiana', 'ogni giorno', 'tutti i giorni', 'giornalmente'],
weekly: ['settimanale', 'ogni settimana', 'tutte le settimane', 'settimanalmente', 'alla settimana'],
monthly: ['mensile', 'ogni mese', 'tutti i mesi', 'mensilmente', 'al mese'],
yearly: ['annuale', 'ogni anno', 'tutti gli anni', 'annualmente', 'all\'anno']
},
every: ['ogni', 'tutti i', 'tutte le'],
other: ['altro', 'altra', 'altri', 'altre'],
weekdays: {
monday: ['lunedì'],
tuesday: ['martedì'],
wednesday: ['mercoledì'],
thursday: ['giovedì'],
friday: ['venerdì'],
saturday: ['sabato'],
sunday: ['domenica']
},
pluralWeekdays: {
monday: ['lunedì'],
tuesday: ['martedì'],
wednesday: ['mercoledì'],
thursday: ['giovedì'],
friday: ['venerdì'],
saturday: ['sabati'],
sunday: ['domeniche']
},
ordinals: {
first: ['primo', 'prima'],
second: ['secondo', 'seconda'],
third: ['terzo', 'terza'],
fourth: ['quarto', 'quarta'],
last: ['ultimo', 'ultima']
},
periods: {
day: ['giorno', 'giorni'],
week: ['settimana', 'settimane'],
month: ['mese', 'mesi'],
year: ['anno', 'anni']
}
},
timeEstimate: {
hours: ['h', 'hr', 'ore', 'ora', 'o'],
minutes: ['m', 'min', 'minuto', 'minuti']
},
fallbackStatus: {
open: ['da fare', 'aperto', 'pendente', 'todo', 'in sospeso'],
inProgress: ['in corso', 'in progresso', 'facendo', 'lavorando'],
done: ['fatto', 'completato', 'finito', 'terminato', 'chiuso'],
cancelled: ['cancellato', 'annullato', 'rimosso'],
waiting: ['in attesa', 'aspettando', 'bloccato', 'fermo']
},
fallbackPriority: {
urgent: ['urgente', 'critico', 'critica', 'massimo', 'massima', 'prioritario', 'prioritaria'],
high: ['alto', 'alta', 'importante', 'elevato', 'elevata'],
normal: ['medio', 'media', 'normale', 'regolare', 'standard'],
low: ['basso', 'bassa', 'minore', 'minimo', 'minima']
}
};

83
src/locales/nl.ts Normal file
View file

@ -0,0 +1,83 @@
import { NLPLanguageConfig } from './types';
/**
* Dutch language configuration for Natural Language Processing
* Translated patterns for Dutch-speaking users
*/
export const nlConfig: NLPLanguageConfig = {
code: 'nl',
name: 'Nederlands',
chronoLocale: 'nl',
dateTriggers: {
due: ['vervalt op', 'deadline', 'moet klaar zijn op', 'tegen', 'uiterlijk', 'voor'],
scheduled: ['gepland voor', 'gepland op', 'beginnen op', 'werken aan', 'op', 'voor']
},
recurrence: {
frequencies: {
daily: ['dagelijks', 'elke dag', 'alle dagen', 'per dag'],
weekly: ['wekelijks', 'elke week', 'alle weken', 'per week'],
monthly: ['maandelijks', 'elke maand', 'alle maanden', 'per maand'],
yearly: ['jaarlijks', 'elk jaar', 'alle jaren', 'per jaar']
},
every: ['elke', 'alle', 'iedere'],
other: ['andere', 'ander'],
weekdays: {
monday: ['maandag'],
tuesday: ['dinsdag'],
wednesday: ['woensdag'],
thursday: ['donderdag'],
friday: ['vrijdag'],
saturday: ['zaterdag'],
sunday: ['zondag']
},
pluralWeekdays: {
monday: ['maandagen'],
tuesday: ['dinsdagen'],
wednesday: ['woensdagen'],
thursday: ['donderdagen'],
friday: ['vrijdagen'],
saturday: ['zaterdagen'],
sunday: ['zondagen']
},
ordinals: {
first: ['eerste'],
second: ['tweede'],
third: ['derde'],
fourth: ['vierde'],
last: ['laatste']
},
periods: {
day: ['dag', 'dagen'],
week: ['week', 'weken'],
month: ['maand', 'maanden'],
year: ['jaar', 'jaren']
}
},
timeEstimate: {
hours: ['u', 'uur', 'uren', 'h'],
minutes: ['m', 'min', 'minuut', 'minuten']
},
fallbackStatus: {
open: ['te doen', 'open', 'nog te doen', 'todo', 'openstaand'],
inProgress: ['bezig', 'in behandeling', 'aan het werk', 'lopend', 'in uitvoering'],
done: ['klaar', 'voltooid', 'gedaan', 'afgerond', 'gesloten'],
cancelled: ['geannuleerd', 'afgezegd', 'ingetrokken'],
waiting: ['wachtend', 'in de wacht', 'geblokkeerd', 'uitgesteld']
},
fallbackPriority: {
urgent: ['urgent', 'kritiek', 'hoogste', 'spoed', 'direct'],
high: ['hoog', 'hoge', 'belangrijk', 'belangrijke'],
normal: ['normaal', 'normale', 'gemiddeld', 'standaard'],
low: ['laag', 'lage', 'klein', 'kleine', 'onbelangrijk']
}
};

83
src/locales/pt.ts Normal file
View file

@ -0,0 +1,83 @@
import { NLPLanguageConfig } from './types';
/**
* Portuguese language configuration for Natural Language Processing
* Translated patterns for Portuguese-speaking users
*/
export const ptConfig: NLPLanguageConfig = {
code: 'pt',
name: 'Português',
chronoLocale: 'pt',
dateTriggers: {
due: ['vencimento', 'prazo', 'deve estar pronto até', 'até', 'para', 'limite'],
scheduled: ['programado para', 'agendado para', 'começar em', 'trabalhar em', 'em', 'no']
},
recurrence: {
frequencies: {
daily: ['diário', 'diária', 'diariamente', 'todos os dias', 'cada dia', 'por dia'],
weekly: ['semanal', 'semanalmente', 'toda semana', 'todas as semanas', 'por semana'],
monthly: ['mensal', 'mensalmente', 'todo mês', 'todos os meses', 'por mês'],
yearly: ['anual', 'anualmente', 'todo ano', 'todos os anos', 'por ano']
},
every: ['todo', 'toda', 'todos', 'todas', 'cada'],
other: ['outro', 'outra', 'outros', 'outras'],
weekdays: {
monday: ['segunda', 'segunda-feira'],
tuesday: ['terça', 'terça-feira'],
wednesday: ['quarta', 'quarta-feira'],
thursday: ['quinta', 'quinta-feira'],
friday: ['sexta', 'sexta-feira'],
saturday: ['sábado'],
sunday: ['domingo']
},
pluralWeekdays: {
monday: ['segundas', 'segundas-feiras'],
tuesday: ['terças', 'terças-feiras'],
wednesday: ['quartas', 'quartas-feiras'],
thursday: ['quintas', 'quintas-feiras'],
friday: ['sextas', 'sextas-feiras'],
saturday: ['sábados'],
sunday: ['domingos']
},
ordinals: {
first: ['primeiro', 'primeira'],
second: ['segundo', 'segunda'],
third: ['terceiro', 'terceira'],
fourth: ['quarto', 'quarta'],
last: ['último', 'última']
},
periods: {
day: ['dia', 'dias'],
week: ['semana', 'semanas'],
month: ['mês', 'meses'],
year: ['ano', 'anos']
}
},
timeEstimate: {
hours: ['h', 'hr', 'hora', 'horas'],
minutes: ['m', 'min', 'minuto', 'minutos']
},
fallbackStatus: {
open: ['a fazer', 'pendente', 'aberto', 'todo', 'por fazer'],
inProgress: ['em andamento', 'em progresso', 'fazendo', 'trabalhando', 'executando'],
done: ['feito', 'concluído', 'terminado', 'finalizado', 'completo'],
cancelled: ['cancelado', 'anulado', 'suspenso'],
waiting: ['aguardando', 'esperando', 'bloqueado', 'em espera']
},
fallbackPriority: {
urgent: ['urgente', 'crítico', 'crítica', 'máximo', 'máxima', 'prioritário', 'prioritária'],
high: ['alto', 'alta', 'importante', 'elevado', 'elevada'],
normal: ['médio', 'média', 'normal', 'regular', 'padrão'],
low: ['baixo', 'baixa', 'menor', 'mínimo', 'mínima']
}
};

83
src/locales/sv.ts Normal file
View file

@ -0,0 +1,83 @@
import { NLPLanguageConfig } from './types';
/**
* Swedish language configuration for Natural Language Processing
* Translated patterns for Swedish-speaking users
*/
export const svConfig: NLPLanguageConfig = {
code: 'sv',
name: 'Svenska',
chronoLocale: 'sv',
dateTriggers: {
due: ['förfaller', 'deadline', 'måste vara klar', 'senast', 'till', 'innan'],
scheduled: ['schemalagd', 'planerad för', 'börja', 'arbeta med', 'den', 'på']
},
recurrence: {
frequencies: {
daily: ['dagligen', 'varje dag', 'alla dagar', 'per dag'],
weekly: ['veckovis', 'varje vecka', 'alla veckor', 'per vecka'],
monthly: ['månadsvis', 'varje månad', 'alla månader', 'per månad'],
yearly: ['årligen', 'varje år', 'alla år', 'per år']
},
every: ['varje', 'alla', 'var'],
other: ['annan', 'annat', 'andra'],
weekdays: {
monday: ['måndag'],
tuesday: ['tisdag'],
wednesday: ['onsdag'],
thursday: ['torsdag'],
friday: ['fredag'],
saturday: ['lördag'],
sunday: ['söndag']
},
pluralWeekdays: {
monday: ['måndagar'],
tuesday: ['tisdagar'],
wednesday: ['onsdagar'],
thursday: ['torsdagar'],
friday: ['fredagar'],
saturday: ['lördagar'],
sunday: ['söndagar']
},
ordinals: {
first: ['första'],
second: ['andra'],
third: ['tredje'],
fourth: ['fjärde'],
last: ['sista']
},
periods: {
day: ['dag', 'dagar'],
week: ['vecka', 'veckor'],
month: ['månad', 'månader'],
year: ['år']
}
},
timeEstimate: {
hours: ['t', 'tim', 'timme', 'timmar', 'h'],
minutes: ['m', 'min', 'minut', 'minuter']
},
fallbackStatus: {
open: ['att göra', 'öppen', 'kvar', 'todo', 'väntande'],
inProgress: ['pågående', 'arbetar', 'gör', 'i process', 'under arbete'],
done: ['klar', 'färdig', 'slutförd', 'avslutad', 'gjord'],
cancelled: ['avbruten', 'inställd', 'avbokad'],
waiting: ['väntar', 'blockerad', 'pausad', 'vilande']
},
fallbackPriority: {
urgent: ['brådskande', 'kritisk', 'högsta', 'akut', 'omedelbar'],
high: ['hög', 'viktig', 'förhöjd', 'prioriterad'],
normal: ['normal', 'medel', 'standard', 'vanlig'],
low: ['låg', 'mindre', 'minimal', 'obetydlig']
}
};

83
src/locales/uk.ts Normal file
View file

@ -0,0 +1,83 @@
import { NLPLanguageConfig } from './types';
/**
* Ukrainian language configuration for Natural Language Processing
* Translated patterns for Ukrainian-speaking users
*/
export const ukConfig: NLPLanguageConfig = {
code: 'uk',
name: 'Українська',
chronoLocale: 'uk',
dateTriggers: {
due: ['термін', 'дедлайн', 'має бути готово до', 'до', 'не пізніше', 'крайній термін'],
scheduled: ['заплановано на', 'запланований', 'почати', 'працювати над', 'на', 'в']
},
recurrence: {
frequencies: {
daily: ['щодня', 'щоденно', 'кожен день', 'всі дні', 'на день'],
weekly: ['щотижня', 'щотижнево', 'кожен тиждень', 'всі тижні', 'на тиждень'],
monthly: ['щомісяця', 'щомісячно', 'кожен місяць', 'всі місяці', 'на місяць'],
yearly: ['щороку', 'щорічно', 'кожен рік', 'всі роки', 'на рік']
},
every: ['кожен', 'кожна', 'кожне', 'всі'],
other: ['інший', 'інша', 'інше', 'інші'],
weekdays: {
monday: ['понеділок'],
tuesday: ['вівторок'],
wednesday: ['середа'],
thursday: ['четвер'],
friday: ['п\'ятниця'],
saturday: ['субота'],
sunday: ['неділя']
},
pluralWeekdays: {
monday: ['понеділки'],
tuesday: ['вівторки'],
wednesday: ['середи'],
thursday: ['четверги'],
friday: ['п\'ятниці'],
saturday: ['суботи'],
sunday: ['неділі']
},
ordinals: {
first: ['перший', 'перша', 'перше'],
second: ['другий', 'друга', 'друге'],
third: ['третій', 'третя', 'третє'],
fourth: ['четвертий', 'четверта', 'четверте'],
last: ['останній', 'остання', 'останнє']
},
periods: {
day: ['день', 'дні', 'днів'],
week: ['тиждень', 'тижні', 'тижнів'],
month: ['місяць', 'місяці', 'місяців'],
year: ['рік', 'роки', 'років']
}
},
timeEstimate: {
hours: ['г', 'год', 'година', 'години', 'годин'],
minutes: ['хв', 'мін', 'хвилина', 'хвилини', 'хвилин']
},
fallbackStatus: {
open: ['зробити', 'відкритий', 'очікує', 'todo', 'в очікуванні'],
inProgress: ['в роботі', 'виконується', 'роблю', 'працюю', 'в процесі'],
done: ['готово', 'виконано', 'завершено', 'закінчено', 'зроблено'],
cancelled: ['скасовано', 'відмінено', 'припинено'],
waiting: ['чекаю', 'очікую', 'заблоковано', 'призупинено']
},
fallbackPriority: {
urgent: ['терміново', 'критично', 'найвищий', 'невідкладно', 'пріоритетно'],
high: ['високий', 'висока', 'важливо', 'підвищений'],
normal: ['середній', 'середня', 'нормально', 'звичайно', 'стандартно'],
low: ['низький', 'низька', 'менший', 'мінімальний', 'незначний']
}
};

View file

@ -240,7 +240,7 @@ export class NaturalLanguageParser {
// Build regex patterns from language config with proper escaping and boundary handling
// Use lookahead/lookbehind for non-ASCII languages where \b doesn't work well
const isNonAscii = ['ru', 'zh', 'ja'].includes(this.languageConfig.code);
const isNonAscii = ['ru', 'zh', 'ja', 'uk'].includes(this.languageConfig.code);
const boundary = isNonAscii ? '(?:^|\\s)' : '\\b';
const endBoundary = isNonAscii ? '(?=\\s|$)' : '\\b';
@ -293,7 +293,7 @@ export class NaturalLanguageParser {
const langConfig = this.languageConfig.fallbackStatus;
// Use appropriate boundary matching for different languages
const isNonAscii = ['ru', 'zh', 'ja'].includes(this.languageConfig.code);
const isNonAscii = ['ru', 'zh', 'ja', 'uk'].includes(this.languageConfig.code);
const boundary = isNonAscii ? '(?:^|\\s)' : '\\b';
const endBoundary = isNonAscii ? '(?=\\s|$)' : '\\b';
@ -574,7 +574,7 @@ export class NaturalLanguageParser {
const patterns = [];
// Use appropriate boundary matching for different languages
const isNonAscii = ['ru', 'zh', 'ja'].includes(this.languageConfig.code);
const isNonAscii = ['ru', 'zh', 'ja', 'uk'].includes(this.languageConfig.code);
const boundary = isNonAscii ? '(?:^|\\s)' : '\\b';
const endBoundary = isNonAscii ? '(?=\\s|$)' : '\\b';
@ -793,7 +793,7 @@ export class NaturalLanguageParser {
const langConfig = this.languageConfig.timeEstimate;
// Use appropriate boundary matching for different languages
const isNonAscii = ['ru', 'zh', 'ja'].includes(this.languageConfig.code);
const isNonAscii = ['ru', 'zh', 'ja', 'uk'].includes(this.languageConfig.code);
const boundary = isNonAscii ? '(?:^|\\s)' : '\\b';
const endBoundary = isNonAscii ? '(?=\\s|$)' : '\\b';

View file

@ -43,6 +43,36 @@ describe('Language Configuration System', () => {
expect(languageRegistry['ja'].name).toBe('日本語');
expect(languageRegistry['ja'].code).toBe('ja');
});
it('should contain Italian configuration', () => {
expect(languageRegistry['it']).toBeDefined();
expect(languageRegistry['it'].name).toBe('Italiano');
expect(languageRegistry['it'].code).toBe('it');
});
it('should contain Dutch configuration', () => {
expect(languageRegistry['nl']).toBeDefined();
expect(languageRegistry['nl'].name).toBe('Nederlands');
expect(languageRegistry['nl'].code).toBe('nl');
});
it('should contain Portuguese configuration', () => {
expect(languageRegistry['pt']).toBeDefined();
expect(languageRegistry['pt'].name).toBe('Português');
expect(languageRegistry['pt'].code).toBe('pt');
});
it('should contain Swedish configuration', () => {
expect(languageRegistry['sv']).toBeDefined();
expect(languageRegistry['sv'].name).toBe('Svenska');
expect(languageRegistry['sv'].code).toBe('sv');
});
it('should contain Ukrainian configuration', () => {
expect(languageRegistry['uk']).toBeDefined();
expect(languageRegistry['uk'].name).toBe('Українська');
expect(languageRegistry['uk'].code).toBe('uk');
});
});
describe('getAvailableLanguages', () => {
@ -55,7 +85,12 @@ describe('Language Configuration System', () => {
{ value: 'de', label: 'Deutsch' },
{ value: 'ru', label: 'Русский' },
{ value: 'zh', label: '中文' },
{ value: 'ja', label: '日本語' }
{ value: 'ja', label: '日本語' },
{ value: 'it', label: 'Italiano' },
{ value: 'nl', label: 'Nederlands' },
{ value: 'pt', label: 'Português' },
{ value: 'sv', label: 'Svenska' },
{ value: 'uk', label: 'Українська' }
]);
});
});
@ -97,7 +132,7 @@ describe('Language Configuration System', () => {
it('should fallback to English for unsupported system language', () => {
// Mock navigator.language
const originalNavigator = global.navigator;
const mockNavigator = { language: 'it-IT' }; // Italian not supported yet
const mockNavigator = { language: 'ko-KR' }; // Korean not supported
Object.defineProperty(global, 'navigator', {
value: mockNavigator,
writable: true
@ -134,7 +169,7 @@ describe('Language Configuration System', () => {
describe('Language Configuration Structure', () => {
it('should have consistent structure across all languages', () => {
const languages = ['en', 'es', 'fr', 'de', 'ru', 'zh', 'ja'];
const languages = ['en', 'es', 'fr', 'de', 'ru', 'zh', 'ja', 'it', 'nl', 'pt', 'sv', 'uk'];
languages.forEach(langCode => {
const config = languageRegistry[langCode];

View file

@ -253,6 +253,171 @@ describe('NaturalLanguageParser Multi-Language', () => {
});
});
describe('Italian Language', () => {
let parser: NaturalLanguageParser;
beforeEach(() => {
// Use empty configs to test language fallback patterns
parser = new NaturalLanguageParser([], [], true, 'it');
});
it('should parse Italian priority keywords', () => {
const result = parser.parseInput('riunione urgente domani');
expect(result.priority).toBe('urgent');
expect(result.title).toMatch(/riunione/);
});
it('should parse Italian status keywords', () => {
const result = parser.parseInput('attività completato');
expect(result.status).toBe('done');
expect(result.title).toBe('attività');
});
it('should parse Italian time estimates', () => {
const result = parser.parseInput('attività 2 ore 30 minuti');
expect(result.estimate).toBe(150); // 2*60 + 30 = 150 minutes
expect(result.title).toBe('attività');
});
it('should parse Italian recurrence patterns', () => {
const result = parser.parseInput('riunione giornaliera team');
expect(result.recurrence).toBe('FREQ=DAILY');
expect(result.title).toMatch(/riunione.*team/);
});
});
describe('Dutch Language', () => {
let parser: NaturalLanguageParser;
beforeEach(() => {
// Use empty configs to test language fallback patterns
parser = new NaturalLanguageParser([], [], true, 'nl');
});
it('should parse Dutch priority keywords', () => {
const result = parser.parseInput('vergadering urgent morgen');
expect(result.priority).toBe('urgent');
expect(result.title).toMatch(/vergadering/);
});
it('should parse Dutch status keywords', () => {
const result = parser.parseInput('taak voltooid');
expect(result.status).toBe('done');
expect(result.title).toBe('taak');
});
it('should parse Dutch time estimates', () => {
const result = parser.parseInput('taak 2 uur 30 minuten');
expect(result.estimate).toBe(150); // 2*60 + 30 = 150 minutes
expect(result.title).toBe('taak');
});
it('should parse Dutch recurrence patterns', () => {
const result = parser.parseInput('vergadering dagelijks team');
expect(result.recurrence).toBe('FREQ=DAILY');
expect(result.title).toMatch(/vergadering.*team/);
});
});
describe('Portuguese Language', () => {
let parser: NaturalLanguageParser;
beforeEach(() => {
// Use empty configs to test language fallback patterns
parser = new NaturalLanguageParser([], [], true, 'pt');
});
it('should parse Portuguese priority keywords', () => {
const result = parser.parseInput('reunião urgente amanhã');
expect(result.priority).toBe('urgent');
expect(result.title).toMatch(/reunião/);
});
it('should parse Portuguese status keywords', () => {
const result = parser.parseInput('tarefa concluído');
expect(result.status).toBe('done');
expect(result.title).toBe('tarefa');
});
it('should parse Portuguese time estimates', () => {
const result = parser.parseInput('tarefa 2 horas 30 minutos');
expect(result.estimate).toBe(150); // 2*60 + 30 = 150 minutes
expect(result.title).toBe('tarefa');
});
it('should parse Portuguese recurrence patterns', () => {
const result = parser.parseInput('reunião diária equipe');
expect(result.recurrence).toBe('FREQ=DAILY');
expect(result.title).toMatch(/reunião.*equipe/);
});
});
describe('Swedish Language', () => {
let parser: NaturalLanguageParser;
beforeEach(() => {
// Use empty configs to test language fallback patterns
parser = new NaturalLanguageParser([], [], true, 'sv');
});
it('should parse Swedish priority keywords', () => {
const result = parser.parseInput('möte brådskande imorgon');
expect(result.priority).toBe('urgent');
expect(result.title).toMatch(/möte/);
});
it('should parse Swedish status keywords', () => {
const result = parser.parseInput('uppgift klar');
expect(result.status).toBe('done');
expect(result.title).toBe('uppgift');
});
it('should parse Swedish time estimates', () => {
const result = parser.parseInput('uppgift 2 timmar 30 minuter');
expect(result.estimate).toBe(150); // 2*60 + 30 = 150 minutes
expect(result.title).toBe('uppgift');
});
it('should parse Swedish recurrence patterns', () => {
const result = parser.parseInput('möte dagligen team');
expect(result.recurrence).toBe('FREQ=DAILY');
expect(result.title).toMatch(/möte.*team/);
});
});
describe('Ukrainian Language', () => {
let parser: NaturalLanguageParser;
beforeEach(() => {
// Use empty configs to test language fallback patterns
parser = new NaturalLanguageParser([], [], true, 'uk');
});
it('should parse Ukrainian priority keywords', () => {
const result = parser.parseInput('зустріч терміново завтра');
expect(result.priority).toBe('urgent');
expect(result.title).toMatch(/зустріч/);
});
it('should parse Ukrainian status keywords', () => {
const result = parser.parseInput('завдання виконано');
expect(result.status).toBe('done');
expect(result.title).toBe('завдання');
});
it('should parse Ukrainian time estimates', () => {
const result = parser.parseInput('завдання 2 години 30 хвилин');
expect(result.estimate).toBe(150); // 2*60 + 30 = 150 minutes
expect(result.title).toBe('завдання');
});
it('should parse Ukrainian recurrence patterns', () => {
const result = parser.parseInput('зустріч щодня команда');
expect(result.recurrence).toBe('FREQ=DAILY');
expect(result.title).toMatch(/зустріч.*команда/);
});
});
describe('Language Fallback', () => {
it('should fallback to English for unsupported language codes', () => {
const parser = new NaturalLanguageParser(mockStatusConfigs, mockPriorityConfigs, true, 'unsupported');