python-iso639 2025.2.8__tar.gz → 2025.11.11__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.
Files changed (24) hide show
  1. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/CHANGELOG.md +21 -1
  2. {python_iso639-2025.2.8/src/python_iso639.egg-info → python_iso639-2025.11.11}/PKG-INFO +33 -17
  3. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/README.md +21 -6
  4. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/pyproject.toml +10 -10
  5. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/iso639/__init__.py +1 -1
  6. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/iso639/_data/__init__.py +16 -18
  7. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/iso639/_data/iso-639-3.tab +14 -11
  8. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/iso639/_data/iso-639-3_Name_Index.tab +27 -5
  9. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/iso639/_data/iso-639-3_Retirements.tab +20 -22
  10. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/iso639/language.py +93 -39
  11. {python_iso639-2025.2.8 → python_iso639-2025.11.11/src/python_iso639.egg-info}/PKG-INFO +33 -17
  12. python_iso639-2025.11.11/src/python_iso639.egg-info/requires.txt +9 -0
  13. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/tests/test_language.py +53 -7
  14. python_iso639-2025.2.8/src/python_iso639.egg-info/requires.txt +0 -9
  15. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/LICENSE.txt +0 -0
  16. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/MANIFEST.in +0 -0
  17. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/setup.cfg +0 -0
  18. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/iso639/_data/iso-639-3-macrolanguages.tab +0 -0
  19. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/iso639/py.typed +0 -0
  20. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/python_iso639.egg-info/SOURCES.txt +0 -0
  21. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/python_iso639.egg-info/dependency_links.txt +0 -0
  22. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/python_iso639.egg-info/not-zip-safe +0 -0
  23. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/src/python_iso639.egg-info/top_level.txt +0 -0
  24. {python_iso639-2025.2.8 → python_iso639-2025.11.11}/tests/test_version_number.py +0 -0
@@ -17,10 +17,30 @@ major/minor/micro version numbers like `05` (it'd have to be just `5`).
17
17
  ### Fixed
18
18
  ### Security
19
19
 
20
+ ## [2025.11.11]
21
+
22
+ ### Added
23
+ * Added support for Python 3.14.
24
+
25
+ ### Changed
26
+ * Updated the ISO 639 data from SIL to the latest 2025-10-15 release.
27
+ * Case-insensitive matching for `Language.match` is now opt-in.
28
+ The keyword argument has been renamed `strict_case` (from `exact` before),
29
+ defaulting to `True`.
30
+
31
+ ### Removed
32
+ * Dropped support for Python 3.9.
33
+
34
+ ## [2025.2.18]
35
+
36
+ ### Added
37
+ * `Language.match` now supports case-insensitive matching
38
+ and ignores leading/trailing whitespace by default.
39
+
20
40
  ## [2025.2.8]
21
41
 
22
42
  ### Added
23
- * Explcitly indicated that the library is type-annotated.
43
+ * Explicitly indicated that the library is type-annotated.
24
44
 
25
45
  ## [2025.1.28]
26
46
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: python-iso639
3
- Version: 2025.2.8
3
+ Version: 2025.11.11
4
4
  Summary: ISO 639 language codes, names, and other associated information
5
5
  Author-email: "Jackson L. Lee" <jacksonlunlee@gmail.com>
6
6
  License: Apache 2.0
@@ -15,26 +15,27 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Text Processing
24
24
  Classifier: Topic :: Text Processing :: General
25
25
  Classifier: Topic :: Text Processing :: Indexing
26
26
  Classifier: Topic :: Text Processing :: Linguistic
27
- Requires-Python: >=3.9
27
+ Requires-Python: >=3.10
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE.txt
30
30
  Provides-Extra: dev
31
- Requires-Dist: black==25.1.0; extra == "dev"
32
- Requires-Dist: build==1.2.2; extra == "dev"
33
- Requires-Dist: flake8==7.1.1; extra == "dev"
34
- Requires-Dist: mypy==1.15.0; extra == "dev"
35
- Requires-Dist: pytest==8.3.4; extra == "dev"
36
- Requires-Dist: requests==2.32.3; extra == "dev"
37
- Requires-Dist: twine==6.1.0; extra == "dev"
31
+ Requires-Dist: black==25.11.0; extra == "dev"
32
+ Requires-Dist: build==1.3.0; extra == "dev"
33
+ Requires-Dist: flake8==7.3.0; extra == "dev"
34
+ Requires-Dist: mypy==1.18.2; extra == "dev"
35
+ Requires-Dist: pytest==9.0.0; extra == "dev"
36
+ Requires-Dist: requests==2.32.5; extra == "dev"
37
+ Requires-Dist: twine==6.2.0; extra == "dev"
38
+ Dynamic: license-file
38
39
 
39
40
  # python-iso639
40
41
 
@@ -83,13 +84,13 @@ Create a `Language` instance by one of the class methods.
83
84
  Language(part3='fra', part2b='fre', part2t='fra', part1='fr', scope='I', type='L', name='French', comment=None, other_names=None, macrolanguage=None, retire_reason=None, retire_change_to=None, retire_remedy=None, retire_date=None)
84
85
  ```
85
86
 
86
- Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.2, Apple M1 Pro)
87
+ Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.3.1, Apple M1 Pro)
87
88
 
88
89
  ```python
89
90
  In [1]: import iso639
90
91
 
91
92
  In [2]: %timeit iso639.Language.from_part3("fra")
92
- 220 ns ± 0.658 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
93
+ 217 ns ± 0.139 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
93
94
  ```
94
95
 
95
96
  #### From Another ISO 639 Code Set or a Reference Name
@@ -154,11 +155,27 @@ Use the `match` classmethod:
154
155
  True
155
156
  ```
156
157
 
158
+ By default, the classmethod `match` supports case-insensitive matching
159
+ and ignores leading/trailing whitespace.
160
+ To enforce exact matching instead, pass in `exact=True`:
161
+
162
+ ```python
163
+ >>> lang5 = iso639.Language.match('FRA')
164
+ >>> lang6 = iso639.Language.match('fra ')
165
+ >>> lang7 = iso639.Language.match('french')
166
+ >>> lang4 == lang5 == lang6 == lang7
167
+ True
168
+ >>> iso639.Language.match("french", exact=True)
169
+ Traceback (most recent call last):
170
+ File "<stdin>", line 1, in <module>
171
+ LanguageNotFoundError: 'french' isn't an ISO language code or name
172
+ ```
173
+
157
174
  The classmethod `match` is particularly useful for consistently
158
175
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
159
176
 
160
177
  ```python
161
- >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3
178
+ >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3 == lang5.part3 == lang6.part3 == lang7.part3
162
179
  True
163
180
  ```
164
181
 
@@ -261,7 +278,6 @@ Beyond that, the precise order in matching is as follows:
261
278
  * ISO 639-3 alternative language names (the "print" ones)
262
279
  * ISO 639-3 alternative language names (the "inverted" ones)
263
280
 
264
- Only exact matching is done (there's no fuzzy string matching of any sort).
265
281
  As soon as a match is found, `Language.match` returns a `Language` instance.
266
282
  If there isn't a match, a `LanguageNotFoundError` is raised.
267
283
 
@@ -287,7 +303,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
287
303
  ```python
288
304
  >>> import iso639
289
305
  >>> iso639.DATA_LAST_UPDATED
290
- datetime.date(2025, 1, 15)
306
+ datetime.date(2025, 10, 15)
291
307
  ```
292
308
 
293
309
  * `ALL_LANGUAGES`: The list of all `Language` objects based on the included language code data
@@ -297,7 +313,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
297
313
  >>> type(iso639.ALL_LANGUAGES)
298
314
  <class 'set'>
299
315
  >>> len(iso639.ALL_LANGUAGES)
300
- 8307
316
+ 8311
301
317
  ```
302
318
 
303
319
  ## Links
@@ -45,13 +45,13 @@ Create a `Language` instance by one of the class methods.
45
45
  Language(part3='fra', part2b='fre', part2t='fra', part1='fr', scope='I', type='L', name='French', comment=None, other_names=None, macrolanguage=None, retire_reason=None, retire_change_to=None, retire_remedy=None, retire_date=None)
46
46
  ```
47
47
 
48
- Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.2, Apple M1 Pro)
48
+ Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.3.1, Apple M1 Pro)
49
49
 
50
50
  ```python
51
51
  In [1]: import iso639
52
52
 
53
53
  In [2]: %timeit iso639.Language.from_part3("fra")
54
- 220 ns ± 0.658 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
54
+ 217 ns ± 0.139 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
55
55
  ```
56
56
 
