;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [LanguageRecordViewComponent, BrowserAnimationsModule],
+ providers: [provideHttpClient()]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(LanguageRecordViewComponent);
+ component = fixture.componentInstance;
+ component.value = 'en';
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should display language name from code', () => {
+ component.value = 'en';
+ component.ngOnInit();
+ expect(component.languageName).toBe('English');
+ });
+
+ it('should display em dash when value is empty', () => {
+ component.value = null;
+ component.ngOnInit();
+ expect(component.languageName).toBe('—');
+ });
+});
diff --git a/frontend/src/app/components/ui-components/record-view-fields/language/language.component.ts b/frontend/src/app/components/ui-components/record-view-fields/language/language.component.ts
new file mode 100644
index 000000000..656ae30cc
--- /dev/null
+++ b/frontend/src/app/components/ui-components/record-view-fields/language/language.component.ts
@@ -0,0 +1,51 @@
+import { LANGUAGES, getLanguageFlag } from '../../../../consts/languages';
+import { Component, Injectable, OnInit } from '@angular/core';
+
+import { BaseRecordViewFieldComponent } from '../base-record-view-field/base-record-view-field.component';
+import { CommonModule } from '@angular/common';
+
+@Injectable()
+@Component({
+ selector: 'app-language-record-view',
+ templateUrl: './language.component.html',
+ styleUrls: ['../base-record-view-field/base-record-view-field.component.css', './language.component.css'],
+ imports: [CommonModule]
+})
+export class LanguageRecordViewComponent extends BaseRecordViewFieldComponent implements OnInit {
+ static type = 'language';
+
+ public languageName: string = '';
+ public languageFlag: string = '';
+ public showFlag: boolean = true;
+
+ ngOnInit(): void {
+ this.parseWidgetParams();
+
+ if (this.value) {
+ const language = LANGUAGES.find(l => l.code.toLowerCase() === this.value.toLowerCase());
+ this.languageName = language ? language.name : this.value;
+ if (language) {
+ this.languageFlag = getLanguageFlag(language);
+ }
+ } else {
+ this.languageName = '—';
+ this.languageFlag = '';
+ }
+ }
+
+ private parseWidgetParams(): void {
+ if (this.widgetStructure?.widget_params) {
+ try {
+ const params = typeof this.widgetStructure.widget_params === 'string'
+ ? JSON.parse(this.widgetStructure.widget_params)
+ : this.widgetStructure.widget_params;
+
+ if (params.show_flag !== undefined) {
+ this.showFlag = params.show_flag;
+ }
+ } catch (e) {
+ console.error('Error parsing language widget params:', e);
+ }
+ }
+ }
+}
diff --git a/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.css b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.css
index 27736edaf..5b2ca4ec5 100644
--- a/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.css
+++ b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.css
@@ -1,11 +1,25 @@
.markdown-container {
padding: 16px;
- background: #f9f9f9;
border-radius: 4px;
- border: 1px solid #e0e0e0;
min-height: 100px;
}
+@media (prefers-color-scheme: light) {
+ .markdown-container {
+ background: #f9f9f9;
+ border: 1px solid #e0e0e0;
+ color: rgba(0, 0, 0, 0.64);
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .markdown-container {
+ background: #2a2a2a;
+ border: 1px solid #404040;
+ color: rgba(255, 255, 255, 0.64);
+ }
+}
+
.markdown-container ::ng-deep h1,
.markdown-container ::ng-deep h2,
.markdown-container ::ng-deep h3,
@@ -27,22 +41,56 @@
}
.markdown-container ::ng-deep code {
- background: #f5f5f5;
padding: 2px 4px;
border-radius: 3px;
font-family: monospace;
}
+@media (prefers-color-scheme: light) {
+ .markdown-container ::ng-deep code {
+ background: #f5f5f5;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .markdown-container ::ng-deep code {
+ background: #3a3a3a;
+ }
+}
+
.markdown-container ::ng-deep pre {
- background: #f5f5f5;
padding: 12px;
border-radius: 4px;
overflow-x: auto;
}
+@media (prefers-color-scheme: light) {
+ .markdown-container ::ng-deep pre {
+ background: #f5f5f5;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .markdown-container ::ng-deep pre {
+ background: #3a3a3a;
+ }
+}
+
.markdown-container ::ng-deep blockquote {
- border-left: 4px solid #ddd;
padding-left: 16px;
margin-left: 0;
- color: #666;
+}
+
+@media (prefers-color-scheme: light) {
+ .markdown-container ::ng-deep blockquote {
+ border-left: 4px solid #ddd;
+ color: #666;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .markdown-container ::ng-deep blockquote {
+ border-left: 4px solid #555;
+ color: #aaa;
+ }
}
diff --git a/frontend/src/app/components/ui-components/table-display-fields/language/language.component.css b/frontend/src/app/components/ui-components/table-display-fields/language/language.component.css
new file mode 100644
index 000000000..02b00a9c4
--- /dev/null
+++ b/frontend/src/app/components/ui-components/table-display-fields/language/language.component.css
@@ -0,0 +1,27 @@
+.field-wrapper {
+ display: flex;
+ align-items: center;
+}
+
+.value-wrapper {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 300px;
+ display: flex;
+ align-items: center;
+}
+
+.language-flag {
+ margin-right: 6px;
+ font-size: 1.2em;
+}
+
+.copy-button {
+ visibility: hidden;
+ margin-left: 4px;
+}
+
+.field-wrapper:hover .copy-button {
+ visibility: visible;
+}
diff --git a/frontend/src/app/components/ui-components/table-display-fields/language/language.component.html b/frontend/src/app/components/ui-components/table-display-fields/language/language.component.html
new file mode 100644
index 000000000..85f344d1e
--- /dev/null
+++ b/frontend/src/app/components/ui-components/table-display-fields/language/language.component.html
@@ -0,0 +1,14 @@
+
+
+ {{ languageFlag }}
+ {{ languageName }}
+
+
+
diff --git a/frontend/src/app/components/ui-components/table-display-fields/language/language.component.spec.ts b/frontend/src/app/components/ui-components/table-display-fields/language/language.component.spec.ts
new file mode 100644
index 000000000..f6d5f0e89
--- /dev/null
+++ b/frontend/src/app/components/ui-components/table-display-fields/language/language.component.spec.ts
@@ -0,0 +1,46 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LanguageDisplayComponent } from './language.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { provideHttpClient } from '@angular/common/http';
+
+describe('LanguageDisplayComponent', () => {
+ let component: LanguageDisplayComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [LanguageDisplayComponent, BrowserAnimationsModule],
+ providers: [provideHttpClient()]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(LanguageDisplayComponent);
+ component = fixture.componentInstance;
+ component.value = 'en';
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should display language name from code', () => {
+ component.value = 'fr';
+ component.ngOnInit();
+ expect(component.languageName).toBe('French');
+ });
+
+ it('should display em dash when value is empty', () => {
+ component.value = null;
+ component.ngOnInit();
+ expect(component.languageName).toBe('—');
+ });
+
+ it('should respect show_flag widget parameter', () => {
+ component.widgetStructure = {
+ widget_params: { show_flag: false }
+ } as any;
+ component.ngOnInit();
+ expect(component.showFlag).toBe(false);
+ });
+});
diff --git a/frontend/src/app/components/ui-components/table-display-fields/language/language.component.ts b/frontend/src/app/components/ui-components/table-display-fields/language/language.component.ts
new file mode 100644
index 000000000..ae7d5e2cf
--- /dev/null
+++ b/frontend/src/app/components/ui-components/table-display-fields/language/language.component.ts
@@ -0,0 +1,54 @@
+import { LANGUAGES, getLanguageFlag } from '../../../../consts/languages';
+import { Component, OnInit } from '@angular/core';
+
+import { BaseTableDisplayFieldComponent } from '../base-table-display-field/base-table-display-field.component';
+import { ClipboardModule } from '@angular/cdk/clipboard';
+import { CommonModule } from '@angular/common';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTooltipModule } from '@angular/material/tooltip';
+
+@Component({
+ selector: 'app-language-display',
+ templateUrl: './language.component.html',
+ styleUrls: ['../base-table-display-field/base-table-display-field.component.css', './language.component.css'],
+ imports: [ClipboardModule, MatIconModule, MatButtonModule, MatTooltipModule, CommonModule]
+})
+export class LanguageDisplayComponent extends BaseTableDisplayFieldComponent implements OnInit {
+ static type = 'language';
+
+ public languageName: string = '';
+ public languageFlag: string = '';
+ public showFlag: boolean = true;
+
+ ngOnInit(): void {
+ this.parseWidgetParams();
+
+ if (this.value) {
+ const language = LANGUAGES.find(l => l.code.toLowerCase() === this.value.toLowerCase());
+ this.languageName = language ? language.name : this.value;
+ if (language) {
+ this.languageFlag = getLanguageFlag(language);
+ }
+ } else {
+ this.languageName = '—';
+ this.languageFlag = '';
+ }
+ }
+
+ private parseWidgetParams(): void {
+ if (this.widgetStructure?.widget_params) {
+ try {
+ const params = typeof this.widgetStructure.widget_params === 'string'
+ ? JSON.parse(this.widgetStructure.widget_params)
+ : this.widgetStructure.widget_params;
+
+ if (params.show_flag !== undefined) {
+ this.showFlag = params.show_flag;
+ }
+ } catch (e) {
+ console.error('Error parsing language widget params:', e);
+ }
+ }
+ }
+}
diff --git a/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.css b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.css
index 6c6f41777..03a09e89e 100644
--- a/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.css
+++ b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.css
@@ -32,9 +32,20 @@
}
.markdown-preview ::ng-deep code {
- background: #f5f5f5;
padding: 1px 3px;
border-radius: 2px;
font-family: monospace;
font-size: 0.9em;
}
+
+@media (prefers-color-scheme: light) {
+ .markdown-preview ::ng-deep code {
+ background: #f5f5f5;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .markdown-preview ::ng-deep code {
+ background: #3a3a3a;
+ }
+}
diff --git a/frontend/src/app/consts/languages.ts b/frontend/src/app/consts/languages.ts
new file mode 100644
index 000000000..a198a147f
--- /dev/null
+++ b/frontend/src/app/consts/languages.ts
@@ -0,0 +1,201 @@
+export interface Language {
+ code: string;
+ name: string;
+ nativeName?: string;
+ flagCountryCode?: string; // ISO 3166-1 alpha-2 country code for flag emoji
+}
+
+export function getLanguageFlag(language: Language): string {
+ if (!language.flagCountryCode || language.flagCountryCode.length !== 2) return '';
+ const codePoints = language.flagCountryCode
+ .toUpperCase()
+ .split('')
+ .map(char => 127397 + char.charCodeAt(0));
+ return String.fromCodePoint(...codePoints);
+}
+
+export const LANGUAGES: Language[] = [
+ { code: 'aa', name: 'Afar', nativeName: 'Afaraf', flagCountryCode: 'ET' },
+ { code: 'ab', name: 'Abkhazian', nativeName: 'аҧсуа бызшәа', flagCountryCode: 'GE' },
+ { code: 'af', name: 'Afrikaans', nativeName: 'Afrikaans', flagCountryCode: 'ZA' },
+ { code: 'ak', name: 'Akan', nativeName: 'Akan', flagCountryCode: 'GH' },
+ { code: 'am', name: 'Amharic', nativeName: 'አማርኛ', flagCountryCode: 'ET' },
+ { code: 'an', name: 'Aragonese', nativeName: 'aragonés', flagCountryCode: 'ES' },
+ { code: 'ar', name: 'Arabic', nativeName: 'العربية', flagCountryCode: 'SA' },
+ { code: 'as', name: 'Assamese', nativeName: 'অসমীয়া', flagCountryCode: 'IN' },
+ { code: 'av', name: 'Avaric', nativeName: 'авар мацӀ', flagCountryCode: 'RU' },
+ { code: 'ay', name: 'Aymara', nativeName: 'aymar aru', flagCountryCode: 'BO' },
+ { code: 'az', name: 'Azerbaijani', nativeName: 'azərbaycan dili', flagCountryCode: 'AZ' },
+ { code: 'ba', name: 'Bashkir', nativeName: 'башҡорт теле', flagCountryCode: 'RU' },
+ { code: 'be', name: 'Belarusian', nativeName: 'беларуская мова', flagCountryCode: 'BY' },
+ { code: 'bg', name: 'Bulgarian', nativeName: 'български език', flagCountryCode: 'BG' },
+ { code: 'bh', name: 'Bihari languages', nativeName: 'भोजपुरी', flagCountryCode: 'IN' },
+ { code: 'bi', name: 'Bislama', nativeName: 'Bislama', flagCountryCode: 'VU' },
+ { code: 'bm', name: 'Bambara', nativeName: 'bamanankan', flagCountryCode: 'ML' },
+ { code: 'bn', name: 'Bengali', nativeName: 'বাংলা', flagCountryCode: 'BD' },
+ { code: 'bo', name: 'Tibetan', nativeName: 'བོད་ཡིག', flagCountryCode: 'CN' },
+ { code: 'br', name: 'Breton', nativeName: 'brezhoneg', flagCountryCode: 'FR' },
+ { code: 'bs', name: 'Bosnian', nativeName: 'bosanski jezik', flagCountryCode: 'BA' },
+ { code: 'ca', name: 'Catalan', nativeName: 'català', flagCountryCode: 'ES' },
+ { code: 'ce', name: 'Chechen', nativeName: 'нохчийн мott', flagCountryCode: 'RU' },
+ { code: 'ch', name: 'Chamorro', nativeName: 'Chamoru', flagCountryCode: 'GU' },
+ { code: 'co', name: 'Corsican', nativeName: 'corsu', flagCountryCode: 'FR' },
+ { code: 'cr', name: 'Cree', nativeName: 'ᓀᐦᐃᔭᐍᐏᐣ', flagCountryCode: 'CA' },
+ { code: 'cs', name: 'Czech', nativeName: 'čeština', flagCountryCode: 'CZ' },
+ { code: 'cu', name: 'Church Slavonic', nativeName: 'ѩзыкъ словѣньскъ', flagCountryCode: 'BG' },
+ { code: 'cv', name: 'Chuvash', nativeName: 'чӑваш чӗлхи', flagCountryCode: 'RU' },
+ { code: 'cy', name: 'Welsh', nativeName: 'Cymraeg', flagCountryCode: 'GB' },
+ { code: 'da', name: 'Danish', nativeName: 'dansk', flagCountryCode: 'DK' },
+ { code: 'de', name: 'German', nativeName: 'Deutsch', flagCountryCode: 'DE' },
+ { code: 'dv', name: 'Divehi', nativeName: 'ދިވެހި', flagCountryCode: 'MV' },
+ { code: 'dz', name: 'Dzongkha', nativeName: 'རྫོང་ཁ', flagCountryCode: 'BT' },
+ { code: 'ee', name: 'Ewe', nativeName: 'Eʋegbe', flagCountryCode: 'GH' },
+ { code: 'el', name: 'Greek', nativeName: 'Ελληνικά', flagCountryCode: 'GR' },
+ { code: 'en', name: 'English', nativeName: 'English', flagCountryCode: 'GB' },
+ { code: 'eo', name: 'Esperanto', nativeName: 'Esperanto', flagCountryCode: 'EU' },
+ { code: 'es', name: 'Spanish', nativeName: 'español', flagCountryCode: 'ES' },
+ { code: 'et', name: 'Estonian', nativeName: 'eesti', flagCountryCode: 'EE' },
+ { code: 'eu', name: 'Basque', nativeName: 'euskara', flagCountryCode: 'ES' },
+ { code: 'fa', name: 'Persian', nativeName: 'فارسی', flagCountryCode: 'IR' },
+ { code: 'ff', name: 'Fulah', nativeName: 'Fulfulde', flagCountryCode: 'SN' },
+ { code: 'fi', name: 'Finnish', nativeName: 'suomi', flagCountryCode: 'FI' },
+ { code: 'fj', name: 'Fijian', nativeName: 'vosa Vakaviti', flagCountryCode: 'FJ' },
+ { code: 'fo', name: 'Faroese', nativeName: 'føroyskt', flagCountryCode: 'FO' },
+ { code: 'fr', name: 'French', nativeName: 'français', flagCountryCode: 'FR' },
+ { code: 'fy', name: 'Western Frisian', nativeName: 'Frysk', flagCountryCode: 'NL' },
+ { code: 'ga', name: 'Irish', nativeName: 'Gaeilge', flagCountryCode: 'IE' },
+ { code: 'gd', name: 'Scottish Gaelic', nativeName: 'Gàidhlig', flagCountryCode: 'GB' },
+ { code: 'gl', name: 'Galician', nativeName: 'galego', flagCountryCode: 'ES' },
+ { code: 'gn', name: 'Guarani', nativeName: "Avañe'ẽ", flagCountryCode: 'PY' },
+ { code: 'gu', name: 'Gujarati', nativeName: 'ગુજરાતી', flagCountryCode: 'IN' },
+ { code: 'gv', name: 'Manx', nativeName: 'Gaelg', flagCountryCode: 'IM' },
+ { code: 'ha', name: 'Hausa', nativeName: 'هَوُسَ', flagCountryCode: 'NG' },
+ { code: 'he', name: 'Hebrew', nativeName: 'עברית', flagCountryCode: 'IL' },
+ { code: 'hi', name: 'Hindi', nativeName: 'हिन्दी', flagCountryCode: 'IN' },
+ { code: 'ho', name: 'Hiri Motu', nativeName: 'Hiri Motu', flagCountryCode: 'PG' },
+ { code: 'hr', name: 'Croatian', nativeName: 'hrvatski jezik', flagCountryCode: 'HR' },
+ { code: 'ht', name: 'Haitian', nativeName: 'Kreyòl ayisyen', flagCountryCode: 'HT' },
+ { code: 'hu', name: 'Hungarian', nativeName: 'magyar', flagCountryCode: 'HU' },
+ { code: 'hy', name: 'Armenian', nativeName: 'Հայերեն', flagCountryCode: 'AM' },
+ { code: 'hz', name: 'Herero', nativeName: 'Otjiherero', flagCountryCode: 'NA' },
+ { code: 'ia', name: 'Interlingua', nativeName: 'Interlingua', flagCountryCode: 'EU' },
+ { code: 'id', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flagCountryCode: 'ID' },
+ { code: 'ie', name: 'Interlingue', nativeName: 'Interlingue', flagCountryCode: 'EU' },
+ { code: 'ig', name: 'Igbo', nativeName: 'Asụsụ Igbo', flagCountryCode: 'NG' },
+ { code: 'ii', name: 'Sichuan Yi', nativeName: 'ꆈꌠ꒿', flagCountryCode: 'CN' },
+ { code: 'ik', name: 'Inupiaq', nativeName: 'Iñupiaq', flagCountryCode: 'US' },
+ { code: 'io', name: 'Ido', nativeName: 'Ido', flagCountryCode: 'EU' },
+ { code: 'is', name: 'Icelandic', nativeName: 'Íslenska', flagCountryCode: 'IS' },
+ { code: 'it', name: 'Italian', nativeName: 'italiano', flagCountryCode: 'IT' },
+ { code: 'iu', name: 'Inuktitut', nativeName: 'ᐃᓄᒃᑎᑐᑦ', flagCountryCode: 'CA' },
+ { code: 'ja', name: 'Japanese', nativeName: '日本語', flagCountryCode: 'JP' },
+ { code: 'jv', name: 'Javanese', nativeName: 'ꦧꦱꦗꦮ', flagCountryCode: 'ID' },
+ { code: 'ka', name: 'Georgian', nativeName: 'ქართული', flagCountryCode: 'GE' },
+ { code: 'kg', name: 'Kongo', nativeName: 'Kikongo', flagCountryCode: 'CD' },
+ { code: 'ki', name: 'Kikuyu', nativeName: 'Gĩkũyũ', flagCountryCode: 'KE' },
+ { code: 'kj', name: 'Kuanyama', nativeName: 'Kuanyama', flagCountryCode: 'AO' },
+ { code: 'kk', name: 'Kazakh', nativeName: 'қазақ тілі', flagCountryCode: 'KZ' },
+ { code: 'kl', name: 'Kalaallisut', nativeName: 'kalaallisut', flagCountryCode: 'GL' },
+ { code: 'km', name: 'Central Khmer', nativeName: 'ខេមរភាសា', flagCountryCode: 'KH' },
+ { code: 'kn', name: 'Kannada', nativeName: 'ಕನ್ನಡ', flagCountryCode: 'IN' },
+ { code: 'ko', name: 'Korean', nativeName: '한국어', flagCountryCode: 'KR' },
+ { code: 'kr', name: 'Kanuri', nativeName: 'Kanuri', flagCountryCode: 'NG' },
+ { code: 'ks', name: 'Kashmiri', nativeName: 'कश्मीरी', flagCountryCode: 'IN' },
+ { code: 'ku', name: 'Kurdish', nativeName: 'Kurdî', flagCountryCode: 'IQ' },
+ { code: 'kv', name: 'Komi', nativeName: 'коми кыв', flagCountryCode: 'RU' },
+ { code: 'kw', name: 'Cornish', nativeName: 'Kernewek', flagCountryCode: 'GB' },
+ { code: 'ky', name: 'Kirghiz', nativeName: 'Кыргызча', flagCountryCode: 'KG' },
+ { code: 'la', name: 'Latin', nativeName: 'latine', flagCountryCode: 'VA' },
+ { code: 'lb', name: 'Luxembourgish', nativeName: 'Lëtzebuergesch', flagCountryCode: 'LU' },
+ { code: 'lg', name: 'Ganda', nativeName: 'Luganda', flagCountryCode: 'UG' },
+ { code: 'li', name: 'Limburgan', nativeName: 'Limburgs', flagCountryCode: 'NL' },
+ { code: 'ln', name: 'Lingala', nativeName: 'Lingála', flagCountryCode: 'CD' },
+ { code: 'lo', name: 'Lao', nativeName: 'ພາສາລາວ', flagCountryCode: 'LA' },
+ { code: 'lt', name: 'Lithuanian', nativeName: 'lietuvių kalba', flagCountryCode: 'LT' },
+ { code: 'lu', name: 'Luba-Katanga', nativeName: 'Tshiluba', flagCountryCode: 'CD' },
+ { code: 'lv', name: 'Latvian', nativeName: 'latviešu valoda', flagCountryCode: 'LV' },
+ { code: 'mg', name: 'Malagasy', nativeName: 'fiteny malagasy', flagCountryCode: 'MG' },
+ { code: 'mh', name: 'Marshallese', nativeName: 'Kajin M̧ajeļ', flagCountryCode: 'MH' },
+ { code: 'mi', name: 'Maori', nativeName: 'te reo Māori', flagCountryCode: 'NZ' },
+ { code: 'mk', name: 'Macedonian', nativeName: 'македонски јазик', flagCountryCode: 'MK' },
+ { code: 'ml', name: 'Malayalam', nativeName: 'മലയാളം', flagCountryCode: 'IN' },
+ { code: 'mn', name: 'Mongolian', nativeName: 'Монгол хэл', flagCountryCode: 'MN' },
+ { code: 'mr', name: 'Marathi', nativeName: 'मराठी', flagCountryCode: 'IN' },
+ { code: 'ms', name: 'Malay', nativeName: 'Bahasa Melayu', flagCountryCode: 'MY' },
+ { code: 'mt', name: 'Maltese', nativeName: 'Malti', flagCountryCode: 'MT' },
+ { code: 'my', name: 'Burmese', nativeName: 'ဗမာစာ', flagCountryCode: 'MM' },
+ { code: 'na', name: 'Nauru', nativeName: 'Dorerin Naoero', flagCountryCode: 'NR' },
+ { code: 'nb', name: 'Norwegian Bokmål', nativeName: 'Norsk bokmål', flagCountryCode: 'NO' },
+ { code: 'nd', name: 'North Ndebele', nativeName: 'isiNdebele', flagCountryCode: 'ZW' },
+ { code: 'ne', name: 'Nepali', nativeName: 'नेपाली', flagCountryCode: 'NP' },
+ { code: 'ng', name: 'Ndonga', nativeName: 'Owambo', flagCountryCode: 'NA' },
+ { code: 'nl', name: 'Dutch', nativeName: 'Nederlands', flagCountryCode: 'NL' },
+ { code: 'nn', name: 'Norwegian Nynorsk', nativeName: 'Norsk nynorsk', flagCountryCode: 'NO' },
+ { code: 'no', name: 'Norwegian', nativeName: 'Norsk', flagCountryCode: 'NO' },
+ { code: 'nr', name: 'South Ndebele', nativeName: 'isiNdebele', flagCountryCode: 'ZA' },
+ { code: 'nv', name: 'Navajo', nativeName: 'Diné bizaad', flagCountryCode: 'US' },
+ { code: 'ny', name: 'Chichewa', nativeName: 'chiCheŵa', flagCountryCode: 'MW' },
+ { code: 'oc', name: 'Occitan', nativeName: 'occitan', flagCountryCode: 'FR' },
+ { code: 'oj', name: 'Ojibwa', nativeName: 'ᐊᓂᔑᓈᐯᒧᐎᓐ', flagCountryCode: 'CA' },
+ { code: 'om', name: 'Oromo', nativeName: 'Afaan Oromoo', flagCountryCode: 'ET' },
+ { code: 'or', name: 'Oriya', nativeName: 'ଓଡ଼ିଆ', flagCountryCode: 'IN' },
+ { code: 'os', name: 'Ossetian', nativeName: 'ирон æвзаг', flagCountryCode: 'GE' },
+ { code: 'pa', name: 'Punjabi', nativeName: 'ਪੰਜਾਬੀ', flagCountryCode: 'IN' },
+ { code: 'pi', name: 'Pali', nativeName: 'पाऴि', flagCountryCode: 'IN' },
+ { code: 'pl', name: 'Polish', nativeName: 'polski', flagCountryCode: 'PL' },
+ { code: 'ps', name: 'Pashto', nativeName: 'پښتو', flagCountryCode: 'AF' },
+ { code: 'pt', name: 'Portuguese', nativeName: 'português', flagCountryCode: 'PT' },
+ { code: 'qu', name: 'Quechua', nativeName: 'Runa Simi', flagCountryCode: 'PE' },
+ { code: 'rm', name: 'Romansh', nativeName: 'rumantsch grischun', flagCountryCode: 'CH' },
+ { code: 'rn', name: 'Rundi', nativeName: 'Ikirundi', flagCountryCode: 'BI' },
+ { code: 'ro', name: 'Romanian', nativeName: 'română', flagCountryCode: 'RO' },
+ { code: 'ru', name: 'Russian', nativeName: 'русский', flagCountryCode: 'RU' },
+ { code: 'rw', name: 'Kinyarwanda', nativeName: 'Ikinyarwanda', flagCountryCode: 'RW' },
+ { code: 'sa', name: 'Sanskrit', nativeName: 'संस्कृतम्', flagCountryCode: 'IN' },
+ { code: 'sc', name: 'Sardinian', nativeName: 'sardu', flagCountryCode: 'IT' },
+ { code: 'sd', name: 'Sindhi', nativeName: 'सिन्धी', flagCountryCode: 'PK' },
+ { code: 'se', name: 'Northern Sami', nativeName: 'Davvisámegiella', flagCountryCode: 'NO' },
+ { code: 'sg', name: 'Sango', nativeName: 'yângâ tî sängö', flagCountryCode: 'CF' },
+ { code: 'si', name: 'Sinhala', nativeName: 'සිංහල', flagCountryCode: 'LK' },
+ { code: 'sk', name: 'Slovak', nativeName: 'slovenčina', flagCountryCode: 'SK' },
+ { code: 'sl', name: 'Slovenian', nativeName: 'slovenski jezik', flagCountryCode: 'SI' },
+ { code: 'sm', name: 'Samoan', nativeName: 'gagana fa\'a Samoa', flagCountryCode: 'WS' },
+ { code: 'sn', name: 'Shona', nativeName: 'chiShona', flagCountryCode: 'ZW' },
+ { code: 'so', name: 'Somali', nativeName: 'Soomaaliga', flagCountryCode: 'SO' },
+ { code: 'sq', name: 'Albanian', nativeName: 'Shqip', flagCountryCode: 'AL' },
+ { code: 'sr', name: 'Serbian', nativeName: 'српски језик', flagCountryCode: 'RS' },
+ { code: 'ss', name: 'Swati', nativeName: 'SiSwati', flagCountryCode: 'SZ' },
+ { code: 'st', name: 'Southern Sotho', nativeName: 'Sesotho', flagCountryCode: 'LS' },
+ { code: 'su', name: 'Sundanese', nativeName: 'Basa Sunda', flagCountryCode: 'ID' },
+ { code: 'sv', name: 'Swedish', nativeName: 'svenska', flagCountryCode: 'SE' },
+ { code: 'sw', name: 'Swahili', nativeName: 'Kiswahili', flagCountryCode: 'TZ' },
+ { code: 'ta', name: 'Tamil', nativeName: 'தமிழ்', flagCountryCode: 'IN' },
+ { code: 'te', name: 'Telugu', nativeName: 'తెలుగు', flagCountryCode: 'IN' },
+ { code: 'tg', name: 'Tajik', nativeName: 'тоҷикӣ', flagCountryCode: 'TJ' },
+ { code: 'th', name: 'Thai', nativeName: 'ไทย', flagCountryCode: 'TH' },
+ { code: 'ti', name: 'Tigrinya', nativeName: 'ትግርኛ', flagCountryCode: 'ER' },
+ { code: 'tk', name: 'Turkmen', nativeName: 'Türkmen', flagCountryCode: 'TM' },
+ { code: 'tl', name: 'Tagalog', nativeName: 'Wikang Tagalog', flagCountryCode: 'PH' },
+ { code: 'tn', name: 'Tswana', nativeName: 'Setswana', flagCountryCode: 'BW' },
+ { code: 'to', name: 'Tonga', nativeName: 'faka Tonga', flagCountryCode: 'TO' },
+ { code: 'tr', name: 'Turkish', nativeName: 'Türkçe', flagCountryCode: 'TR' },
+ { code: 'ts', name: 'Tsonga', nativeName: 'Xitsonga', flagCountryCode: 'ZA' },
+ { code: 'tt', name: 'Tatar', nativeName: 'татар теле', flagCountryCode: 'RU' },
+ { code: 'tw', name: 'Twi', nativeName: 'Twi', flagCountryCode: 'GH' },
+ { code: 'ty', name: 'Tahitian', nativeName: 'Reo Tahiti', flagCountryCode: 'PF' },
+ { code: 'ug', name: 'Uighur', nativeName: 'ئۇيغۇرچە', flagCountryCode: 'CN' },
+ { code: 'uk', name: 'Ukrainian', nativeName: 'Українська', flagCountryCode: 'UA' },
+ { code: 'ur', name: 'Urdu', nativeName: 'اردو', flagCountryCode: 'PK' },
+ { code: 'uz', name: 'Uzbek', nativeName: 'Oʻzbek', flagCountryCode: 'UZ' },
+ { code: 've', name: 'Venda', nativeName: 'Tshivenḓa', flagCountryCode: 'ZA' },
+ { code: 'vi', name: 'Vietnamese', nativeName: 'Tiếng Việt', flagCountryCode: 'VN' },
+ { code: 'vo', name: 'Volapük', nativeName: 'Volapük', flagCountryCode: 'EU' },
+ { code: 'wa', name: 'Walloon', nativeName: 'walon', flagCountryCode: 'BE' },
+ { code: 'wo', name: 'Wolof', nativeName: 'Wollof', flagCountryCode: 'SN' },
+ { code: 'xh', name: 'Xhosa', nativeName: 'isiXhosa', flagCountryCode: 'ZA' },
+ { code: 'yi', name: 'Yiddish', nativeName: 'ייִדיש', flagCountryCode: 'IL' },
+ { code: 'yo', name: 'Yoruba', nativeName: 'Yorùbá', flagCountryCode: 'NG' },
+ { code: 'za', name: 'Zhuang', nativeName: 'Saɯ cueŋƅ', flagCountryCode: 'CN' },
+ { code: 'zh', name: 'Chinese', nativeName: '中文', flagCountryCode: 'CN' },
+ { code: 'zu', name: 'Zulu', nativeName: 'isiZulu', flagCountryCode: 'ZA' }
+];
diff --git a/frontend/src/app/consts/record-edit-types.ts b/frontend/src/app/consts/record-edit-types.ts
index 9e172b121..6b698ba9a 100644
--- a/frontend/src/app/consts/record-edit-types.ts
+++ b/frontend/src/app/consts/record-edit-types.ts
@@ -10,6 +10,7 @@ import { ForeignKeyEditComponent } from '../components/ui-components/record-edit
import { IdEditComponent } from '../components/ui-components/record-edit-fields/id/id.component';
import { ImageEditComponent } from '../components/ui-components/record-edit-fields/image/image.component';
import { JsonEditorEditComponent } from '../components/ui-components/record-edit-fields/json-editor/json-editor.component';
+import { LanguageEditComponent } from '../components/ui-components/record-edit-fields/language/language.component';
import { LongTextEditComponent } from 'src/app/components/ui-components/record-edit-fields/long-text/long-text.component'
import { MarkdownEditComponent } from '../components/ui-components/record-edit-fields/markdown/markdown.component';
import { MoneyEditComponent } from '../components/ui-components/record-edit-fields/money/money.component';
@@ -45,6 +46,7 @@ export const UIwidgets = {
Foreign_key: ForeignKeyEditComponent,
Image: ImageEditComponent,
JSON: JsonEditorEditComponent,
+ Language: LanguageEditComponent,
Markdown: MarkdownEditComponent,
Money: MoneyEditComponent,
Number: NumberEditComponent,
diff --git a/frontend/src/app/consts/record-view-types.ts b/frontend/src/app/consts/record-view-types.ts
index 2e663fa92..e90b35cac 100644
--- a/frontend/src/app/consts/record-view-types.ts
+++ b/frontend/src/app/consts/record-view-types.ts
@@ -9,6 +9,7 @@ import { ForeignKeyRecordViewComponent } from '../components/ui-components/recor
import { IdRecordViewComponent } from '../components/ui-components/record-view-fields/id/id.component';
import { ImageRecordViewComponent } from '../components/ui-components/record-view-fields/image/image.component';
import { JsonEditorRecordViewComponent } from '../components/ui-components/record-view-fields/json-editor/json-editor.component';
+import { LanguageRecordViewComponent } from '../components/ui-components/record-view-fields/language/language.component';
import { LongTextRecordViewComponent } from 'src/app/components/ui-components/record-view-fields/long-text/long-text.component';
import { MarkdownRecordViewComponent } from '../components/ui-components/record-view-fields/markdown/markdown.component';
import { MoneyRecordViewComponent } from '../components/ui-components/record-view-fields/money/money.component';
@@ -33,6 +34,7 @@ export const UIwidgets = {
Time: TimeRecordViewComponent,
DateTime: DateTimeRecordViewComponent,
JSON: JsonEditorRecordViewComponent,
+ Language: LanguageRecordViewComponent,
Markdown: MarkdownRecordViewComponent,
Textarea: LongTextRecordViewComponent,
String: TextRecordViewComponent,
diff --git a/frontend/src/app/consts/table-display-types.ts b/frontend/src/app/consts/table-display-types.ts
index 0abf9ae22..abb6edf7f 100644
--- a/frontend/src/app/consts/table-display-types.ts
+++ b/frontend/src/app/consts/table-display-types.ts
@@ -9,6 +9,7 @@ import { ForeignKeyDisplayComponent } from '../components/ui-components/table-di
import { IdDisplayComponent } from '../components/ui-components/table-display-fields/id/id.component';
import { ImageDisplayComponent } from '../components/ui-components/table-display-fields/image/image.component';
import { JsonEditorDisplayComponent } from '../components/ui-components/table-display-fields/json-editor/json-editor.component';
+import { LanguageDisplayComponent } from '../components/ui-components/table-display-fields/language/language.component';
import { LongTextDisplayComponent } from 'src/app/components/ui-components/table-display-fields/long-text/long-text.component';
import { MarkdownDisplayComponent } from '../components/ui-components/table-display-fields/markdown/markdown.component';
import { MoneyDisplayComponent } from '../components/ui-components/table-display-fields/money/money.component';
@@ -38,6 +39,7 @@ export const UIwidgets = {
Foreign_key: ForeignKeyDisplayComponent,
Image: ImageDisplayComponent,
JSON: JsonEditorDisplayComponent,
+ Language: LanguageDisplayComponent,
Markdown: MarkdownDisplayComponent,
Money: MoneyDisplayComponent,
Number: NumberDisplayComponent,