Line data Source code
1 : // Copyright 2026 The Authors
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // https://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : import 'package:intl/locale.dart';
16 : import 'package:snowball_stemmer/snowball_stemmer.dart';
17 :
18 : /// Reduces words to their base form (stem) using the Snowball algorithm.
19 : ///
20 : /// Supports 28 languages (see [Stemmer.new] for the full list). Construct
21 : /// with a [Locale] and call [stem] on individual tokens.
22 : ///
23 : /// ```dart
24 : /// final stemmer = Stemmer(Locale.parse('en'));
25 : /// print(stemmer.stem('libraries')); // → 'librari'
26 : /// print(stemmer.stem('running')); // → 'run'
27 : /// ```
28 : class Stemmer {
29 : final Locale _locale;
30 : final SnowballStemmer _stemmer;
31 :
32 1 : Stemmer._internal(Locale locale, SnowballStemmer stemmer)
33 : : _locale = locale,
34 : _stemmer = stemmer;
35 :
36 : /// Creates a [Stemmer] for the given [locale].
37 : ///
38 : /// Throws [ArgumentError] if the locale's language code is not supported.
39 : ///
40 : /// Supported (ISO 639-1 → Snowball algorithm): `ar` (Arabic), `hy`
41 : /// (Armenian), `eu` (Basque), `ca` (Catalan), `da` (Danish), `nl` (Dutch),
42 : /// `en` (English), `fi` (Finnish), `fr` (French), `de` (German), `el`
43 : /// (Greek), `hi` (Hindi), `hu` (Hungarian), `id` (Indonesian), `ga`
44 : /// (Irish), `it` (Italian), `lt` (Lithuanian), `ne` (Nepali), `no`
45 : /// (Norwegian), `pt` (Portuguese), `ro` (Romanian), `ru` (Russian), `sr`
46 : /// (Serbian), `es` (Spanish), `sv` (Swedish), `ta` (Tamil), `tr` (Turkish),
47 : /// `yi` (Yiddish) — every language `package:snowball_stemmer` implements,
48 : /// except its generic `porter` variant (an alternate English algorithm,
49 : /// not a distinct language — `en` already maps to `Algorithm.english`).
50 1 : factory Stemmer(Locale locale) {
51 1 : switch (locale.languageCode) {
52 1 : case 'ar':
53 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.arabic));
54 1 : case 'hy':
55 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.armenian));
56 1 : case 'eu':
57 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.basque));
58 1 : case 'ca':
59 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.catalan));
60 1 : case 'da':
61 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.danish));
62 1 : case 'nl':
63 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.dutch));
64 1 : case 'en':
65 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.english));
66 1 : case 'fi':
67 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.finnish));
68 1 : case 'fr':
69 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.french));
70 1 : case 'de':
71 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.german));
72 1 : case 'el':
73 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.greek));
74 1 : case 'hi':
75 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.hindi));
76 1 : case 'hu':
77 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.hungarian));
78 1 : case 'id':
79 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.indonesian));
80 1 : case 'ga':
81 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.irish));
82 1 : case 'it':
83 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.italian));
84 1 : case 'lt':
85 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.lithuanian));
86 1 : case 'ne':
87 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.nepali));
88 1 : case 'no':
89 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.norwegian));
90 1 : case 'pt':
91 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.portuguese));
92 1 : case 'ro':
93 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.romanian));
94 1 : case 'ru':
95 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.russian));
96 1 : case 'sr':
97 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.serbian));
98 1 : case 'es':
99 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.spanish));
100 1 : case 'sv':
101 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.swedish));
102 1 : case 'ta':
103 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.tamil));
104 1 : case 'tr':
105 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.turkish));
106 1 : case 'yi':
107 2 : return Stemmer._internal(locale, SnowballStemmer(Algorithm.yiddish));
108 : }
109 1 : throw ArgumentError.value(
110 1 : locale.languageCode,
111 : 'locale.languageCode',
112 : 'No stemmer available for language',
113 : );
114 : }
115 :
116 : /// Returns the stem of [word].
117 : ///
118 : /// The result is a normalised base form suitable for index lookups; it is
119 : /// not guaranteed to be a valid dictionary word.
120 1 : String stem(String word) {
121 2 : return _stemmer.stem(word);
122 : }
123 :
124 : /// The IETF language code this stemmer was created for (e.g. `'en'`).
125 3 : String get languageCode => _locale.languageCode;
126 : }
|