57
57
  #### From Another ISO 639 Code Set or a Reference Name
@@ -116,11 +116,27 @@ Use the `match` classmethod:
116
116
  True
117
117
  ```
118
118
 
119
+ By default, the classmethod `match` supports case-insensitive matching
120
+ and ignores leading/trailing whitespace.
121
+ To enforce exact matching instead, pass in `exact=True`:
122
+
123
+ ```python
124
+ >>> lang5 = iso639.Language.match('FRA')
125
+ >>> lang6 = iso639.Language.match('fra ')
126
+ >>> lang7 = iso639.Language.match('french')
127
+ >>> lang4 == lang5 == lang6 == lang7
128
+ True
129
+ >>> iso639.Language.match("french", exact=True)
130
+ Traceback (most recent call last):
131
+ File "<stdin>", line 1, in <module>
132
+ LanguageNotFoundError: 'french' isn't an ISO language code or name
133
+ ```
134
+
119
135
  The classmethod `match` is particularly useful for consistently
120
136
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
121
137
 
122
138
  ```python
123
- >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3
139
+ >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3 == lang5.part3 == lang6.part3 == lang7.part3
124
140
  True
125
141
  ```
126
142
 
@@ -223,7 +239,6 @@ Beyond that, the precise order in matching is as follows:
223
239
  * ISO 639-3 alternative language names (the "print" ones)
224
240
  * ISO 639-3 alternative language names (the "inverted" ones)
225
241
 
226
- Only exact matching is done (there's no fuzzy string matching of any sort).
227
242
  As soon as a match is found, `Language.match` returns a `Language` instance.
228
243
  If there isn't a match, a `LanguageNotFoundError` is raised.
229
244
 
@@ -249,7 +264,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
249
264
  ```python
250
265
  >>> import iso639
251
266
  >>> iso639.DATA_LAST_UPDATED
252
- datetime.date(2025, 1, 15)
267
+ datetime.date(2025, 10, 15)
253
268
  ```
254
269
 
255
270
  * `ALL_LANGUAGES`: The list of all `Language` objects based on the included language code data
@@ -259,7 +274,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
259
274
  >>> type(iso639.ALL_LANGUAGES)
260
275
  <class 'set'>
261
276
  >>> len(iso639.ALL_LANGUAGES)
262
- 8307
277
+ 8311
263
278
  ```
264
279
 
265
280
  ## Links
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-iso639"
7
- version = "2025.2.8"
7
+ version = "2025.11.11"
8
8
  description = "ISO 639 language codes, names, and other associated information"
9
9
  readme = "README.md"
10
- requires-python = ">= 3.9"
10
+ requires-python = ">= 3.10"
11
11
  license = { text = "Apache 2.0" }
12
12
  authors = [ { name = "Jackson L. Lee", email = "jacksonlunlee@gmail.com" } ]
13
13
  keywords = ["ISO 639", "language codes", "languages", "linguistics"]
