mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api'
2import * as bcp47Match from 'bcp-47-match'
3import lande from 'lande'
4
5import {hasProp} from 'lib/type-guards'
6import {
7 AppLanguage,
8 LANGUAGES_MAP_CODE2,
9 LANGUAGES_MAP_CODE3,
10} from './languages'
11
12export function code2ToCode3(lang: string): string {
13 if (lang.length === 2) {
14 return LANGUAGES_MAP_CODE2[lang]?.code3 || lang
15 }
16 return lang
17}
18
19export function code3ToCode2(lang: string): string {
20 if (lang.length === 3) {
21 return LANGUAGES_MAP_CODE3[lang]?.code2 || lang
22 }
23 return lang
24}
25
26export function code3ToCode2Strict(lang: string): string | undefined {
27 if (lang.length === 3) {
28 return LANGUAGES_MAP_CODE3[lang]?.code2
29 }
30
31 return undefined
32}
33
34export function codeToLanguageName(lang: string): string {
35 const lang2 = code3ToCode2(lang)
36 return LANGUAGES_MAP_CODE2[lang2]?.name || lang
37}
38
39export function getPostLanguage(
40 post: AppBskyFeedDefs.PostView,
41): string | undefined {
42 let candidates: string[] = []
43 let postText: string = ''
44 if (hasProp(post.record, 'text') && typeof post.record.text === 'string') {
45 postText = post.record.text
46 }
47
48 if (
49 AppBskyFeedPost.isRecord(post.record) &&
50 hasProp(post.record, 'langs') &&
51 Array.isArray(post.record.langs)
52 ) {
53 candidates = post.record.langs
54 }
55
56 // if there's only one declared language, use that
57 if (candidates?.length === 1) {
58 return candidates[0]
59 }
60
61 // no text? can't determine
62 if (postText.trim().length === 0) {
63 return undefined
64 }
65
66 // run the language model
67 let langsProbabilityMap = lande(postText)
68
69 // filter down using declared languages
70 if (candidates?.length) {
71 langsProbabilityMap = langsProbabilityMap.filter(
72 ([lang, _probability]: [string, number]) => {
73 return candidates.includes(code3ToCode2(lang))
74 },
75 )
76 }
77
78 if (langsProbabilityMap[0]) {
79 return code3ToCode2(langsProbabilityMap[0][0])
80 }
81}
82
83export function isPostInLanguage(
84 post: AppBskyFeedDefs.PostView,
85 targetLangs: string[],
86): boolean {
87 const lang = getPostLanguage(post)
88 if (!lang) {
89 // the post has no text, so we just say "yes" for now
90 return true
91 }
92 return bcp47Match.basicFilter(lang, targetLangs).length > 0
93}
94
95export function getTranslatorLink(text: string, lang: string): string {
96 return `https://translate.google.com/?sl=auto&tl=${lang}&text=${encodeURIComponent(
97 text,
98 )}`
99}
100
101/**
102 * Returns a valid `appLanguage` value from an arbitrary string.
103 *
104 * Contenxt: post-refactor, we populated some user's `appLanguage` setting with
105 * `postLanguage`, which can be a comma-separated list of values. This breaks
106 * `appLanguage` handling in the app, so we introduced this util to parse out a
107 * valid `appLanguage` from the pre-populated `postLanguage` values.
108 *
109 * The `appLanguage` will continue to be incorrect until the user returns to
110 * language settings and selects a new option, at which point we'll re-save
111 * their choice, which should then be a valid option. Since we don't know when
112 * this will happen, we should leave this here until we feel it's safe to
113 * remove, or we re-migrate their storage.
114 */
115export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
116 const langs = appLanguage.split(',').filter(Boolean)
117
118 for (const lang of langs) {
119 switch (fixLegacyLanguageCode(lang)) {
120 case 'en':
121 return AppLanguage.en
122 case 'ca':
123 return AppLanguage.ca
124 case 'de':
125 return AppLanguage.de
126 case 'es':
127 return AppLanguage.es
128 case 'fi':
129 return AppLanguage.fi
130 case 'fr':
131 return AppLanguage.fr
132 case 'ga':
133 return AppLanguage.ga
134 case 'hi':
135 return AppLanguage.hi
136 case 'id':
137 return AppLanguage.id
138 case 'it':
139 return AppLanguage.it
140 case 'ja':
141 return AppLanguage.ja
142 case 'ko':
143 return AppLanguage.ko
144 case 'pt-BR':
145 return AppLanguage.pt_BR
146 case 'tr':
147 return AppLanguage.tr
148 case 'uk':
149 return AppLanguage.uk
150 case 'zh-CN':
151 return AppLanguage.zh_CN
152 case 'zh-TW':
153 return AppLanguage.zh_TW
154 default:
155 continue
156 }
157 }
158 return AppLanguage.en
159}
160
161export function fixLegacyLanguageCode(code: string | null): string | null {
162 // handle some legacy code conversions, see https://xml.coverpages.org/iso639a.html
163 if (code === 'in') {
164 // indonesian
165 return 'id'
166 }
167 if (code === 'iw') {
168 // hebrew
169 return 'he'
170 }
171 if (code === 'ji') {
172 // yiddish
173 return 'yi'
174 }
175 return code
176}