@@ -21,11 +21,11 @@ classifiers = [
21
21
  "Operating System :: OS Independent",
22
22
  "Programming Language :: Python :: 3",
23
23
  "Programming Language :: Python :: 3 :: Only",
24
- "Programming Language :: Python :: 3.9",
25
24
  "Programming Language :: Python :: 3.10",
26
25
  "Programming Language :: Python :: 3.11",
27
26
  "Programming Language :: Python :: 3.12",
28
27
  "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
29
  "Topic :: Text Processing",
30
30
  "Topic :: Text Processing :: General",
31
31
  "Topic :: Text Processing :: Indexing",
@@ -34,13 +34,13 @@ classifiers = [
34
34
 
35
35
  [project.optional-dependencies]
36
36
  dev = [
37
- "black == 25.1.0",
38
- "build == 1.2.2",
39
- "flake8 == 7.1.1",
40
- "mypy == 1.15.0",
41
- "pytest == 8.3.4",
42
- "requests == 2.32.3",
43
- "twine == 6.1.0",
37
+ "black == 25.11.0",
38
+ "build == 1.3.0",
39
+ "flake8 == 7.3.0",
40
+ "mypy == 1.18.2",
41
+ "pytest == 9.0.0",
42
+ "requests == 2.32.5",
43
+ "twine == 6.2.0",
44
44
  ]
45
45
 
46
46
  [project.urls]
@@ -9,7 +9,7 @@ from .language import ALL_LANGUAGES, Language, LanguageNotFoundError
9
9
  # While DATA_LAST_UPDATED is strictly the date for the ISO 639-3 data release,
10
10
  # __version__ can be bumped for changes other than data updates.
11
11
  __version__ = version("python-iso639")
12
- DATA_LAST_UPDATED = datetime.date(2025, 1, 15)
12
+ DATA_LAST_UPDATED = datetime.date(2025, 10, 15)
13
13
 
14
14
 
15
15
  __all__ = [
@@ -5,8 +5,6 @@ import os
5
5
  from collections import defaultdict
6
6
  from enum import Enum
7
7
 
8
- from typing import Dict, List, Union
9
-
10
8
 
11
9
  class _CodesColumn(Enum):
12
10
  ID = "Id"
@@ -40,10 +38,10 @@ class _MacrolanguagesColumn(Enum):
40
38
  STATUS = "I_Status"
41
39
 
42
40
 
43
- _COLUMN_TYPE = Union[
44
- _CodesColumn, _NameIndexColumn, _RetirementsColumn, _MacrolanguagesColumn
45
- ]
46
- _ROW_TYPE = Dict[_COLUMN_TYPE, str]
41
+ _COLUMN_TYPE = (
42
+ _CodesColumn | _NameIndexColumn | _RetirementsColumn | _MacrolanguagesColumn
43
+ )
44
+ _ROW_TYPE = dict[_COLUMN_TYPE, str]
47
45
 
48
46
 
49
47
  class _Table(Enum):
@@ -62,56 +60,56 @@ _DATA_TSV_PATHS = {
62
60
  }
63
61
 
64
62
 
65
- def _load_tsv(table: _Table) -> List[_ROW_TYPE]:
63
+ def _load_tsv(table: _Table) -> list[_ROW_TYPE]:
66
64
  column = table.value
67
65
  with open(_DATA_TSV_PATHS[table], encoding="utf-8", newline="") as tsv_file:
68
66
  tsv_reader = csv.DictReader(tsv_file, delimiter="\t")
69
67
  return [{column(k): v for k, v in row.items()} for row in tsv_reader]
70
68
 
71
69
 
72
- _PART3_TO_CODES: Dict[str, _ROW_TYPE] = {
70
+ _PART3_TO_CODES: dict[str, _ROW_TYPE] = {
73
71
  row[_CodesColumn.ID]: row for row in _load_tsv(_Table.CODES)
74
72
  }
75
73
 
76
- _part3_to_name_index: defaultdict[str, List[_ROW_TYPE]] = defaultdict(list)
74
+ _part3_to_name_index: defaultdict[str, list[_ROW_TYPE]] = defaultdict(list)
77
75
  for row in _load_tsv(_Table.NAME_INDEX):
78
76
  _part3_to_name_index[row[_NameIndexColumn.ID]].append(row)
79
- _PART3_TO_NAME_INDEX: Dict[str, List[_ROW_TYPE]] = dict(_part3_to_name_index)
77
+ _PART3_TO_NAME_INDEX: dict[str, list[_ROW_TYPE]] = dict(_part3_to_name_index)
80
78
 
81
- _PART3_TO_RETIREMENTS: Dict[str, _ROW_TYPE] = {
79
+ _PART3_TO_RETIREMENTS: dict[str, _ROW_TYPE] = {
82
80
  row[_RetirementsColumn.ID]: row for row in _load_tsv(_Table.RETIREMENTS)
83
81
  }
84
- _PART3_TO_MACROLANGUAGES: Dict[str, _ROW_TYPE] = {
82
+ _PART3_TO_MACROLANGUAGES: dict[str, _ROW_TYPE] = {
85
83
  row[_MacrolanguagesColumn.ID]: row for row in _load_tsv(_Table.MACROLANGUAGES)
86
84
  }
87
85
 
88
- _PART2B_TO_PART3: Dict[str, str] = {
86
+ _PART2B_TO_PART3: dict[str, str] = {
89
87
  row[_CodesColumn.PART2B]: part3
90
88
  for part3, row in _PART3_TO_CODES.items()
91
89
  if row[_CodesColumn.PART2B]
92
90
  }
93
- _PART2T_TO_PART3: Dict[str, str] = {
91
+ _PART2T_TO_PART3: dict[str, str] = {
94
92
  row[_CodesColumn.PART2T]: part3
95
93
  for part3, row in _PART3_TO_CODES.items()
96
94
  if row[_CodesColumn.PART2T]
97
95
  }
98
- _PART1_TO_PART3: Dict[str, str] = {
96
+ _PART1_TO_PART3: dict[str, str] = {
99
97
  row[_CodesColumn.PART1]: part3
100
98
  for part3, row in _PART3_TO_CODES.items()
101
99
  if row[_CodesColumn.PART1]
102
100
  }
103
- _REF_NAME_TO_PART3: Dict[str, str] = {
101
+ _REF_NAME_TO_PART3: dict[str, str] = {
104
102
  row[_CodesColumn.REF_NAME]: part3
105
103
  for part3, row in _PART3_TO_CODES.items()
106
104
  if row[_CodesColumn.REF_NAME]
107
105
  }
108
- _PRINT_NAME_TO_PART3: Dict[str, str] = {
106
+ _PRINT_NAME_TO_PART3: dict[str, str] = {
109
107
  row[_NameIndexColumn.PRINT_NAME]: part3
110
108
  for part3, rows in _PART3_TO_NAME_INDEX.items()
111
109
  for row in rows
112
110
  if row[_NameIndexColumn.PRINT_NAME]
113
111
  }
114
- _INVERTED_NAME_TO_PART3: Dict[str, str] = {
112
+ _INVERTED_NAME_TO_PART3: dict[str, str] = {
115
113
  row[_NameIndexColumn.INVERTED_NAME]: part3
116
114
  for part3, rows in _PART3_TO_NAME_INDEX.items()
117
115
  for row in rows
@@ -1297,7 +1297,7 @@ cli I L Chakali
1297
1297
  clj I L Laitu Chin
1298
1298
  clk I L Idu-Mishmi
1299
1299
  cll I L Chala
1300
- clm I L Clallam
1300
+ clm I L Klallam
1301
1301
  clo I L Lowland Oaxaca Chontal
1302
1302
  cls I H Classical Sanskrit
1303
1303
  clt I L Lautu Chin
@@ -2325,7 +2325,7 @@ gyz I L Geji
2325
2325
  gza I L Ganza
2326
2326
  gzi I L Gazi
2327
2327
  gzn I L Gane
2328
- haa I L Han
2328
+ haa I L Hän
2329
2329
  hab I L Hanoi Sign Language
2330
2330
  hac I L Gurani
2331
2331
  had I L Hatam
@@ -2892,7 +2892,7 @@ kce I L Kaivi
2892
2892
  kcf I L Ukaan
2893
2893
  kcg I L Tyap
2894
2894
  kch I L Vono
2895
- kci I L Kamantan
2895
+ kci I L Ngyian
2896
2896
  kcj I L Kobiana
2897
2897
  kck I L Kalanga
2898
2898
  kcl I L Kela (Papua New Guinea)
@@ -3390,7 +3390,7 @@ kwg I L Sara Kaba Deme
3390
3390
  kwh I L Kowiai
3391
3391
  kwi I L Awa-Cuaiquer
3392
3392
  kwj I L Kwanga
3393
- kwk I L Kwakiutl
3393
+ kwk I L Kwak'wala
3394
3394
  kwl I L Kofyar
3395
3395
  kwm I L Kwambi
3396
3396
  kwn I L Kwangali
@@ -3795,7 +3795,7 @@ lup I L Lumbu
3795
3795
  luq I L Lucumi
3796
3796
  lur I L Laura
3797
3797
  lus lus lus I L Lushai
3798
- lut I L Lushootseed
3798
+ lut I E Lushootseed
3799
3799
  luu I L Lumba-Yakkha
3800
3800
  luv I L Luwati
3801
3801
  luw I L Luo (Cameroon)
@@ -4011,7 +4011,7 @@ mhj I L Mogholi
4011
4011
  mhk I L Mungaka
4012
4012
  mhl I L Mauwake
4013
4013
  mhm I L Makhuwa-Moniga
4014
- mhn I L Mócheno
4014
+ mhn I L Mòcheno
4015
4015
  mho I L Mashi (Zambia)
4016
4016
  mhp I L Balinese Malay
4017
4017
  mhq I L Mandan
@@ -4574,7 +4574,7 @@ nes I L Bhoti Kinnauri
4574
4574
  net I L Nete
4575
4575
  neu I C Neo
4576
4576
  nev I L Nyaheun
4577
- new new new I L Newari
4577
+ new new new I L Nepal Bhasa
4578
4578
  nex I L Neme
4579
4579
  ney I L Neyo
4580
4580
  nez I L Nez Perce
@@ -4916,7 +4916,7 @@ nxo I L Ndambomo
4916
4916
  nxq I L Naxi
4917
4917
  nxr I L Ninggerum
4918
4918
  nxx I L Nafri
4919
- nya nya nya ny I L Nyanja
4919
+ nya nya nya ny I L Chichewa
4920
4920
  nyb I L Nyangbo
4921
4921
  nyc I L Nyanga-li
4922
4922
  nyd I L Nyore
@@ -4954,6 +4954,7 @@ nzy I L Nzakambay
4954
4954
  nzz I L Nanga Dama Dogon
4955
4955
  oaa I L Orok
4956
4956
  oac I L Oroch
4957
+ oak I L Noakhali
4957
4958
  oar I H Old Aramaic (up to 700 BCE)
4958
4959
  oav I H Old Avar
4959
4960
  obi I E Obispeño
@@ -5017,6 +5018,7 @@ okv I L Orokaiva
5017
5018
  okx I L Okpe (Northwestern Edo)
5018
5019
  okz I H Old Khmer
5019
5020
  ola I L Walungge
5021
+ olb I L Oli-Bodiman
5020
5022
  old I L Mochi
5021
5023
  ole I L Olekha
5022
5024
  olk I E Olkol
@@ -5088,11 +5090,12 @@ ory I L Odia
5088
5090
  orz I L Ormu
5089
5091
  osa osa osa I L Osage
5090
5092
  osc I H Oscan
5093
+ osd I L Digor Ossetic
5091
5094
  osi I L Osing
5092
5095
  osn I H Old Sundanese
5093
5096
  oso I L Ososo
5094
5097
  osp I H Old Spanish
5095
- oss oss oss os I L Ossetian
5098
+ oss oss oss os I L Iron Ossetic
5096
5099
  ost I L Osatu
5097
5100
  osu I L Southern One
5098
5101
  osx I H Old Saxon
@@ -5758,6 +5761,7 @@ scu I L Shumcho
5758
5761
  scv I L Sheni
5759
5762
  scw I L Sha
5760
5763
  scx I H Sicel
5764
+ scz I L Shaetlan
5761
5765
  sda I L Toraja-Sa'dan
5762
5766
  sdb I L Shabak
5763
5767
  sdc I L Sassarese Sardinian
@@ -7651,7 +7655,6 @@ yob I E Yoba
7651
7655
  yog I L Yogad
7652
7656
  yoi I L Yonaguni
7653
7657
  yok I L Yokuts
7654
- yol I E Yola
7655
7658
  yom I L Yombe
7656
7659
  yon I L Yongkom
7657
7660
  yor yor yor yo I L Yoruba
@@ -7833,7 +7836,7 @@ zml I E Matngala
7833
7836
  zmm I L Marimanindji
7834
7837
  zmn I L Mbangwe
7835
7838
  zmo I L Molo
7836
- zmp I L Mpuono
7839
+ zmp I L Mbuun
7837
7840
  zmq I L Mituku
7838
7841
  zmr I L Maranunggu
7839
7842
  zms I L Mbesa
@@ -378,6 +378,7 @@ ary Moroccan Arabic Arabic, Moroccan
378
378
  arz Egyptian Arabic Arabic, Egyptian
379
379
  asa Asu (Tanzania) Asu (Tanzania)
380
380
  asb Assiniboine Assiniboine
381
+ asb Nakoda Assiniboine Nakoda Assiniboine
381
382
  asc Casuarina Coast Asmat Asmat, Casuarina Coast
382
383
  ase American Sign Language American Sign Language
383
384
  asf Auslan Auslan
@@ -413,6 +414,7 @@ ate Atemble Atemble
413
414
  atg Ivbie North-Okpela-Arhe Ivbie North-Okpela-Arhe
414
415
  ati Attié Attié
415
416
  atj Atikamekw Atikamekw
417
+ atj Nehirowimowin Nehirowimowin
416
418
  atk Ati Ati
417
419
  atl Mt. Iraya Agta Agta, Mt. Iraya
418
420
  atm Ata Ata
@@ -1340,6 +1342,7 @@ cky Cakfem-Mushere Cakfem-Mushere
1340
1342
  ckz Cakchiquel-Quiché Mixed Language Cakchiquel-Quiché Mixed Language
1341
1343
  cla Ron Ron
1342
1344
  clc Chilcotin Chilcotin
1345
+ clc Tsilhqot’in Tsilhqot’in
1343
1346
  cld Chaldean Neo-Aramaic Neo-Aramaic, Chaldean
1344
1347
  cle Lealao Chinantec Chinantec, Lealao
1345
1348
  clh Chilisso Chilisso
@@ -1348,6 +1351,7 @@ clj Laitu Chin Chin, Laitu
1348
1351
  clk Idu-Mishmi Idu-Mishmi
1349
1352
  cll Chala Chala
1350
1353
  clm Clallam Clallam
1354
+ clm Klallam Klallam
1351
1355
  clo Lowland Oaxaca Chontal Chontal, Lowland Oaxaca
1352
1356
  cls Classical Sanskrit Classical Sanskrit
1353
1357
  clt Lautu Chin Chin, Lautu
@@ -2432,7 +2436,7 @@ gyz Gyaazi Gyaazi
2432
2436
  gza Ganza Ganza
2433
2437
  gzi Gazi Gazi
2434
2438
  gzn Gane Gane
2435
- haa Han Han
2439
+ haa Hän Hän
2436
2440
  hab Hanoi Sign Language Hanoi Sign Language
2437
2441
  hac Gurani Gurani
2438
2442
  had Hatam Hatam
@@ -3042,6 +3046,7 @@ kcf Ukaan Ukaan
3042
3046
  kcg Tyap Tyap
3043
3047
  kch Vono Vono
3044
3048
  kci Kamantan Kamantan
3049
+ kci Ngyian Ngyian
3045
3050
  kcj Kobiana Kobiana
3046
3051
  kck Kalanga Kalanga
3047
3052
  kcl Kala Kala
@@ -3565,6 +3570,7 @@ kwh Kowiai Kowiai
3565
3570
  kwi Awa-Cuaiquer Awa-Cuaiquer
3566
3571
  kwj Kwanga Kwanga
3567
3572
  kwk Kwakiutl Kwakiutl
3573
+ kwk Kwak'wala Kwak'wala
3568
3574
  kwl Kofyar Kofyar
3569
3575
  kwm Kwambi Kwambi
3570
3576
  kwn Kwangali Kwangali
@@ -4217,7 +4223,7 @@ mhj Mogholi Mogholi
4217
4223
  mhk Mungaka Mungaka
4218
4224
  mhl Mauwake Mauwake
4219
4225
  mhm Makhuwa-Moniga Makhuwa-Moniga
4220
- mhn Mócheno Mócheno
4226
+ mhn Mòcheno Mòcheno
4221
4227
  mho Mashi (Zambia) Mashi (Zambia)
4222
4228
  mhp Balinese Malay Malay, Balinese
4223
4229
  mhq Mandan Mandan
@@ -4804,6 +4810,7 @@ net Nete Nete
4804
4810
  neu Neo Neo
4805
4811
  nev Nyaheun Nyaheun
4806
4812
  new Nepal Bhasa Bhasa, Nepal
4813
+ new Newar Newar
4807
4814
  new Newari Newari
4808
4815
  nex Neme Neme
4809
4816
  ney Neyo Neyo
@@ -5202,6 +5209,8 @@ nzy Nzakambay Nzakambay
5202
5209
  nzz Nanga Dama Dogon Dogon, Nanga Dama
5203
5210
  oaa Orok Orok
5204
5211
  oac Oroch Oroch
5212
+ oak Noakhailla Noakhailla
5213
+ oak Noakhali Noakhali
5205
5214
  oar Ancient Aramaic (up to 700 BCE) Aramaic, Ancient (up to 700 BCE)
5206
5215
  oar Old Aramaic (up to 700 BCE) Aramaic, Old (up to 700 BCE)
5207
5216
  oav Old Avar Avar, Old
@@ -5266,6 +5275,7 @@ okv Orokaiva Orokaiva
5266
5275
  okx Okpe (Northwestern Edo) Okpe (Northwestern Edo)
5267
5276
  okz Old Khmer Khmer, Old
5268
5277
  ola Walungge Walungge
5278
+ olb Oli-Bodiman Oli-Bodiman
5269
5279
  old Mochi Mochi
5270
5280
  ole Olekha Olekha
5271
5281
  olk Olkol Olkol
@@ -5338,10 +5348,16 @@ ory Oriya (individual language) Oriya (individual language)
5338
5348
  orz Ormu Ormu
5339
5349
  osa Osage Osage
5340
5350
  osc Oscan Oscan
5351
+ osd Digor Digor
5352
+ osd Digor Ossetian Ossetian, Digor
5353
+ osd Digor Ossetic Ossetic, Digor
5341
5354
  osi Osing Osing
5342
5355
  osn Old Sundanese Sundanese, Old
5343
5356
  oso Ososo Ososo
5344
5357
  osp Old Spanish Spanish, Old
5358
+ oss Iron Iron
5359
+ oss Iron Ossetian Ossetian, Iron
5360
+ oss Iron Ossetic Ossetic, Iron
5345
5361
  oss Ossetian Ossetian
5346
5362
  oss Ossetic Ossetic
5347
5363
  ost Osatu Osatu
@@ -5738,7 +5754,7 @@ pyx Pyu (Myanmar) Pyu (Myanmar)
5738
5754
  pyy Pyen Pyen
5739
5755
  pze Pesse Pesse
5740
5756
  pzh Pazeh Pazeh
5741
- pzn Jejara Naga Jejara Naga
5757
+ pzn Jejara Naga Naga, Jejara
5742
5758
  pzn Para Naga Naga, Para
5743
5759
  qua Quapaw Quapaw
5744
5760
  qub Huallaga Huánuco Quechua Quechua, Huallaga Huánuco
@@ -6043,6 +6059,7 @@ scu Shumcho Shumcho
6043
6059
  scv Sheni Sheni
6044
6060
  scw Sha Sha
6045
6061
  scx Sicel Sicel
6062
+ scz Shaetlan Shaetlan
6046
6063
  sda Toraja-Sa'dan Toraja-Sa'dan
6047
6064
  sdb Shabak Shabak
6048
6065
  sdc Sassarese Sardinian Sardinian, Sassarese
@@ -6066,6 +6083,7 @@ sdz Sallands Sallands
6066
6083
  sea Semai Semai
6067
6084
  seb Shempire Senoufo Senoufo, Shempire
6068
6085
  sec Sechelt Sechelt
6086
+ sec She shashishalhem She shashishalhem
6069
6087
  sed Sedang Sedang
6070
6088
  see Seneca Seneca
6071
6089
  sef Cebaara Senoufo Senoufo, Cebaara
@@ -6130,6 +6148,7 @@ sho Shanga Shanga
6130
6148
  shp Shipibo-Conibo Shipibo-Conibo
6131
6149
  shq Sala Sala
6132
6150
  shr Shi Shi
6151
+ shs Secwepemctsín Secwepemctsín
6133
6152
  shs Shuswap Shuswap
6134
6153
  sht Shasta Shasta
6135
6154
  shu Chadian Arabic Arabic, Chadian
@@ -6335,6 +6354,7 @@ sqq Sou Sou
6335
6354
  sqr Siculo Arabic Arabic, Siculo
6336
6355
  sqs Sri Lankan Sign Language Sri Lankan Sign Language
6337
6356
  sqt Soqotri Soqotri
6357
+ squ Sḵwx̱wú7mesh sníchim Sḵwx̱wú7mesh sníchim
6338
6358
  squ Squamish Squamish
6339
6359
  sqx Kufr Qassem Sign Language (KQSL) Kufr Qassem Sign Language (KQSL)
6340
6360
  sra Saruga Saruga
@@ -6668,11 +6688,14 @@ thk Tharaka Tharaka
6668
6688
  thl Dangaura Tharu Tharu, Dangaura
6669
6689
  thm Aheu Aheu
6670
6690
  thn Thachanadan Thachanadan
6691
+ thp Nłeʔkepmxcín Nłeʔkepmxcín
6671
6692
  thp Thompson Thompson
6693
+ thp Thompson River Salish Salish, Thompson River
6672
6694
  thq Kochila Tharu Tharu, Kochila
6673
6695
  thr Rana Tharu Tharu, Rana
6674
6696
  ths Thakali Thakali
6675
6697
  tht Tahltan Tahltan
6698
+ tht Tāłtān Tāłtān
6676
6699
  thu Thuri Thuri
6677
6700
  thv Tahaggart Tamahaq Tamahaq, Tahaggart
6678
6701
  thy Tha Tha
@@ -8011,7 +8034,6 @@ yob Yoba Yoba
8011
8034
  yog Yogad Yogad
8012
8035
  yoi Yonaguni Yonaguni
8013
8036
  yok Yokuts Yokuts
8014
- yol Yola Yola
8015
8037
  yom Yombe Yombe
8016
8038
  yon Yongkom Yongkom
8017
8039
  yor Yoruba Yoruba
@@ -8201,7 +8223,7 @@ zmm Marimanindji Marimanindji
8201
8223
  zmm Marramaninyshi Marramaninyshi
8202
8224
  zmn Mbangwe Mbangwe
8203
8225
  zmo Molo Molo
8204
- zmp Mpuono Mpuono
8226
+ zmp Mbuun Mbuun
8205
8227
  zmq Mituku Mituku
8206
8228
  zmr Maranunggu Maranunggu
8207
8229
  zms Mbesa Mbesa
@@ -1,5 +1,5 @@
1
1
  Id Ref_Name Ret_Reason Change_To Ret_Remedy Effective
2
- fri Western Frisian C fry 2007-02-01
2
+ fri Western Frisian C fry 2005-11-16
3
3
  auv Auvergnat M oci 2007-03-14
4
4
  gsc Gascon M oci 2007-03-14
5
5
  lms Limousin M oci 2007-03-14
@@ -18,7 +18,6 @@ mob Moinba S Split into five languages: Chug [cvg]; Lish [lsh]; Kalaktang Monp
18
18
  mzf Aiku S Split into four languages: Ambrak [aag]; Yangum Dey [yde]; Yangum Gel [ygl]; Yangum Mon [ymo] 2007-07-18
19
19
  nhj Tlalitzlipa Nahuatl M nhi 2007-07-18
20
20
  nhs Southeastern Puebla Nahuatl S Split into Sierra Negra Nahuatl [nsu] and Southeastern Puebla Nahuatl [npl] 2007-07-18
21
- occ Occidental D ile 2007-07-18
22
21
  tmx Tomyang M ybi 2007-07-18
23
22
  tot Patla-Chicontla Totonac S Split into Upper Necaxa Totonac [tku] and Tecpatlán Totonac [tcw] 2007-07-18
24
23
  xmi Miarrã N 2007-07-18
@@ -27,6 +26,7 @@ ztc Lachirioag Zapotec M zty 2007-07-18
27
26
  atf Atuence N 2007-08-10
28
27
  bqe Navarro-Labourdin Basque M eus 2007-08-10
29
28
  bsz Souletin Basque M eus 2007-08-10
29
+ occ Occidental D ile 2007-12-07
30
30
  aex Amerax M eng 2008-01-14
31
31
  ahe Ahe M knx 2008-01-14
32
32
  aiz Aari S Split into Aari [aiw] (new identifier) and Gayil [gyl] 2008-01-14
@@ -169,21 +169,21 @@ cmk Chimakum D xch 2010-01-18
169
169
  drh Darkhat M khk 2010-01-18
170
170
  drw Darwazi M prs 2010-01-18
171
171
  gav Gabutamon M dev 2010-01-18
172
- mof Mohegan-Montauk-Narragansett S split into Mohegan-Pequot [xpq] and Narragansett [xnt] 2010-01-18
172
+ mof Mohegan-Montauk-Narragansett S Split into Mohegan-Pequot [xpq] and Narragansett [xnt] 2010-01-18
173
173
  mst Cataelano Mandaya M mry 2010-01-18
174
174
  myt Sangab Mandaya M mry 2010-01-18
175
- rmr Caló S split into Caló [rmq] and Erromintxela [emx] 2010-01-18
176
- sgl Sanglechi-Ishkashimi S split into Sanglechi [sgy] and Ishkashimi [isk] 2010-01-18
175
+ rmr Caló S Split into Caló [rmq] and Erromintxela [emx] 2010-01-18
176
+ sgl Sanglechi-Ishkashimi S Split into Sanglechi [sgy] and Ishkashimi [isk] 2010-01-18
177
177
  sul Surigaonon S Split into Tandaganon [tgn] and Surigaonon [sgd] (new identifier) 2010-01-18
178
178
  sum Sumo-Mayangna S Split into Mayangna [yan] and Ulwa [ulw] 2010-01-18
179
179
  tnf Tangshewi M prs 2010-01-18
180
180
  wgw Wagawaga S Split into Yaleba [ylb] and Wagawaga [wgb] (new identifier) 2010-01-18
181
181
  ayx Ayi (China) D nun 2011-05-18
182
- bjq Southern Betsimisaraka Malagasy S split into Southern Betsimisaraka [bzc] and Tesaka Malagasy [tkg] 2011-05-18
182
+ bjq Southern Betsimisaraka Malagasy S Split into Southern Betsimisaraka [bzc] and Tesaka Malagasy [tkg] 2011-05-18
183
183
  dha Dhanwar (India) N 2011-05-18
184
- dkl Kolum So Dogon S split into Ampari Dogon [aqd] and Mombo Dogon [dmb] 2011-05-18
184
+ dkl Kolum So Dogon S Split into Ampari Dogon [aqd] and Mombo Dogon [dmb] 2011-05-18
185
185
  mja Mahei N 2011-05-18
186
- nbf Naxi S split into Naxi [nxq] and Narua [nru] 2011-05-18
186
+ nbf Naxi S Split into Naxi [nxq] and Narua [nru] 2011-05-18
187
187
  noo Nootka S Split into [dtd] Ditidaht and [nuk] Nuu-chah-nulth 2011-05-18
188
188
  tie Tingal M ras 2011-05-18
189
189
  tkk Takpa D twm 2011-05-18
@@ -195,15 +195,14 @@ dap Nisi (India) S Split into Nyishi [njz] and Tagin [tgj] 2012-02-03
195
195
  dwl Walo Kumbe Dogon S Split into Dogon, Bankan Tey (Walo) [dbw] and Dogon, Ben Tey (Beni) [dbt] 2012-02-03
196
196
  elp Elpaputih N 2012-02-03
197
197
  gbc Garawa S Split into Garrwa [wrk] and Wanyi [wny] 2012-02-03
198
- gio Gelao S Split into Qau [gqu] and A'ou [aou] with some going to Green Gelao [gig], some to Red Gelao [gir], and some to White Gelao [giw] 2012-02-03
198
+ gio Gelao S Split into Qau [gqu] and A'ou [aou] 2012-02-03
199
199
  hrr Horuru M jal 2012-02-03
200
200
  ibi Ibilo M opa 2012-02-03
201
- jar Jarawa (Nigeria) S split into Gwak [jgk] and Bankal [jjr] 2012-02-03
202
- kdv Kado S split into Kadu [zkd] and Kanan [zkn] 2012-02-03
201
+ jar Jarawa (Nigeria) S Split into Gwak [jgk] and Bankal [jjr] 2012-02-03
202
+ kdv Kado S Split into Kadu [zkd] and Kanan [zkn] 2012-02-03
203
203
  kgh Upper Tanudan Kalinga M kml 2012-02-03
204
204
  kpp Paku Karen S Split into Paku Karen [jkp] and Mobwa Karen [jkm] 2012-02-03
205
205
  kzh Kenuzi-Dongola S Split into Andaandi (Dongolawi) [dgl] and Kenzi (Mattoki) [xnz] 2012-02-03
206
- lcq Luhu M ppr 2012-02-03
207
206
  mgx Omati S Split into Barikewa [jbk] and Mouwase [jmw] 2012-02-03
208
207
  nln Durango Nahuatl S Split into Eastern Durango Nahuatl [azd] and Western Durango Nahuatl [azn] 2012-02-03
209
208
  pbz Palu N 2012-02-03
@@ -248,7 +247,6 @@ aue ǂKxʼauǁʼein D ktz 2015-01-12
248
247
  bmy Bemba (Democratic Republic of Congo) N 2015-01-12
249
248
  bxx Borna (Democratic Republic of Congo) N 2015-01-12
250
249
  byy Buya N 2015-01-12
251
- dzd Daza N 2015-01-12
252
250
  gfx Mangetti Dune ǃXung M vaj 2015-01-12
253
251
  gti Gbati-ri M nyc 2015-01-12
254
252
  ime Imeraguen N 2015-01-12
@@ -265,7 +263,6 @@ sgo Songa N 2015-01-12
265
263
  thx The D oyb 2015-01-12
266
264
  tsf Southwestern Tamang M taj 2015-01-12
267
265
  uok Uokha M ema 2015-01-12
268
- xsj Subi D suj 2015-01-12
269
266
  yds Yiddish Sign Language N 2015-01-12
270
267
  ymt Mator-Taygi-Karagas D mtm 2015-01-12
271
268
  ynh Yangho N 2015-01-12
@@ -276,9 +273,9 @@ cbh Cagua N 2016-01-15
276
273
  coy Coyaima M pij 2016-01-15
277
274
  cqu Chilean Quechua M quh 2016-01-15
278
275
  cum Cumeral N 2016-01-15
279
- duj Dhuwal S split into [dwu] Dhuwal and [dwy] Dhuwaya 2016-01-15
276
+ duj Dhuwal S Split into [dwu] Dhuwal and [dwy] Dhuwaya 2016-01-15
280
277
  ggn Eastern Gurung M gvr 2016-01-15
281
- ggo Southern Gondi S split into [esg] Aheri Gondi and [wsg] Adilabad Gondi 2016-01-15
278
+ ggo Southern Gondi S Split into [esg] Aheri Gondi and [wsg] Adilabad Gondi 2016-01-15
282
279
  guv Gey M duz 2016-01-15
283
280
  iap Iapama N 2016-01-15
284
281
  ill Iranun S Split into Iranun (Philippines) [ilp] and Iranun (Malaysia) [ilm] 2016-01-15
@@ -349,7 +346,7 @@ plp Palpa N 2020-01-23
349
346
  sdm Semandang S Split into Semandang [sdq], Beginci [ebc] and Gerai [gef] 2020-01-23
350
347
  tbb Tapeba N 2020-01-23
351
348
  xrq Karranga M dmw 2020-01-23
352
- xtz Tasmanian S Split into [xpv] Northern Tasman, [xph] North Midlands Tasman, [xpb] Northeastern Tasman, [xpd] Oyster Bay Tasmanian, [xpf] Southeast Tasma, [xpx] Southwestern Tasman, [xpw] Northwestern Tasman., [xpl] Port Sorell Tasman. and [xpz] Bruny Island Tasman. 2020-01-23
349
+ xtz Tasmanian S Split into [xpv] Northern Tasman, [xph] North Midlands Tasman, [xpb] Northeastern Tasman, [xpd] Oyster Bay Tasman, [xpf] Southeast Tasman, [xpx] Southwestern Tasman, [xpw] Northwestern Tasman, [xpl] Port Sorell Tasman, and [xpz] Bruny Island Tasman 2020-01-23
353
350
  zir Ziriya D scv 2020-01-23
354
351
  thw Thudam M ola 2020-03-19
355
352
  bic Bikaru M bir 2021-01-15
@@ -361,7 +358,7 @@ ngo Ngoni S Split into Ngoni (Tanzania) [xnj] and Ngoni (Mozambique) [xnq] 2021
361
358
  pat Papitalai M kxr 2021-01-15
362
359
  vki Ija-Zuba S Split into Koro Nulu [vkn] and Koro Zuba [vkz] 2021-01-15
363
360
  wra Warapu S Split into Bauni [bpe], Uni [uni] and Bouni [suo] 2021-01-15
364
- ajt Judeo-Tunisian Arabic D aeb 2022-01-20
361
+ ajt Judeo-Tunisian Arabic M aeb 2022-01-20
365
362
  cug Chungmboko S Split into Chung [cnq] and Mbuk [bpc] 2022-01-20
366
363
  lak Laka (Nigeria) M ksp 2022-01-20
367
364
  lno Lango (South Sudan) S Split into Lango [lgo], Imotong [imt], Logir [lqr] and Okolie [oie] 2022-01-20
@@ -373,7 +370,9 @@ wrd Warduji N 2022-01-20
373
370
  wya Wyandot S Split into Wyandot [wyn] and Wendat [wdt] 2022-01-20
374
371
  ajp South Levantine Arabic M apc 2023-01-20
375
372
  kgm Karipúna D plu 2023-01-20
373
+ ksa Shuwa-Zamani S Split into [rsw] Rishiwa and [izm] Kizamani 2023-01-20
376
374
  nom Nocamán M cbr 2023-01-20
375
+ plj Polci S Split into Pesse [pze], Nyamzak-Dir-Mbarimi [nzr], Zul [zlu], and Buli [uly] 2023-01-20
377
376
  pmk Pamlico M crr 2023-01-20
378
377
  prp Parsi M guj 2023-01-20
379
378
  slq Salchuq N 2023-01-20
@@ -383,7 +382,6 @@ tpw Tupí D tpn 2023-01-20
383
382
  xss Assan M zko 2023-01-20
384
383
  zkb Koibal D kjh 2023-01-20
385
384
  zua Zeem S Split into [tvi] Tulai, [dyr] Dyarim, [dsk] Dokshi, [cxh] Cha'ari, and [zem] Zeem 2023-01-20
386
- ksa Shuwa-Zamani S Split into [rsw] Rishiwa and [izm] Kizamani 2023-01-23
387
- plj Polci S Split into Pesse [pze], Nyamzak-Dir-Mbarimi [nzr], Zul [zlu], and Buli [uly] 2023-01-23
388
- dek Dek D sqm 2024-09-09
389
- nte Nathembo M eko 2024-09-09
385
+ dek Dek D sqm 2024-10-15
386
+ nte Nathembo M eko 2024-10-15
387
+ yol Yola M enm 2025-10-15
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import datetime
6
6
  from dataclasses import dataclass
7
7
 
8
- from typing import Dict, List, Union, Set
8
+ from typing import NoReturn
9
9
 
10
10
  from ._data import (
11
11
  _PART3_TO_CODES,
@@ -26,6 +26,9 @@ from ._data import (
26
26
  )
27
27
 
28
28
 
29
+ _STRING_CLEANING_FUNCS = [lambda x: x.lower(), lambda x: x.title()]
30
+
31
+
29
32
  class LanguageNotFoundError(Exception):
30
33
  pass
31
34
 
@@ -67,31 +70,26 @@ class Language:
67
70
 
68
71
  # From the "codes" table
69
72
  part3: str
70
- # Although Union[..., None] and Optional[...] are equivalent, I prefer Union.
71
- # Optional simply doesn't sound right, as it would imply that the attribute in
72
- # question is optional, which it's not.
73
- # When support for Python 3.9 is dropped, we will switch to the pipe syntax
74
- # for `... | None`.
75
- part2b: Union[str, None]
76
- part2t: Union[str, None]
77
- part1: Union[str, None]
73
+ part2b: str | None
74
+ part2t: str | None
75
+ part1: str | None
78
76
  scope: str
79
- type: Union[str, None]
77
+ type: str | None
80
78
  status: str
81
79
  name: str
82
- comment: Union[str, None]
80
+ comment: str | None
83
81
 
84
82
  # From the "name_index" table
85
- other_names: Union[List[Name], None]
83
+ other_names: list[Name] | None
86
84
 
87
85
  # From the "macrolanguages" table
88
- macrolanguage: Union[str, None]
86
+ macrolanguage: str | None
89
87
 
90
88
  # From the "retirements" table
91
- retire_reason: Union[str, None]
92
- retire_change_to: Union[str, None]
93
- retire_remedy: Union[str, None]
94
- retire_date: Union[datetime.date, None]
89
+ retire_reason: str | None
90
+ retire_change_to: str | None
91
+ retire_remedy: str | None
92
+ retire_date: datetime.date | None
95
93
 
96
94
  def __hash__(self) -> int:
97
95
  return hash(self.part3)
@@ -100,13 +98,19 @@ class Language:
100
98
  return isinstance(other, Language) and self.part3 == other.part3
101
99
 
102
100
  @classmethod
103
- def match(cls, user_input: str, /) -> Language:
101
+ def match(cls, user_input: str, /, *, strict_case: bool = True) -> Language:
104
102
  """Return a ``Language`` instance by matching on the user input.
105
103
 
106
104
  Parameters
107
105
  ----------
108
106
  user_input : str
109
107
  A language code or name.
108
+ strict_case : bool, optional
109
+ Defaults to ``True``, for enforcing strict case sensitivity.
110
+ If ``False`` and if ``user_input`` doesn't find a match,
111
+ further match attempts will be made with the all-lowercase version of
112
+ ``user_input`` (``"foobar"``) and the title-case version of ``user_input``
113
+ (``"Foobar"``).
110
114
 
111
115
  Returns
112
116
  -------
@@ -130,7 +134,7 @@ class Language:
130
134
  # Order of columns to query the data tables.
131
135
  # Bias towards (and therefore prioritize) the user input being
132
136
  # a language code rather than a language name.
133
- query_order: List[_COLUMN_TYPE] = [
137
+ query_order: list[_COLUMN_TYPE] = [
134
138
  _CodesColumn.ID,
135
139
  _CodesColumn.PART2B,
136
140
  _CodesColumn.PART2T,
@@ -140,42 +144,48 @@ class Language:
140
144
  _NameIndexColumn.PRINT_NAME,
141
145
  _NameIndexColumn.INVERTED_NAME,
142
146
  ]
143
- return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order)]
147
+ return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order, strict_case)]
144
148
 
145
149
  @classmethod
146
150
  def from_part3(cls, user_input: str, /) -> Language:
147
151
  """Return a ``Language`` instance from an ISO 639-3 code."""
148
152
  return _PART3_TO_LANGUAGES[
149
- _get_part3(user_input, [_CodesColumn.ID, _RetirementsColumn.ID])
153
+ _get_part3_exact(user_input, [_CodesColumn.ID, _RetirementsColumn.ID])
150
154
  ]
151
155
 
152
156
  @classmethod
153
157
  def from_part2b(cls, user_input: str, /) -> Language:
154
158
  """Return a ``Language`` instance from an ISO 639-2 (bibliographic) code."""
155
- return _PART3_TO_LANGUAGES[_get_part3(user_input, [_CodesColumn.PART2B])]
159
+ return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART2B])]
156
160
 
157
161
  @classmethod
158
162
  def from_part2t(cls, user_input: str, /) -> Language:
159
163
  """Return a ``Language`` instance from an ISO 639-2 (terminological) code."""
160
- return _PART3_TO_LANGUAGES[_get_part3(user_input, [_CodesColumn.PART2T])]
164
+ return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART2T])]
161
165
 
162
166
  @classmethod
163
167
  def from_part1(cls, user_input: str, /) -> Language:
164
168
  """Return a ``Language`` instance from an ISO 639-1 code."""
165
- return _PART3_TO_LANGUAGES[_get_part3(user_input, [_CodesColumn.PART1])]
169
+ return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART1])]
166
170
 
167
171
  @classmethod
168
172
  def from_name(cls, user_input: str, /) -> Language:
169
173
  """Return a ``Language`` instance from an ISO 639-3 reference language name."""
170
- query_order: List[_COLUMN_TYPE] = [
174
+ query_order: list[_COLUMN_TYPE] = [
171
175
  _CodesColumn.REF_NAME,
172
176
  _NameIndexColumn.PRINT_NAME,
173
177
  _NameIndexColumn.INVERTED_NAME,
174
178
  ]
175
- return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order)]
179
+ return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, query_order)]
180
+
176
181
 
182
+ def _raise_language_not_found_error(user_input: str) -> NoReturn:
183
+ raise LanguageNotFoundError(f"{user_input!r} isn't an ISO language code or name")
177
184
 
178
- def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
185
+
186
+ def _get_part3(
187
+ user_input: str, query_order: list[_COLUMN_TYPE], strict_case: bool = True
188
+ ) -> str:
179
189
  """Get the part 3 code of a language.
180
190
 
181
191
  Parameters
@@ -184,6 +194,53 @@ def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
184
194
  The user-provided language code or name.
185
195
  query_order : List[_COLUMN_TYPE]
186
196
  A list of columns to specify query order.
197
+ strict_case : bool, optional
198
+ Defaults to ``True``, for enforcing strict case sensitivity.
199
+ If ``False`` and if ``user_input`` doesn't find a match,
200
+ further match attempts will be made with the all-lowercase version of
201
+ ``user_input`` (``"foobar"``) and the title-case version of ``user_input``
202
+ (``"Foobar"``).
203
+
204
+ Returns
205
+ -------
206
+ str
207
+
208
+ Raises
209
+ ------
210
+ LanguageNotFoundError
211
+ If `part3` isn't a language name or code
212
+ """
213
+ try:
214
+ return _get_part3_exact(user_input, query_order)
215
+ except LanguageNotFoundError as e:
216
+ if strict_case:
217
+ raise e
218
+ else:
219
+ for func in _STRING_CLEANING_FUNCS:
220
+ try:
221
+ return _get_part3_exact(func(user_input), query_order, user_input)
222
+ except LanguageNotFoundError:
223
+ continue
224
+ else:
225
+ _raise_language_not_found_error(user_input)
226
+
227
+
228
+ def _get_part3_exact(
229
+ user_input: str,
230
+ query_order: list[_COLUMN_TYPE],
231
+ original_user_input: str | None = None,
232
+ ) -> str:
233
+ """Get the part 3 code of a language.
234
+
235
+ Parameters
236
+ ----------
237
+ user_input : str
238
+ The user-provided language code or name.
239
+ query_order : list[_COLUMN_TYPE]
240
+ A list of columns to specify query order.
241
+ original_user_input : str, optional
242
+ The original user input. Default is `None`.
243
+ This argument is used when the user input has been cleaned.
187
244
 
188
245
  Returns
189
246
  -------
@@ -194,12 +251,12 @@ def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
194
251
  LanguageNotFoundError
195
252
  If `part3` isn't a language name or code
196
253
  """
197
- part3: Union[str, None] = None
254
+ user_input = user_input.strip()
255
+ part3: str | None = None
198
256
  for column in query_order:
199
257
  if column == _CodesColumn.ID:
200
258
  if user_input in _PART3_TO_CODES:
201
- part3 = user_input
202
- break
259
+ return user_input
203
260
  elif column == _CodesColumn.PART2B:
204
261
  part3 = _PART2B_TO_PART3.get(user_input)
205
262
  elif column == _CodesColumn.PART2T:
@@ -208,8 +265,7 @@ def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
208
265
  part3 = _PART1_TO_PART3.get(user_input)
209
266
  elif column == _RetirementsColumn.ID:
210
267
  if user_input in _PART3_TO_RETIREMENTS:
211
- part3 = user_input
212
- break
268
+ return user_input
213
269
  elif column == _CodesColumn.REF_NAME:
214
270
  part3 = _REF_NAME_TO_PART3.get(user_input)
215
271
  elif column == _NameIndexColumn.PRINT_NAME:
@@ -222,9 +278,7 @@ def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
222
278
  break
223
279
 
224
280
  if part3 is None:
225
- raise LanguageNotFoundError(
226
- f"{user_input!r} isn't an ISO language code or name"
227
- )
281
+ _raise_language_not_found_error(original_user_input or user_input)
228
282
 
229
283
  return part3
230
284
 
@@ -251,7 +305,7 @@ def _get_language(part3: str) -> Language:
251
305
  else from_retirements[_RetirementsColumn.REF_NAME] # type: ignore
252
306
  )
253
307
 
254
- other_names: Union[List[Name], None] = []
308
+ other_names: list[Name] | None = []
255
309
  for row in _PART3_TO_NAME_INDEX.get(part3, []):
256
310
  p, i = row[_NameIndexColumn.PRINT_NAME], row[_NameIndexColumn.INVERTED_NAME]
257
311
  if not ref_name == p == i:
@@ -314,7 +368,7 @@ def _get_language(part3: str) -> Language:
314
368
  return language
315
369
 
316
370
 
317
- def _get_all_languages() -> Dict[str, Language]:
371
+ def _get_all_languages() -> dict[str, Language]:
318
372
  languages = {}
319
373
  for part3 in _PART3_TO_CODES:
320
374
  languages[part3] = _get_language(part3)
@@ -323,6 +377,6 @@ def _get_all_languages() -> Dict[str, Language]:
323
377
  return languages
324
378
 
325
379
 
326
- _PART3_TO_LANGUAGES: Dict[str, Language] = _get_all_languages()
380
+ _PART3_TO_LANGUAGES: dict[str, Language] = _get_all_languages()
327
381
 
328
- ALL_LANGUAGES: Set[Language] = set(_PART3_TO_LANGUAGES.values())
382
+ ALL_LANGUAGES: set[Language] = set(_PART3_TO_LANGUAGES.values())
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: python-iso639
3
- Version: 2025.2.8
3
+ Version: 2025.11.11
4
4
  Summary: ISO 639 language codes, names, and other associated information
5
5
  Author-email: "Jackson L. Lee" <jacksonlunlee@gmail.com>
6
6
  License: Apache 2.0
@@ -15,26 +15,27 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Text Processing
24
24
  Classifier: Topic :: Text Processing :: General
25
25
  Classifier: Topic :: Text Processing :: Indexing
26
26
  Classifier: Topic :: Text Processing :: Linguistic
27
- Requires-Python: >=3.9
27
+ Requires-Python: >=3.10
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE.txt
30
30
  Provides-Extra: dev
31
- Requires-Dist: black==25.1.0; extra == "dev"
32
- Requires-Dist: build==1.2.2; extra == "dev"
33
- Requires-Dist: flake8==7.1.1; extra == "dev"
34
- Requires-Dist: mypy==1.15.0; extra == "dev"
35
- Requires-Dist: pytest==8.3.4; extra == "dev"
36
- Requires-Dist: requests==2.32.3; extra == "dev"
37
- Requires-Dist: twine==6.1.0; extra == "dev"
31
+ Requires-Dist: black==25.11.0; extra == "dev"
32
+ Requires-Dist: build==1.3.0; extra == "dev"
33
+ Requires-Dist: flake8==7.3.0; extra == "dev"
34
+ Requires-Dist: mypy==1.18.2; extra == "dev"
35
+ Requires-Dist: pytest==9.0.0; extra == "dev"
36
+ Requires-Dist: requests==2.32.5; extra == "dev"
37
+ Requires-Dist: twine==6.2.0; extra == "dev"
38
+ Dynamic: license-file
38
39
 
39
40
  # python-iso639
40
41
 
@@ -83,13 +84,13 @@ Create a `Language` instance by one of the class methods.
83
84
  Language(part3='fra', part2b='fre', part2t='fra', part1='fr', scope='I', type='L', name='French', comment=None, other_names=None, macrolanguage=None, retire_reason=None, retire_change_to=None, retire_remedy=None, retire_date=None)
84
85
  ```
85
86
 
86
- Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.2, Apple M1 Pro)
87
+ Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.3.1, Apple M1 Pro)
87
88
 
88
89
  ```python
89
90
  In [1]: import iso639
90
91
 
91
92
  In [2]: %timeit iso639.Language.from_part3("fra")
92
- 220 ns ± 0.658 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
93
+ 217 ns ± 0.139 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
93
94
  ```
94
95
 
95
96
  #### From Another ISO 639 Code Set or a Reference Name
@@ -154,11 +155,27 @@ Use the `match` classmethod:
154
155
  True
155
156
  ```
156
157
 
158
+ By default, the classmethod `match` supports case-insensitive matching
159
+ and ignores leading/trailing whitespace.
160
+ To enforce exact matching instead, pass in `exact=True`:
161
+
162
+ ```python
163
+ >>> lang5 = iso639.Language.match('FRA')
164
+ >>> lang6 = iso639.Language.match('fra ')
165
+ >>> lang7 = iso639.Language.match('french')
166
+ >>> lang4 == lang5 == lang6 == lang7
167
+ True
168
+ >>> iso639.Language.match("french", exact=True)
169
+ Traceback (most recent call last):
170
+ File "<stdin>", line 1, in <module>
171
+ LanguageNotFoundError: 'french' isn't an ISO language code or name
172
+ ```
173
+
157
174
  The classmethod `match` is particularly useful for consistently
158
175
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
159
176
 
160
177
  ```python
161
- >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3
178
+ >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3 == lang5.part3 == lang6.part3 == lang7.part3
162
179
  True
163
180
  ```
164
181
 
@@ -261,7 +278,6 @@ Beyond that, the precise order in matching is as follows:
261
278
  * ISO 639-3 alternative language names (the "print" ones)
262
279
  * ISO 639-3 alternative language names (the "inverted" ones)
263
280
 
264
- Only exact matching is done (there's no fuzzy string matching of any sort).
265
281
  As soon as a match is found, `Language.match` returns a `Language` instance.
266
282
  If there isn't a match, a `LanguageNotFoundError` is raised.
267
283
 
@@ -287,7 +303,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
287
303
  ```python
288
304
  >>> import iso639
289
305
  >>> iso639.DATA_LAST_UPDATED
290
- datetime.date(2025, 1, 15)
306
+ datetime.date(2025, 10, 15)
291
307
  ```
292
308
 
293
309
  * `ALL_LANGUAGES`: The list of all `Language` objects based on the included language code data
@@ -297,7 +313,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
297
313
  >>> type(iso639.ALL_LANGUAGES)
298
314
  <class 'set'>
299
315
  >>> len(iso639.ALL_LANGUAGES)
300
- 8307
316
+ 8311
301
317
  ```
302
318
 
303
319
  ## Links
@@ -0,0 +1,9 @@
1
+
2
+ [dev]
3
+ black==25.11.0
4
+ build==1.3.0
5
+ flake8==7.3.0
6
+ mypy==1.18.2
7
+ pytest==9.0.0
8
+ requests==2.32.5
9
+ twine==6.2.0
@@ -6,19 +6,62 @@ from iso639.language import Name
6
6
  import pytest
7
7
 
8
8
 
9
+ @pytest.mark.parametrize(
10
+ "user_input, strict_case, expected_part3",
11
+ [
12
+ ("fra", True, "fra"),
13
+ (" fra ", True, "fra"),
14
+ ("fra", False, "fra"),
15
+ ("FRA", True, None),
16
+ ("FRA", False, "fra"),
17
+ (" FRA ", False, "fra"),
18
+ ("Fra", True, None),
19
+ ("Fra", False, "fra"),
20
+ (" Fra ", False, "fra"),
21
+ ("French", True, "fra"),
22
+ ("French", False, "fra"),
23
+ ("FRENCH", True, None),
24
+ ("FRENCH", False, "fra"),
25
+ (" FRENCH ", False, "fra"),
26
+ ("french", True, None),
27
+ ("french", False, "fra"),
28
+ (" french ", False, "fra"),
29
+ ("Castilian", True, "spa"),
30
+ ],
31
+ )
32
+ def test_match(user_input, strict_case, expected_part3):
33
+ if expected_part3 is None:
34
+ with pytest.raises(LanguageNotFoundError):
35
+ Language.match(user_input, strict_case=strict_case)
36
+ else:
37
+ actual_part3 = Language.match(user_input, strict_case=strict_case).part3
38
+ assert actual_part3 == expected_part3
39
+
40
+
9
41
  @pytest.mark.parametrize(
10
42
  "user_input, expected_part3",
11
43
  [
12
44
  ("fra", "fra"),
13
- ("fre", "fra"),
14
- ("fr", "fra"),
45
+ (" fra ", "fra"),
46
+ ("FRA", None),
47
+ (" FRA ", None),
48
+ ("Fra", None),
49
+ (" Fra ", None),
15
50
  ("French", "fra"),
51
+ ("FRENCH", None),
52
+ (" FRENCH ", None),
53
+ ("french", None),
54
+ (" french ", None),
16
55
  ("Castilian", "spa"),
17
56
  ],
18
57
  )
19
- def test_match(user_input, expected_part3):
20
- actual_part3 = Language.match(user_input).part3
21
- assert actual_part3 == expected_part3
58
+ def test_match_no_strict_case_kwarg(user_input, expected_part3):
59
+ if expected_part3 is None:
60
+ with pytest.raises(LanguageNotFoundError):
61
+ Language.match(user_input)
62
+ else:
63
+ actual_part3 = Language.match(user_input).part3
64
+ assert actual_part3 == expected_part3
22
65
 
23
66
 
24
67
  def test_name():
@@ -89,12 +132,15 @@ def test_invalid_inputs():
89
132
 
90
133
 
91
134
  def test_data_last_updated():
92
- assert DATA_LAST_UPDATED == datetime.date(2025, 1, 15), "Need to update README.md"
135
+ assert DATA_LAST_UPDATED == datetime.date(2025, 10, 15), "Need to update README.md"
93
136
 
94
137
 
95
138
  def test_all_languages():
96
139
  assert type(ALL_LANGUAGES) is set
97
- assert len(ALL_LANGUAGES) == 8307, "Need to update README.md"
140
+ # Defining `len_all_languages` so that when the assertion fails,
141
+ # the error message is more informative.
142
+ len_all_languages = len(ALL_LANGUAGES)
143
+ assert len_all_languages == 8311, "Need to update README.md"
98
144
  lang = list(ALL_LANGUAGES)[0]
99
145
  assert type(lang) is Language
100
146
 
@@ -1,9 +0,0 @@
1
-
2
- [dev]
3
- black==25.1.0
4
- build==1.2.2
5
- flake8==7.1.1
6
- mypy==1.15.0
7
- pytest==8.3.4
8
- requests==2.32.3
9
- twine==6.1.0