├── .eslintrc ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── assets ├── RobotoMono-Regular.ttf ├── icon.png └── spellcheck.swift ├── package.json ├── pnpm-lock.yaml ├── scripts ├── preference.ts └── prepare.ts ├── src ├── components │ ├── Main.tsx │ ├── SpellcheckItem.tsx │ └── TranslateDetail.tsx ├── data │ ├── languages.json │ ├── languages.ts │ └── web-dictionaries.ts ├── logic │ ├── diff.tsx │ ├── hooks.ts │ ├── spellcheck.ts │ ├── text.ts │ └── translator.ts ├── translate.tsx └── types.ts └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:react/recommended", 4 | "plugin:react/jsx-runtime", 5 | "@antfu" 6 | ], 7 | "rules": { 8 | "react/jsx-indent": ["error", 2], 9 | "react/jsx-indent-props": ["error", 2], 10 | "react/jsx-max-props-per-line": ["error", { "maximum": 4 }], 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### macOS ### 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | 29 | ## Build generated 30 | build/ 31 | DerivedData/ 32 | assets/spellcheck 33 | 34 | ### Node ### 35 | # Logs 36 | logs 37 | *.log 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | lerna-debug.log* 42 | 43 | # Diagnostic reports (https://nodejs.org/api/report.html) 44 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 45 | 46 | # Runtime data 47 | pids 48 | *.pid 49 | *.seed 50 | *.pid.lock 51 | 52 | # Directory for instrumented libs generated by jscoverage/JSCover 53 | lib-cov 54 | 55 | # Coverage directory used by tools like istanbul 56 | coverage 57 | *.lcov 58 | 59 | # nyc test coverage 60 | .nyc_output 61 | 62 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 63 | .grunt 64 | 65 | # Bower dependency directory (https://bower.io/) 66 | bower_components 67 | 68 | # node-waf configuration 69 | .lock-wscript 70 | 71 | # Compiled binary addons (https://nodejs.org/api/addons.html) 72 | build/Release 73 | 74 | # Dependency directories 75 | node_modules/ 76 | jspm_packages/ 77 | 78 | # Build directories 79 | dist/ 80 | 81 | # TypeScript v1 declaration files 82 | typings/ 83 | 84 | # TypeScript cache 85 | *.tsbuildinfo 86 | 87 | # Optional npm cache directory 88 | .npm 89 | 90 | # Optional eslint cache 91 | .eslintcache 92 | 93 | # Microbundle cache 94 | .rpt2_cache/ 95 | .rts2_cache_cjs/ 96 | .rts2_cache_es/ 97 | .rts2_cache_umd/ 98 | 99 | # Optional REPL history 100 | .node_repl_history 101 | 102 | # Output of 'npm pack' 103 | *.tgz 104 | 105 | # Yarn Integrity file 106 | .yarn-integrity 107 | 108 | # dotenv environment variables file 109 | .env 110 | .env.test 111 | .env.json 112 | 113 | # parcel-bundler cache (https://parceljs.org/) 114 | .cache 115 | 116 | # Next.js build output 117 | .next 118 | 119 | # Nuxt.js build / generate output 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | .cache/ 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | # https://nextjs.org/blog/next-9-1#public-directory-support 127 | # public 128 | 129 | # vuepress build output 130 | .vuepress/dist 131 | 132 | # Serverless directories 133 | .serverless/ 134 | 135 | # FuseBox cache 136 | .fusebox/ 137 | 138 | # DynamoDB Local files 139 | .dynamodb/ 140 | 141 | # TernJS port file 142 | .tern-port 143 | 144 | # Stores VSCode versions used for testing VSCode extensions 145 | .vscode-test 146 | 147 | ## JS Source Maps 148 | *.js.map 149 | 150 | ## Extension archives 151 | examples/**/*.zip 152 | extensions/**/*.zip 153 | 154 | # IntelliJ / WebStorm 155 | .idea/ 156 | *.iml 157 | 158 | # Raycast 159 | raycast-env.d.ts 160 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Anthony Fu 4 | Copyright (c) 2023 Slavik Nychkalo 5 | Copyright (c) 2021 Raycast 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 |

6 | 7 |

MultiTranslate

8 | 9 |

10 | A Raycast extension that translates text to multiple languages at once. 11 |

12 | 13 | 14 | 15 | ## Motivation 16 | 17 | I speak Chinese, work in English, live in France, and sometimes also consume Japanese content. I do language-switching all the time. For example, I'd like to know what's the meaning of a new English word, find the English translation of a Chinese word, or verify if my French sentence is correct, etc. Most of the time I just need a quick answer, but I found I spend quite a lot of time telling the translator which language **from** and **to**, which should be detected automatically. So instead of setting the language manually every time, let's translate it to all languages we use at once. 18 | 19 | ## Features 20 | 21 | 22 | ## Installation 23 | 24 | Currently, you need to clone this repo and install it locally in developer mode. 25 | 26 | You will need to have [Node.js](https://nodejs.org) and [pnpm](https://pnpm.io/) installed. 27 | 28 | 1. Clone this repo `git clone https://github.com/antfu/raycast-multi-translate` 29 | 2. Go to the folder `cd raycast-multi-translate` 30 | 3. Install dependencies `pnpm install` 31 | 4. Go to Raycast, run `Import Extension` and select the folder 32 | 33 | There is **no plan** to publish to the bloated [raycast/extensions](https://github.com/raycast/extensions) until they make a decentralized publishing system. 34 | 35 | ## Features 36 | 37 | ### Multiple Translations Targets 38 | 39 | Support translating up to 7 languages at once. Options can be found in the extensions tab of Raycast settings. 40 | 41 | 42 | 43 | ### Spellcheck 44 | 45 | When you misspell some words, an additional item will be listed, with diffing support to help you find the correct spelling easier. 46 | 47 | 48 | 49 | ### Source Language Detection 50 | 51 | By default we send the text to Google Translate and let it detect the source language automatically. In some cases, it might not be accurate because different works in different languages can spell the same. For example, `ours` in French means `bear`, but in English, it means `belonging to us`. In this case, if you want to translate `ours` in French, you can add `>fr` to the end of the text to force the source language to be French. 52 | 53 | 54 | 55 | ## Credits 56 | 57 | Originally forked from https://github.com/raycast/extensions/tree/main/extensions/google-translate, thanks to [@gebeto](https://github.com/gebeto) et al. 58 | 59 |
60 | Changes from the original extension 61 | 62 | - Support translating for any language to multiple languages at once 63 | - Removed the representation of flag emojis, as languages are not countries 64 | - Removed the `Translate From` command, and the language selection dropdown, as they are automatized 65 | - Show details view by default 66 | - Cache translation results 67 | - Make "Use current selection" passive and don't interfere the input field 68 | - Added spellcheck functionality 69 | 70 |
71 | 72 | ## License 73 | 74 | MIT License 75 | -------------------------------------------------------------------------------- /assets/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antfu/raycast-multi-translate/70fb0aa7f9151ccc942d53e8f696cc2410205267/assets/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antfu/raycast-multi-translate/70fb0aa7f9151ccc942d53e8f696cc2410205267/assets/icon.png -------------------------------------------------------------------------------- /assets/spellcheck.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | let spellChecker = NSSpellChecker.shared 4 | spellChecker.automaticallyIdentifiesLanguages = true 5 | 6 | // Debug: `swift ./spellcheck.swift "how are yu?"` 7 | guard CommandLine.argc == 2, let stringToCheck = CommandLine.arguments.last else { 8 | debugPrint("error: \(CommandLine.arguments)") 9 | exit(0) 10 | } 11 | 12 | print(checkSpelling(stringToCheck), terminator: "") 13 | 14 | func checkSpelling(_ text: String) -> String { 15 | var misspellings = spellChecker.check(text, range: NSRange(location: 0, length: text.utf16.count), types: NSTextCheckingResult.CheckingType.spelling.rawValue, options: nil, inSpellDocumentWithTag: 0, orthography: nil, wordCount: nil) 16 | if misspellings.isEmpty { 17 | return text 18 | } 19 | misspellings.reverse() 20 | var correctedText = NSString(string: text) 21 | for misspelling in misspellings { 22 | let guesses = spellChecker.guesses(forWordRange: misspelling.range, in: text, language: spellChecker.language(), inSpellDocumentWithTag: 0) 23 | if let suggest = guesses?.first { 24 | correctedText = NSString(string: correctedText.replacingCharacters(in: misspelling.range, with: suggest)) 25 | } 26 | } 27 | return String(correctedText) 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://www.raycast.com/schemas/extension.json", 3 | "name": "multitranslate", 4 | "title": "MultiTranslate", 5 | "private": true, 6 | "packageManager": "pnpm@8.6.1", 7 | "description": "Translates text to multiple languages at once", 8 | "author": "antfu", 9 | "license": "MIT", 10 | "categories": [ 11 | "Productivity", 12 | "Communication" 13 | ], 14 | "icon": "icon.png", 15 | "contributors": [ 16 | "gebeto", 17 | "FezVrasta", 18 | "metakirby5", 19 | "AlanHuang" 20 | ], 21 | "commands": [ 22 | { 23 | "name": "translate", 24 | "title": "MultiTranslate", 25 | "description": "Translate to multiple languages", 26 | "mode": "view" 27 | } 28 | ], 29 | "preferences": [ 30 | { 31 | "name": "getSystemSelection", 32 | "type": "checkbox", 33 | "default": true, 34 | "required": true, 35 | "label": "Read system text selection", 36 | "description": "Automatically get current text selection and translate" 37 | }, 38 | { 39 | "name": "lang1", 40 | "type": "dropdown", 41 | "title": "Language 1", 42 | "description": "Language 1", 43 | "data": [ 44 | { 45 | "title": "Afrikaans", 46 | "value": "af" 47 | }, 48 | { 49 | "title": "Albanian", 50 | "value": "sq" 51 | }, 52 | { 53 | "title": "Amharic", 54 | "value": "am" 55 | }, 56 | { 57 | "title": "Arabic", 58 | "value": "ar" 59 | }, 60 | { 61 | "title": "Armenian", 62 | "value": "hy" 63 | }, 64 | { 65 | "title": "Azerbaijani", 66 | "value": "az" 67 | }, 68 | { 69 | "title": "Basque", 70 | "value": "eu" 71 | }, 72 | { 73 | "title": "Belarusian", 74 | "value": "be" 75 | }, 76 | { 77 | "title": "Bengali", 78 | "value": "bn" 79 | }, 80 | { 81 | "title": "Bosnian", 82 | "value": "bs" 83 | }, 84 | { 85 | "title": "Bulgarian", 86 | "value": "bg" 87 | }, 88 | { 89 | "title": "Catalan", 90 | "value": "ca" 91 | }, 92 | { 93 | "title": "Cebuano", 94 | "value": "ceb" 95 | }, 96 | { 97 | "title": "Chinese Simplified", 98 | "value": "zh-CN" 99 | }, 100 | { 101 | "title": "Taiwanese Mandarin", 102 | "value": "zh-TW" 103 | }, 104 | { 105 | "title": "Corsican", 106 | "value": "co" 107 | }, 108 | { 109 | "title": "Croatian", 110 | "value": "hr" 111 | }, 112 | { 113 | "title": "Czech", 114 | "value": "cs" 115 | }, 116 | { 117 | "title": "Danish", 118 | "value": "da" 119 | }, 120 | { 121 | "title": "Dutch", 122 | "value": "nl" 123 | }, 124 | { 125 | "title": "English", 126 | "value": "en" 127 | }, 128 | { 129 | "title": "Esperanto", 130 | "value": "eo" 131 | }, 132 | { 133 | "title": "Estonian", 134 | "value": "et" 135 | }, 136 | { 137 | "title": "Finnish", 138 | "value": "fi" 139 | }, 140 | { 141 | "title": "French", 142 | "value": "fr" 143 | }, 144 | { 145 | "title": "Frisian", 146 | "value": "fy" 147 | }, 148 | { 149 | "title": "Galician", 150 | "value": "gl" 151 | }, 152 | { 153 | "title": "Georgian", 154 | "value": "ka" 155 | }, 156 | { 157 | "title": "German", 158 | "value": "de" 159 | }, 160 | { 161 | "title": "Greek", 162 | "value": "el" 163 | }, 164 | { 165 | "title": "Gujarati", 166 | "value": "gu" 167 | }, 168 | { 169 | "title": "Haitian Creole", 170 | "value": "ht" 171 | }, 172 | { 173 | "title": "Hausa", 174 | "value": "ha" 175 | }, 176 | { 177 | "title": "Hawaiian", 178 | "value": "haw" 179 | }, 180 | { 181 | "title": "Hebrew", 182 | "value": "he" 183 | }, 184 | { 185 | "title": "Hindi", 186 | "value": "hi" 187 | }, 188 | { 189 | "title": "Hmong", 190 | "value": "hmn" 191 | }, 192 | { 193 | "title": "Hungarian", 194 | "value": "hu" 195 | }, 196 | { 197 | "title": "Icelandic", 198 | "value": "is" 199 | }, 200 | { 201 | "title": "Igbo", 202 | "value": "ig" 203 | }, 204 | { 205 | "title": "Indonesian", 206 | "value": "id" 207 | }, 208 | { 209 | "title": "Irish", 210 | "value": "ga" 211 | }, 212 | { 213 | "title": "Italian", 214 | "value": "it" 215 | }, 216 | { 217 | "title": "Japanese", 218 | "value": "ja" 219 | }, 220 | { 221 | "title": "Javanese", 222 | "value": "jv" 223 | }, 224 | { 225 | "title": "Kannada", 226 | "value": "kn" 227 | }, 228 | { 229 | "title": "Kazakh", 230 | "value": "kk" 231 | }, 232 | { 233 | "title": "Khmer", 234 | "value": "km" 235 | }, 236 | { 237 | "title": "Kinyarwanda", 238 | "value": "rw" 239 | }, 240 | { 241 | "title": "Korean", 242 | "value": "ko" 243 | }, 244 | { 245 | "title": "Kurdish", 246 | "value": "ku" 247 | }, 248 | { 249 | "title": "Kyrgyz", 250 | "value": "ky" 251 | }, 252 | { 253 | "title": "Lao", 254 | "value": "lo" 255 | }, 256 | { 257 | "title": "Latvian", 258 | "value": "lv" 259 | }, 260 | { 261 | "title": "Lithuanian", 262 | "value": "lt" 263 | }, 264 | { 265 | "title": "Luxembourgish", 266 | "value": "lb" 267 | }, 268 | { 269 | "title": "Macedonian", 270 | "value": "mk" 271 | }, 272 | { 273 | "title": "Malagasy", 274 | "value": "mg" 275 | }, 276 | { 277 | "title": "Malay", 278 | "value": "ms" 279 | }, 280 | { 281 | "title": "Malayalam", 282 | "value": "ml" 283 | }, 284 | { 285 | "title": "Maltese", 286 | "value": "mt" 287 | }, 288 | { 289 | "title": "Maori", 290 | "value": "mi" 291 | }, 292 | { 293 | "title": "Marathi", 294 | "value": "mr" 295 | }, 296 | { 297 | "title": "Mongolian", 298 | "value": "mn" 299 | }, 300 | { 301 | "title": "Myanmar (Burmese)", 302 | "value": "my" 303 | }, 304 | { 305 | "title": "Nepali", 306 | "value": "ne" 307 | }, 308 | { 309 | "title": "Norwegian", 310 | "value": "no" 311 | }, 312 | { 313 | "title": "Nyanja (Chichewa)", 314 | "value": "ny" 315 | }, 316 | { 317 | "title": "Odia (Oriya)", 318 | "value": "or" 319 | }, 320 | { 321 | "title": "Pashto", 322 | "value": "ps" 323 | }, 324 | { 325 | "title": "Persian", 326 | "value": "fa" 327 | }, 328 | { 329 | "title": "Polish", 330 | "value": "pl" 331 | }, 332 | { 333 | "title": "Portuguese", 334 | "value": "pt" 335 | }, 336 | { 337 | "title": "Punjabi", 338 | "value": "pa" 339 | }, 340 | { 341 | "title": "Romanian", 342 | "value": "ro" 343 | }, 344 | { 345 | "title": "Russian", 346 | "value": "ru" 347 | }, 348 | { 349 | "title": "Samoan", 350 | "value": "sm" 351 | }, 352 | { 353 | "title": "Scots Gaelic", 354 | "value": "gd" 355 | }, 356 | { 357 | "title": "Serbian", 358 | "value": "sr" 359 | }, 360 | { 361 | "title": "Sesotho", 362 | "value": "st" 363 | }, 364 | { 365 | "title": "Shona", 366 | "value": "sn" 367 | }, 368 | { 369 | "title": "Sindhi", 370 | "value": "sd" 371 | }, 372 | { 373 | "title": "Sinhala (Sinhalese)", 374 | "value": "si" 375 | }, 376 | { 377 | "title": "Slovak", 378 | "value": "sk" 379 | }, 380 | { 381 | "title": "Slovenian", 382 | "value": "sl" 383 | }, 384 | { 385 | "title": "Somali", 386 | "value": "so" 387 | }, 388 | { 389 | "title": "Spanish", 390 | "value": "es" 391 | }, 392 | { 393 | "title": "Sundanese", 394 | "value": "su" 395 | }, 396 | { 397 | "title": "Swahili", 398 | "value": "sw" 399 | }, 400 | { 401 | "title": "Swedish", 402 | "value": "sv" 403 | }, 404 | { 405 | "title": "Tagalog (Filipino)", 406 | "value": "tl" 407 | }, 408 | { 409 | "title": "Tajik", 410 | "value": "tg" 411 | }, 412 | { 413 | "title": "Tamil", 414 | "value": "ta" 415 | }, 416 | { 417 | "title": "Tatar", 418 | "value": "tt" 419 | }, 420 | { 421 | "title": "Telugu", 422 | "value": "te" 423 | }, 424 | { 425 | "title": "Thai", 426 | "value": "th" 427 | }, 428 | { 429 | "title": "Turkish", 430 | "value": "tr" 431 | }, 432 | { 433 | "title": "Turkmen", 434 | "value": "tk" 435 | }, 436 | { 437 | "title": "Ukrainian", 438 | "value": "uk" 439 | }, 440 | { 441 | "title": "Urdu", 442 | "value": "ur" 443 | }, 444 | { 445 | "title": "Uyghur", 446 | "value": "ug" 447 | }, 448 | { 449 | "title": "Uzbek", 450 | "value": "uz" 451 | }, 452 | { 453 | "title": "Vietnamese", 454 | "value": "vi" 455 | }, 456 | { 457 | "title": "Welsh", 458 | "value": "cy" 459 | }, 460 | { 461 | "title": "Xhosa", 462 | "value": "xh" 463 | }, 464 | { 465 | "title": "Yiddish", 466 | "value": "yi" 467 | }, 468 | { 469 | "title": "Yoruba", 470 | "value": "yo" 471 | }, 472 | { 473 | "title": "Zulu", 474 | "value": "zu" 475 | } 476 | ], 477 | "required": true, 478 | "default": "en" 479 | }, 480 | { 481 | "name": "lang2", 482 | "type": "dropdown", 483 | "title": "Language 2", 484 | "description": "Language 2", 485 | "data": [ 486 | { 487 | "title": "-", 488 | "value": "none" 489 | }, 490 | { 491 | "title": "Afrikaans", 492 | "value": "af" 493 | }, 494 | { 495 | "title": "Albanian", 496 | "value": "sq" 497 | }, 498 | { 499 | "title": "Amharic", 500 | "value": "am" 501 | }, 502 | { 503 | "title": "Arabic", 504 | "value": "ar" 505 | }, 506 | { 507 | "title": "Armenian", 508 | "value": "hy" 509 | }, 510 | { 511 | "title": "Azerbaijani", 512 | "value": "az" 513 | }, 514 | { 515 | "title": "Basque", 516 | "value": "eu" 517 | }, 518 | { 519 | "title": "Belarusian", 520 | "value": "be" 521 | }, 522 | { 523 | "title": "Bengali", 524 | "value": "bn" 525 | }, 526 | { 527 | "title": "Bosnian", 528 | "value": "bs" 529 | }, 530 | { 531 | "title": "Bulgarian", 532 | "value": "bg" 533 | }, 534 | { 535 | "title": "Catalan", 536 | "value": "ca" 537 | }, 538 | { 539 | "title": "Cebuano", 540 | "value": "ceb" 541 | }, 542 | { 543 | "title": "Chinese Simplified", 544 | "value": "zh-CN" 545 | }, 546 | { 547 | "title": "Taiwanese Mandarin", 548 | "value": "zh-TW" 549 | }, 550 | { 551 | "title": "Corsican", 552 | "value": "co" 553 | }, 554 | { 555 | "title": "Croatian", 556 | "value": "hr" 557 | }, 558 | { 559 | "title": "Czech", 560 | "value": "cs" 561 | }, 562 | { 563 | "title": "Danish", 564 | "value": "da" 565 | }, 566 | { 567 | "title": "Dutch", 568 | "value": "nl" 569 | }, 570 | { 571 | "title": "English", 572 | "value": "en" 573 | }, 574 | { 575 | "title": "Esperanto", 576 | "value": "eo" 577 | }, 578 | { 579 | "title": "Estonian", 580 | "value": "et" 581 | }, 582 | { 583 | "title": "Finnish", 584 | "value": "fi" 585 | }, 586 | { 587 | "title": "French", 588 | "value": "fr" 589 | }, 590 | { 591 | "title": "Frisian", 592 | "value": "fy" 593 | }, 594 | { 595 | "title": "Galician", 596 | "value": "gl" 597 | }, 598 | { 599 | "title": "Georgian", 600 | "value": "ka" 601 | }, 602 | { 603 | "title": "German", 604 | "value": "de" 605 | }, 606 | { 607 | "title": "Greek", 608 | "value": "el" 609 | }, 610 | { 611 | "title": "Gujarati", 612 | "value": "gu" 613 | }, 614 | { 615 | "title": "Haitian Creole", 616 | "value": "ht" 617 | }, 618 | { 619 | "title": "Hausa", 620 | "value": "ha" 621 | }, 622 | { 623 | "title": "Hawaiian", 624 | "value": "haw" 625 | }, 626 | { 627 | "title": "Hebrew", 628 | "value": "he" 629 | }, 630 | { 631 | "title": "Hindi", 632 | "value": "hi" 633 | }, 634 | { 635 | "title": "Hmong", 636 | "value": "hmn" 637 | }, 638 | { 639 | "title": "Hungarian", 640 | "value": "hu" 641 | }, 642 | { 643 | "title": "Icelandic", 644 | "value": "is" 645 | }, 646 | { 647 | "title": "Igbo", 648 | "value": "ig" 649 | }, 650 | { 651 | "title": "Indonesian", 652 | "value": "id" 653 | }, 654 | { 655 | "title": "Irish", 656 | "value": "ga" 657 | }, 658 | { 659 | "title": "Italian", 660 | "value": "it" 661 | }, 662 | { 663 | "title": "Japanese", 664 | "value": "ja" 665 | }, 666 | { 667 | "title": "Javanese", 668 | "value": "jv" 669 | }, 670 | { 671 | "title": "Kannada", 672 | "value": "kn" 673 | }, 674 | { 675 | "title": "Kazakh", 676 | "value": "kk" 677 | }, 678 | { 679 | "title": "Khmer", 680 | "value": "km" 681 | }, 682 | { 683 | "title": "Kinyarwanda", 684 | "value": "rw" 685 | }, 686 | { 687 | "title": "Korean", 688 | "value": "ko" 689 | }, 690 | { 691 | "title": "Kurdish", 692 | "value": "ku" 693 | }, 694 | { 695 | "title": "Kyrgyz", 696 | "value": "ky" 697 | }, 698 | { 699 | "title": "Lao", 700 | "value": "lo" 701 | }, 702 | { 703 | "title": "Latvian", 704 | "value": "lv" 705 | }, 706 | { 707 | "title": "Lithuanian", 708 | "value": "lt" 709 | }, 710 | { 711 | "title": "Luxembourgish", 712 | "value": "lb" 713 | }, 714 | { 715 | "title": "Macedonian", 716 | "value": "mk" 717 | }, 718 | { 719 | "title": "Malagasy", 720 | "value": "mg" 721 | }, 722 | { 723 | "title": "Malay", 724 | "value": "ms" 725 | }, 726 | { 727 | "title": "Malayalam", 728 | "value": "ml" 729 | }, 730 | { 731 | "title": "Maltese", 732 | "value": "mt" 733 | }, 734 | { 735 | "title": "Maori", 736 | "value": "mi" 737 | }, 738 | { 739 | "title": "Marathi", 740 | "value": "mr" 741 | }, 742 | { 743 | "title": "Mongolian", 744 | "value": "mn" 745 | }, 746 | { 747 | "title": "Myanmar (Burmese)", 748 | "value": "my" 749 | }, 750 | { 751 | "title": "Nepali", 752 | "value": "ne" 753 | }, 754 | { 755 | "title": "Norwegian", 756 | "value": "no" 757 | }, 758 | { 759 | "title": "Nyanja (Chichewa)", 760 | "value": "ny" 761 | }, 762 | { 763 | "title": "Odia (Oriya)", 764 | "value": "or" 765 | }, 766 | { 767 | "title": "Pashto", 768 | "value": "ps" 769 | }, 770 | { 771 | "title": "Persian", 772 | "value": "fa" 773 | }, 774 | { 775 | "title": "Polish", 776 | "value": "pl" 777 | }, 778 | { 779 | "title": "Portuguese", 780 | "value": "pt" 781 | }, 782 | { 783 | "title": "Punjabi", 784 | "value": "pa" 785 | }, 786 | { 787 | "title": "Romanian", 788 | "value": "ro" 789 | }, 790 | { 791 | "title": "Russian", 792 | "value": "ru" 793 | }, 794 | { 795 | "title": "Samoan", 796 | "value": "sm" 797 | }, 798 | { 799 | "title": "Scots Gaelic", 800 | "value": "gd" 801 | }, 802 | { 803 | "title": "Serbian", 804 | "value": "sr" 805 | }, 806 | { 807 | "title": "Sesotho", 808 | "value": "st" 809 | }, 810 | { 811 | "title": "Shona", 812 | "value": "sn" 813 | }, 814 | { 815 | "title": "Sindhi", 816 | "value": "sd" 817 | }, 818 | { 819 | "title": "Sinhala (Sinhalese)", 820 | "value": "si" 821 | }, 822 | { 823 | "title": "Slovak", 824 | "value": "sk" 825 | }, 826 | { 827 | "title": "Slovenian", 828 | "value": "sl" 829 | }, 830 | { 831 | "title": "Somali", 832 | "value": "so" 833 | }, 834 | { 835 | "title": "Spanish", 836 | "value": "es" 837 | }, 838 | { 839 | "title": "Sundanese", 840 | "value": "su" 841 | }, 842 | { 843 | "title": "Swahili", 844 | "value": "sw" 845 | }, 846 | { 847 | "title": "Swedish", 848 | "value": "sv" 849 | }, 850 | { 851 | "title": "Tagalog (Filipino)", 852 | "value": "tl" 853 | }, 854 | { 855 | "title": "Tajik", 856 | "value": "tg" 857 | }, 858 | { 859 | "title": "Tamil", 860 | "value": "ta" 861 | }, 862 | { 863 | "title": "Tatar", 864 | "value": "tt" 865 | }, 866 | { 867 | "title": "Telugu", 868 | "value": "te" 869 | }, 870 | { 871 | "title": "Thai", 872 | "value": "th" 873 | }, 874 | { 875 | "title": "Turkish", 876 | "value": "tr" 877 | }, 878 | { 879 | "title": "Turkmen", 880 | "value": "tk" 881 | }, 882 | { 883 | "title": "Ukrainian", 884 | "value": "uk" 885 | }, 886 | { 887 | "title": "Urdu", 888 | "value": "ur" 889 | }, 890 | { 891 | "title": "Uyghur", 892 | "value": "ug" 893 | }, 894 | { 895 | "title": "Uzbek", 896 | "value": "uz" 897 | }, 898 | { 899 | "title": "Vietnamese", 900 | "value": "vi" 901 | }, 902 | { 903 | "title": "Welsh", 904 | "value": "cy" 905 | }, 906 | { 907 | "title": "Xhosa", 908 | "value": "xh" 909 | }, 910 | { 911 | "title": "Yiddish", 912 | "value": "yi" 913 | }, 914 | { 915 | "title": "Yoruba", 916 | "value": "yo" 917 | }, 918 | { 919 | "title": "Zulu", 920 | "value": "zu" 921 | } 922 | ], 923 | "required": false, 924 | "default": "none" 925 | }, 926 | { 927 | "name": "lang3", 928 | "type": "dropdown", 929 | "title": "Language 3", 930 | "description": "Language 3", 931 | "data": [ 932 | { 933 | "title": "-", 934 | "value": "none" 935 | }, 936 | { 937 | "title": "Afrikaans", 938 | "value": "af" 939 | }, 940 | { 941 | "title": "Albanian", 942 | "value": "sq" 943 | }, 944 | { 945 | "title": "Amharic", 946 | "value": "am" 947 | }, 948 | { 949 | "title": "Arabic", 950 | "value": "ar" 951 | }, 952 | { 953 | "title": "Armenian", 954 | "value": "hy" 955 | }, 956 | { 957 | "title": "Azerbaijani", 958 | "value": "az" 959 | }, 960 | { 961 | "title": "Basque", 962 | "value": "eu" 963 | }, 964 | { 965 | "title": "Belarusian", 966 | "value": "be" 967 | }, 968 | { 969 | "title": "Bengali", 970 | "value": "bn" 971 | }, 972 | { 973 | "title": "Bosnian", 974 | "value": "bs" 975 | }, 976 | { 977 | "title": "Bulgarian", 978 | "value": "bg" 979 | }, 980 | { 981 | "title": "Catalan", 982 | "value": "ca" 983 | }, 984 | { 985 | "title": "Cebuano", 986 | "value": "ceb" 987 | }, 988 | { 989 | "title": "Chinese Simplified", 990 | "value": "zh-CN" 991 | }, 992 | { 993 | "title": "Taiwanese Mandarin", 994 | "value": "zh-TW" 995 | }, 996 | { 997 | "title": "Corsican", 998 | "value": "co" 999 | }, 1000 | { 1001 | "title": "Croatian", 1002 | "value": "hr" 1003 | }, 1004 | { 1005 | "title": "Czech", 1006 | "value": "cs" 1007 | }, 1008 | { 1009 | "title": "Danish", 1010 | "value": "da" 1011 | }, 1012 | { 1013 | "title": "Dutch", 1014 | "value": "nl" 1015 | }, 1016 | { 1017 | "title": "English", 1018 | "value": "en" 1019 | }, 1020 | { 1021 | "title": "Esperanto", 1022 | "value": "eo" 1023 | }, 1024 | { 1025 | "title": "Estonian", 1026 | "value": "et" 1027 | }, 1028 | { 1029 | "title": "Finnish", 1030 | "value": "fi" 1031 | }, 1032 | { 1033 | "title": "French", 1034 | "value": "fr" 1035 | }, 1036 | { 1037 | "title": "Frisian", 1038 | "value": "fy" 1039 | }, 1040 | { 1041 | "title": "Galician", 1042 | "value": "gl" 1043 | }, 1044 | { 1045 | "title": "Georgian", 1046 | "value": "ka" 1047 | }, 1048 | { 1049 | "title": "German", 1050 | "value": "de" 1051 | }, 1052 | { 1053 | "title": "Greek", 1054 | "value": "el" 1055 | }, 1056 | { 1057 | "title": "Gujarati", 1058 | "value": "gu" 1059 | }, 1060 | { 1061 | "title": "Haitian Creole", 1062 | "value": "ht" 1063 | }, 1064 | { 1065 | "title": "Hausa", 1066 | "value": "ha" 1067 | }, 1068 | { 1069 | "title": "Hawaiian", 1070 | "value": "haw" 1071 | }, 1072 | { 1073 | "title": "Hebrew", 1074 | "value": "he" 1075 | }, 1076 | { 1077 | "title": "Hindi", 1078 | "value": "hi" 1079 | }, 1080 | { 1081 | "title": "Hmong", 1082 | "value": "hmn" 1083 | }, 1084 | { 1085 | "title": "Hungarian", 1086 | "value": "hu" 1087 | }, 1088 | { 1089 | "title": "Icelandic", 1090 | "value": "is" 1091 | }, 1092 | { 1093 | "title": "Igbo", 1094 | "value": "ig" 1095 | }, 1096 | { 1097 | "title": "Indonesian", 1098 | "value": "id" 1099 | }, 1100 | { 1101 | "title": "Irish", 1102 | "value": "ga" 1103 | }, 1104 | { 1105 | "title": "Italian", 1106 | "value": "it" 1107 | }, 1108 | { 1109 | "title": "Japanese", 1110 | "value": "ja" 1111 | }, 1112 | { 1113 | "title": "Javanese", 1114 | "value": "jv" 1115 | }, 1116 | { 1117 | "title": "Kannada", 1118 | "value": "kn" 1119 | }, 1120 | { 1121 | "title": "Kazakh", 1122 | "value": "kk" 1123 | }, 1124 | { 1125 | "title": "Khmer", 1126 | "value": "km" 1127 | }, 1128 | { 1129 | "title": "Kinyarwanda", 1130 | "value": "rw" 1131 | }, 1132 | { 1133 | "title": "Korean", 1134 | "value": "ko" 1135 | }, 1136 | { 1137 | "title": "Kurdish", 1138 | "value": "ku" 1139 | }, 1140 | { 1141 | "title": "Kyrgyz", 1142 | "value": "ky" 1143 | }, 1144 | { 1145 | "title": "Lao", 1146 | "value": "lo" 1147 | }, 1148 | { 1149 | "title": "Latvian", 1150 | "value": "lv" 1151 | }, 1152 | { 1153 | "title": "Lithuanian", 1154 | "value": "lt" 1155 | }, 1156 | { 1157 | "title": "Luxembourgish", 1158 | "value": "lb" 1159 | }, 1160 | { 1161 | "title": "Macedonian", 1162 | "value": "mk" 1163 | }, 1164 | { 1165 | "title": "Malagasy", 1166 | "value": "mg" 1167 | }, 1168 | { 1169 | "title": "Malay", 1170 | "value": "ms" 1171 | }, 1172 | { 1173 | "title": "Malayalam", 1174 | "value": "ml" 1175 | }, 1176 | { 1177 | "title": "Maltese", 1178 | "value": "mt" 1179 | }, 1180 | { 1181 | "title": "Maori", 1182 | "value": "mi" 1183 | }, 1184 | { 1185 | "title": "Marathi", 1186 | "value": "mr" 1187 | }, 1188 | { 1189 | "title": "Mongolian", 1190 | "value": "mn" 1191 | }, 1192 | { 1193 | "title": "Myanmar (Burmese)", 1194 | "value": "my" 1195 | }, 1196 | { 1197 | "title": "Nepali", 1198 | "value": "ne" 1199 | }, 1200 | { 1201 | "title": "Norwegian", 1202 | "value": "no" 1203 | }, 1204 | { 1205 | "title": "Nyanja (Chichewa)", 1206 | "value": "ny" 1207 | }, 1208 | { 1209 | "title": "Odia (Oriya)", 1210 | "value": "or" 1211 | }, 1212 | { 1213 | "title": "Pashto", 1214 | "value": "ps" 1215 | }, 1216 | { 1217 | "title": "Persian", 1218 | "value": "fa" 1219 | }, 1220 | { 1221 | "title": "Polish", 1222 | "value": "pl" 1223 | }, 1224 | { 1225 | "title": "Portuguese", 1226 | "value": "pt" 1227 | }, 1228 | { 1229 | "title": "Punjabi", 1230 | "value": "pa" 1231 | }, 1232 | { 1233 | "title": "Romanian", 1234 | "value": "ro" 1235 | }, 1236 | { 1237 | "title": "Russian", 1238 | "value": "ru" 1239 | }, 1240 | { 1241 | "title": "Samoan", 1242 | "value": "sm" 1243 | }, 1244 | { 1245 | "title": "Scots Gaelic", 1246 | "value": "gd" 1247 | }, 1248 | { 1249 | "title": "Serbian", 1250 | "value": "sr" 1251 | }, 1252 | { 1253 | "title": "Sesotho", 1254 | "value": "st" 1255 | }, 1256 | { 1257 | "title": "Shona", 1258 | "value": "sn" 1259 | }, 1260 | { 1261 | "title": "Sindhi", 1262 | "value": "sd" 1263 | }, 1264 | { 1265 | "title": "Sinhala (Sinhalese)", 1266 | "value": "si" 1267 | }, 1268 | { 1269 | "title": "Slovak", 1270 | "value": "sk" 1271 | }, 1272 | { 1273 | "title": "Slovenian", 1274 | "value": "sl" 1275 | }, 1276 | { 1277 | "title": "Somali", 1278 | "value": "so" 1279 | }, 1280 | { 1281 | "title": "Spanish", 1282 | "value": "es" 1283 | }, 1284 | { 1285 | "title": "Sundanese", 1286 | "value": "su" 1287 | }, 1288 | { 1289 | "title": "Swahili", 1290 | "value": "sw" 1291 | }, 1292 | { 1293 | "title": "Swedish", 1294 | "value": "sv" 1295 | }, 1296 | { 1297 | "title": "Tagalog (Filipino)", 1298 | "value": "tl" 1299 | }, 1300 | { 1301 | "title": "Tajik", 1302 | "value": "tg" 1303 | }, 1304 | { 1305 | "title": "Tamil", 1306 | "value": "ta" 1307 | }, 1308 | { 1309 | "title": "Tatar", 1310 | "value": "tt" 1311 | }, 1312 | { 1313 | "title": "Telugu", 1314 | "value": "te" 1315 | }, 1316 | { 1317 | "title": "Thai", 1318 | "value": "th" 1319 | }, 1320 | { 1321 | "title": "Turkish", 1322 | "value": "tr" 1323 | }, 1324 | { 1325 | "title": "Turkmen", 1326 | "value": "tk" 1327 | }, 1328 | { 1329 | "title": "Ukrainian", 1330 | "value": "uk" 1331 | }, 1332 | { 1333 | "title": "Urdu", 1334 | "value": "ur" 1335 | }, 1336 | { 1337 | "title": "Uyghur", 1338 | "value": "ug" 1339 | }, 1340 | { 1341 | "title": "Uzbek", 1342 | "value": "uz" 1343 | }, 1344 | { 1345 | "title": "Vietnamese", 1346 | "value": "vi" 1347 | }, 1348 | { 1349 | "title": "Welsh", 1350 | "value": "cy" 1351 | }, 1352 | { 1353 | "title": "Xhosa", 1354 | "value": "xh" 1355 | }, 1356 | { 1357 | "title": "Yiddish", 1358 | "value": "yi" 1359 | }, 1360 | { 1361 | "title": "Yoruba", 1362 | "value": "yo" 1363 | }, 1364 | { 1365 | "title": "Zulu", 1366 | "value": "zu" 1367 | } 1368 | ], 1369 | "required": false, 1370 | "default": "none" 1371 | }, 1372 | { 1373 | "name": "lang4", 1374 | "type": "dropdown", 1375 | "title": "Language 4", 1376 | "description": "Language 4", 1377 | "data": [ 1378 | { 1379 | "title": "-", 1380 | "value": "none" 1381 | }, 1382 | { 1383 | "title": "Afrikaans", 1384 | "value": "af" 1385 | }, 1386 | { 1387 | "title": "Albanian", 1388 | "value": "sq" 1389 | }, 1390 | { 1391 | "title": "Amharic", 1392 | "value": "am" 1393 | }, 1394 | { 1395 | "title": "Arabic", 1396 | "value": "ar" 1397 | }, 1398 | { 1399 | "title": "Armenian", 1400 | "value": "hy" 1401 | }, 1402 | { 1403 | "title": "Azerbaijani", 1404 | "value": "az" 1405 | }, 1406 | { 1407 | "title": "Basque", 1408 | "value": "eu" 1409 | }, 1410 | { 1411 | "title": "Belarusian", 1412 | "value": "be" 1413 | }, 1414 | { 1415 | "title": "Bengali", 1416 | "value": "bn" 1417 | }, 1418 | { 1419 | "title": "Bosnian", 1420 | "value": "bs" 1421 | }, 1422 | { 1423 | "title": "Bulgarian", 1424 | "value": "bg" 1425 | }, 1426 | { 1427 | "title": "Catalan", 1428 | "value": "ca" 1429 | }, 1430 | { 1431 | "title": "Cebuano", 1432 | "value": "ceb" 1433 | }, 1434 | { 1435 | "title": "Chinese Simplified", 1436 | "value": "zh-CN" 1437 | }, 1438 | { 1439 | "title": "Taiwanese Mandarin", 1440 | "value": "zh-TW" 1441 | }, 1442 | { 1443 | "title": "Corsican", 1444 | "value": "co" 1445 | }, 1446 | { 1447 | "title": "Croatian", 1448 | "value": "hr" 1449 | }, 1450 | { 1451 | "title": "Czech", 1452 | "value": "cs" 1453 | }, 1454 | { 1455 | "title": "Danish", 1456 | "value": "da" 1457 | }, 1458 | { 1459 | "title": "Dutch", 1460 | "value": "nl" 1461 | }, 1462 | { 1463 | "title": "English", 1464 | "value": "en" 1465 | }, 1466 | { 1467 | "title": "Esperanto", 1468 | "value": "eo" 1469 | }, 1470 | { 1471 | "title": "Estonian", 1472 | "value": "et" 1473 | }, 1474 | { 1475 | "title": "Finnish", 1476 | "value": "fi" 1477 | }, 1478 | { 1479 | "title": "French", 1480 | "value": "fr" 1481 | }, 1482 | { 1483 | "title": "Frisian", 1484 | "value": "fy" 1485 | }, 1486 | { 1487 | "title": "Galician", 1488 | "value": "gl" 1489 | }, 1490 | { 1491 | "title": "Georgian", 1492 | "value": "ka" 1493 | }, 1494 | { 1495 | "title": "German", 1496 | "value": "de" 1497 | }, 1498 | { 1499 | "title": "Greek", 1500 | "value": "el" 1501 | }, 1502 | { 1503 | "title": "Gujarati", 1504 | "value": "gu" 1505 | }, 1506 | { 1507 | "title": "Haitian Creole", 1508 | "value": "ht" 1509 | }, 1510 | { 1511 | "title": "Hausa", 1512 | "value": "ha" 1513 | }, 1514 | { 1515 | "title": "Hawaiian", 1516 | "value": "haw" 1517 | }, 1518 | { 1519 | "title": "Hebrew", 1520 | "value": "he" 1521 | }, 1522 | { 1523 | "title": "Hindi", 1524 | "value": "hi" 1525 | }, 1526 | { 1527 | "title": "Hmong", 1528 | "value": "hmn" 1529 | }, 1530 | { 1531 | "title": "Hungarian", 1532 | "value": "hu" 1533 | }, 1534 | { 1535 | "title": "Icelandic", 1536 | "value": "is" 1537 | }, 1538 | { 1539 | "title": "Igbo", 1540 | "value": "ig" 1541 | }, 1542 | { 1543 | "title": "Indonesian", 1544 | "value": "id" 1545 | }, 1546 | { 1547 | "title": "Irish", 1548 | "value": "ga" 1549 | }, 1550 | { 1551 | "title": "Italian", 1552 | "value": "it" 1553 | }, 1554 | { 1555 | "title": "Japanese", 1556 | "value": "ja" 1557 | }, 1558 | { 1559 | "title": "Javanese", 1560 | "value": "jv" 1561 | }, 1562 | { 1563 | "title": "Kannada", 1564 | "value": "kn" 1565 | }, 1566 | { 1567 | "title": "Kazakh", 1568 | "value": "kk" 1569 | }, 1570 | { 1571 | "title": "Khmer", 1572 | "value": "km" 1573 | }, 1574 | { 1575 | "title": "Kinyarwanda", 1576 | "value": "rw" 1577 | }, 1578 | { 1579 | "title": "Korean", 1580 | "value": "ko" 1581 | }, 1582 | { 1583 | "title": "Kurdish", 1584 | "value": "ku" 1585 | }, 1586 | { 1587 | "title": "Kyrgyz", 1588 | "value": "ky" 1589 | }, 1590 | { 1591 | "title": "Lao", 1592 | "value": "lo" 1593 | }, 1594 | { 1595 | "title": "Latvian", 1596 | "value": "lv" 1597 | }, 1598 | { 1599 | "title": "Lithuanian", 1600 | "value": "lt" 1601 | }, 1602 | { 1603 | "title": "Luxembourgish", 1604 | "value": "lb" 1605 | }, 1606 | { 1607 | "title": "Macedonian", 1608 | "value": "mk" 1609 | }, 1610 | { 1611 | "title": "Malagasy", 1612 | "value": "mg" 1613 | }, 1614 | { 1615 | "title": "Malay", 1616 | "value": "ms" 1617 | }, 1618 | { 1619 | "title": "Malayalam", 1620 | "value": "ml" 1621 | }, 1622 | { 1623 | "title": "Maltese", 1624 | "value": "mt" 1625 | }, 1626 | { 1627 | "title": "Maori", 1628 | "value": "mi" 1629 | }, 1630 | { 1631 | "title": "Marathi", 1632 | "value": "mr" 1633 | }, 1634 | { 1635 | "title": "Mongolian", 1636 | "value": "mn" 1637 | }, 1638 | { 1639 | "title": "Myanmar (Burmese)", 1640 | "value": "my" 1641 | }, 1642 | { 1643 | "title": "Nepali", 1644 | "value": "ne" 1645 | }, 1646 | { 1647 | "title": "Norwegian", 1648 | "value": "no" 1649 | }, 1650 | { 1651 | "title": "Nyanja (Chichewa)", 1652 | "value": "ny" 1653 | }, 1654 | { 1655 | "title": "Odia (Oriya)", 1656 | "value": "or" 1657 | }, 1658 | { 1659 | "title": "Pashto", 1660 | "value": "ps" 1661 | }, 1662 | { 1663 | "title": "Persian", 1664 | "value": "fa" 1665 | }, 1666 | { 1667 | "title": "Polish", 1668 | "value": "pl" 1669 | }, 1670 | { 1671 | "title": "Portuguese", 1672 | "value": "pt" 1673 | }, 1674 | { 1675 | "title": "Punjabi", 1676 | "value": "pa" 1677 | }, 1678 | { 1679 | "title": "Romanian", 1680 | "value": "ro" 1681 | }, 1682 | { 1683 | "title": "Russian", 1684 | "value": "ru" 1685 | }, 1686 | { 1687 | "title": "Samoan", 1688 | "value": "sm" 1689 | }, 1690 | { 1691 | "title": "Scots Gaelic", 1692 | "value": "gd" 1693 | }, 1694 | { 1695 | "title": "Serbian", 1696 | "value": "sr" 1697 | }, 1698 | { 1699 | "title": "Sesotho", 1700 | "value": "st" 1701 | }, 1702 | { 1703 | "title": "Shona", 1704 | "value": "sn" 1705 | }, 1706 | { 1707 | "title": "Sindhi", 1708 | "value": "sd" 1709 | }, 1710 | { 1711 | "title": "Sinhala (Sinhalese)", 1712 | "value": "si" 1713 | }, 1714 | { 1715 | "title": "Slovak", 1716 | "value": "sk" 1717 | }, 1718 | { 1719 | "title": "Slovenian", 1720 | "value": "sl" 1721 | }, 1722 | { 1723 | "title": "Somali", 1724 | "value": "so" 1725 | }, 1726 | { 1727 | "title": "Spanish", 1728 | "value": "es" 1729 | }, 1730 | { 1731 | "title": "Sundanese", 1732 | "value": "su" 1733 | }, 1734 | { 1735 | "title": "Swahili", 1736 | "value": "sw" 1737 | }, 1738 | { 1739 | "title": "Swedish", 1740 | "value": "sv" 1741 | }, 1742 | { 1743 | "title": "Tagalog (Filipino)", 1744 | "value": "tl" 1745 | }, 1746 | { 1747 | "title": "Tajik", 1748 | "value": "tg" 1749 | }, 1750 | { 1751 | "title": "Tamil", 1752 | "value": "ta" 1753 | }, 1754 | { 1755 | "title": "Tatar", 1756 | "value": "tt" 1757 | }, 1758 | { 1759 | "title": "Telugu", 1760 | "value": "te" 1761 | }, 1762 | { 1763 | "title": "Thai", 1764 | "value": "th" 1765 | }, 1766 | { 1767 | "title": "Turkish", 1768 | "value": "tr" 1769 | }, 1770 | { 1771 | "title": "Turkmen", 1772 | "value": "tk" 1773 | }, 1774 | { 1775 | "title": "Ukrainian", 1776 | "value": "uk" 1777 | }, 1778 | { 1779 | "title": "Urdu", 1780 | "value": "ur" 1781 | }, 1782 | { 1783 | "title": "Uyghur", 1784 | "value": "ug" 1785 | }, 1786 | { 1787 | "title": "Uzbek", 1788 | "value": "uz" 1789 | }, 1790 | { 1791 | "title": "Vietnamese", 1792 | "value": "vi" 1793 | }, 1794 | { 1795 | "title": "Welsh", 1796 | "value": "cy" 1797 | }, 1798 | { 1799 | "title": "Xhosa", 1800 | "value": "xh" 1801 | }, 1802 | { 1803 | "title": "Yiddish", 1804 | "value": "yi" 1805 | }, 1806 | { 1807 | "title": "Yoruba", 1808 | "value": "yo" 1809 | }, 1810 | { 1811 | "title": "Zulu", 1812 | "value": "zu" 1813 | } 1814 | ], 1815 | "required": false, 1816 | "default": "none" 1817 | }, 1818 | { 1819 | "name": "lang5", 1820 | "type": "dropdown", 1821 | "title": "Language 5", 1822 | "description": "Language 5", 1823 | "data": [ 1824 | { 1825 | "title": "-", 1826 | "value": "none" 1827 | }, 1828 | { 1829 | "title": "Afrikaans", 1830 | "value": "af" 1831 | }, 1832 | { 1833 | "title": "Albanian", 1834 | "value": "sq" 1835 | }, 1836 | { 1837 | "title": "Amharic", 1838 | "value": "am" 1839 | }, 1840 | { 1841 | "title": "Arabic", 1842 | "value": "ar" 1843 | }, 1844 | { 1845 | "title": "Armenian", 1846 | "value": "hy" 1847 | }, 1848 | { 1849 | "title": "Azerbaijani", 1850 | "value": "az" 1851 | }, 1852 | { 1853 | "title": "Basque", 1854 | "value": "eu" 1855 | }, 1856 | { 1857 | "title": "Belarusian", 1858 | "value": "be" 1859 | }, 1860 | { 1861 | "title": "Bengali", 1862 | "value": "bn" 1863 | }, 1864 | { 1865 | "title": "Bosnian", 1866 | "value": "bs" 1867 | }, 1868 | { 1869 | "title": "Bulgarian", 1870 | "value": "bg" 1871 | }, 1872 | { 1873 | "title": "Catalan", 1874 | "value": "ca" 1875 | }, 1876 | { 1877 | "title": "Cebuano", 1878 | "value": "ceb" 1879 | }, 1880 | { 1881 | "title": "Chinese Simplified", 1882 | "value": "zh-CN" 1883 | }, 1884 | { 1885 | "title": "Taiwanese Mandarin", 1886 | "value": "zh-TW" 1887 | }, 1888 | { 1889 | "title": "Corsican", 1890 | "value": "co" 1891 | }, 1892 | { 1893 | "title": "Croatian", 1894 | "value": "hr" 1895 | }, 1896 | { 1897 | "title": "Czech", 1898 | "value": "cs" 1899 | }, 1900 | { 1901 | "title": "Danish", 1902 | "value": "da" 1903 | }, 1904 | { 1905 | "title": "Dutch", 1906 | "value": "nl" 1907 | }, 1908 | { 1909 | "title": "English", 1910 | "value": "en" 1911 | }, 1912 | { 1913 | "title": "Esperanto", 1914 | "value": "eo" 1915 | }, 1916 | { 1917 | "title": "Estonian", 1918 | "value": "et" 1919 | }, 1920 | { 1921 | "title": "Finnish", 1922 | "value": "fi" 1923 | }, 1924 | { 1925 | "title": "French", 1926 | "value": "fr" 1927 | }, 1928 | { 1929 | "title": "Frisian", 1930 | "value": "fy" 1931 | }, 1932 | { 1933 | "title": "Galician", 1934 | "value": "gl" 1935 | }, 1936 | { 1937 | "title": "Georgian", 1938 | "value": "ka" 1939 | }, 1940 | { 1941 | "title": "German", 1942 | "value": "de" 1943 | }, 1944 | { 1945 | "title": "Greek", 1946 | "value": "el" 1947 | }, 1948 | { 1949 | "title": "Gujarati", 1950 | "value": "gu" 1951 | }, 1952 | { 1953 | "title": "Haitian Creole", 1954 | "value": "ht" 1955 | }, 1956 | { 1957 | "title": "Hausa", 1958 | "value": "ha" 1959 | }, 1960 | { 1961 | "title": "Hawaiian", 1962 | "value": "haw" 1963 | }, 1964 | { 1965 | "title": "Hebrew", 1966 | "value": "he" 1967 | }, 1968 | { 1969 | "title": "Hindi", 1970 | "value": "hi" 1971 | }, 1972 | { 1973 | "title": "Hmong", 1974 | "value": "hmn" 1975 | }, 1976 | { 1977 | "title": "Hungarian", 1978 | "value": "hu" 1979 | }, 1980 | { 1981 | "title": "Icelandic", 1982 | "value": "is" 1983 | }, 1984 | { 1985 | "title": "Igbo", 1986 | "value": "ig" 1987 | }, 1988 | { 1989 | "title": "Indonesian", 1990 | "value": "id" 1991 | }, 1992 | { 1993 | "title": "Irish", 1994 | "value": "ga" 1995 | }, 1996 | { 1997 | "title": "Italian", 1998 | "value": "it" 1999 | }, 2000 | { 2001 | "title": "Japanese", 2002 | "value": "ja" 2003 | }, 2004 | { 2005 | "title": "Javanese", 2006 | "value": "jv" 2007 | }, 2008 | { 2009 | "title": "Kannada", 2010 | "value": "kn" 2011 | }, 2012 | { 2013 | "title": "Kazakh", 2014 | "value": "kk" 2015 | }, 2016 | { 2017 | "title": "Khmer", 2018 | "value": "km" 2019 | }, 2020 | { 2021 | "title": "Kinyarwanda", 2022 | "value": "rw" 2023 | }, 2024 | { 2025 | "title": "Korean", 2026 | "value": "ko" 2027 | }, 2028 | { 2029 | "title": "Kurdish", 2030 | "value": "ku" 2031 | }, 2032 | { 2033 | "title": "Kyrgyz", 2034 | "value": "ky" 2035 | }, 2036 | { 2037 | "title": "Lao", 2038 | "value": "lo" 2039 | }, 2040 | { 2041 | "title": "Latvian", 2042 | "value": "lv" 2043 | }, 2044 | { 2045 | "title": "Lithuanian", 2046 | "value": "lt" 2047 | }, 2048 | { 2049 | "title": "Luxembourgish", 2050 | "value": "lb" 2051 | }, 2052 | { 2053 | "title": "Macedonian", 2054 | "value": "mk" 2055 | }, 2056 | { 2057 | "title": "Malagasy", 2058 | "value": "mg" 2059 | }, 2060 | { 2061 | "title": "Malay", 2062 | "value": "ms" 2063 | }, 2064 | { 2065 | "title": "Malayalam", 2066 | "value": "ml" 2067 | }, 2068 | { 2069 | "title": "Maltese", 2070 | "value": "mt" 2071 | }, 2072 | { 2073 | "title": "Maori", 2074 | "value": "mi" 2075 | }, 2076 | { 2077 | "title": "Marathi", 2078 | "value": "mr" 2079 | }, 2080 | { 2081 | "title": "Mongolian", 2082 | "value": "mn" 2083 | }, 2084 | { 2085 | "title": "Myanmar (Burmese)", 2086 | "value": "my" 2087 | }, 2088 | { 2089 | "title": "Nepali", 2090 | "value": "ne" 2091 | }, 2092 | { 2093 | "title": "Norwegian", 2094 | "value": "no" 2095 | }, 2096 | { 2097 | "title": "Nyanja (Chichewa)", 2098 | "value": "ny" 2099 | }, 2100 | { 2101 | "title": "Odia (Oriya)", 2102 | "value": "or" 2103 | }, 2104 | { 2105 | "title": "Pashto", 2106 | "value": "ps" 2107 | }, 2108 | { 2109 | "title": "Persian", 2110 | "value": "fa" 2111 | }, 2112 | { 2113 | "title": "Polish", 2114 | "value": "pl" 2115 | }, 2116 | { 2117 | "title": "Portuguese", 2118 | "value": "pt" 2119 | }, 2120 | { 2121 | "title": "Punjabi", 2122 | "value": "pa" 2123 | }, 2124 | { 2125 | "title": "Romanian", 2126 | "value": "ro" 2127 | }, 2128 | { 2129 | "title": "Russian", 2130 | "value": "ru" 2131 | }, 2132 | { 2133 | "title": "Samoan", 2134 | "value": "sm" 2135 | }, 2136 | { 2137 | "title": "Scots Gaelic", 2138 | "value": "gd" 2139 | }, 2140 | { 2141 | "title": "Serbian", 2142 | "value": "sr" 2143 | }, 2144 | { 2145 | "title": "Sesotho", 2146 | "value": "st" 2147 | }, 2148 | { 2149 | "title": "Shona", 2150 | "value": "sn" 2151 | }, 2152 | { 2153 | "title": "Sindhi", 2154 | "value": "sd" 2155 | }, 2156 | { 2157 | "title": "Sinhala (Sinhalese)", 2158 | "value": "si" 2159 | }, 2160 | { 2161 | "title": "Slovak", 2162 | "value": "sk" 2163 | }, 2164 | { 2165 | "title": "Slovenian", 2166 | "value": "sl" 2167 | }, 2168 | { 2169 | "title": "Somali", 2170 | "value": "so" 2171 | }, 2172 | { 2173 | "title": "Spanish", 2174 | "value": "es" 2175 | }, 2176 | { 2177 | "title": "Sundanese", 2178 | "value": "su" 2179 | }, 2180 | { 2181 | "title": "Swahili", 2182 | "value": "sw" 2183 | }, 2184 | { 2185 | "title": "Swedish", 2186 | "value": "sv" 2187 | }, 2188 | { 2189 | "title": "Tagalog (Filipino)", 2190 | "value": "tl" 2191 | }, 2192 | { 2193 | "title": "Tajik", 2194 | "value": "tg" 2195 | }, 2196 | { 2197 | "title": "Tamil", 2198 | "value": "ta" 2199 | }, 2200 | { 2201 | "title": "Tatar", 2202 | "value": "tt" 2203 | }, 2204 | { 2205 | "title": "Telugu", 2206 | "value": "te" 2207 | }, 2208 | { 2209 | "title": "Thai", 2210 | "value": "th" 2211 | }, 2212 | { 2213 | "title": "Turkish", 2214 | "value": "tr" 2215 | }, 2216 | { 2217 | "title": "Turkmen", 2218 | "value": "tk" 2219 | }, 2220 | { 2221 | "title": "Ukrainian", 2222 | "value": "uk" 2223 | }, 2224 | { 2225 | "title": "Urdu", 2226 | "value": "ur" 2227 | }, 2228 | { 2229 | "title": "Uyghur", 2230 | "value": "ug" 2231 | }, 2232 | { 2233 | "title": "Uzbek", 2234 | "value": "uz" 2235 | }, 2236 | { 2237 | "title": "Vietnamese", 2238 | "value": "vi" 2239 | }, 2240 | { 2241 | "title": "Welsh", 2242 | "value": "cy" 2243 | }, 2244 | { 2245 | "title": "Xhosa", 2246 | "value": "xh" 2247 | }, 2248 | { 2249 | "title": "Yiddish", 2250 | "value": "yi" 2251 | }, 2252 | { 2253 | "title": "Yoruba", 2254 | "value": "yo" 2255 | }, 2256 | { 2257 | "title": "Zulu", 2258 | "value": "zu" 2259 | } 2260 | ], 2261 | "required": false, 2262 | "default": "none" 2263 | }, 2264 | { 2265 | "name": "lang6", 2266 | "type": "dropdown", 2267 | "title": "Language 6", 2268 | "description": "Language 6", 2269 | "data": [ 2270 | { 2271 | "title": "-", 2272 | "value": "none" 2273 | }, 2274 | { 2275 | "title": "Afrikaans", 2276 | "value": "af" 2277 | }, 2278 | { 2279 | "title": "Albanian", 2280 | "value": "sq" 2281 | }, 2282 | { 2283 | "title": "Amharic", 2284 | "value": "am" 2285 | }, 2286 | { 2287 | "title": "Arabic", 2288 | "value": "ar" 2289 | }, 2290 | { 2291 | "title": "Armenian", 2292 | "value": "hy" 2293 | }, 2294 | { 2295 | "title": "Azerbaijani", 2296 | "value": "az" 2297 | }, 2298 | { 2299 | "title": "Basque", 2300 | "value": "eu" 2301 | }, 2302 | { 2303 | "title": "Belarusian", 2304 | "value": "be" 2305 | }, 2306 | { 2307 | "title": "Bengali", 2308 | "value": "bn" 2309 | }, 2310 | { 2311 | "title": "Bosnian", 2312 | "value": "bs" 2313 | }, 2314 | { 2315 | "title": "Bulgarian", 2316 | "value": "bg" 2317 | }, 2318 | { 2319 | "title": "Catalan", 2320 | "value": "ca" 2321 | }, 2322 | { 2323 | "title": "Cebuano", 2324 | "value": "ceb" 2325 | }, 2326 | { 2327 | "title": "Chinese Simplified", 2328 | "value": "zh-CN" 2329 | }, 2330 | { 2331 | "title": "Taiwanese Mandarin", 2332 | "value": "zh-TW" 2333 | }, 2334 | { 2335 | "title": "Corsican", 2336 | "value": "co" 2337 | }, 2338 | { 2339 | "title": "Croatian", 2340 | "value": "hr" 2341 | }, 2342 | { 2343 | "title": "Czech", 2344 | "value": "cs" 2345 | }, 2346 | { 2347 | "title": "Danish", 2348 | "value": "da" 2349 | }, 2350 | { 2351 | "title": "Dutch", 2352 | "value": "nl" 2353 | }, 2354 | { 2355 | "title": "English", 2356 | "value": "en" 2357 | }, 2358 | { 2359 | "title": "Esperanto", 2360 | "value": "eo" 2361 | }, 2362 | { 2363 | "title": "Estonian", 2364 | "value": "et" 2365 | }, 2366 | { 2367 | "title": "Finnish", 2368 | "value": "fi" 2369 | }, 2370 | { 2371 | "title": "French", 2372 | "value": "fr" 2373 | }, 2374 | { 2375 | "title": "Frisian", 2376 | "value": "fy" 2377 | }, 2378 | { 2379 | "title": "Galician", 2380 | "value": "gl" 2381 | }, 2382 | { 2383 | "title": "Georgian", 2384 | "value": "ka" 2385 | }, 2386 | { 2387 | "title": "German", 2388 | "value": "de" 2389 | }, 2390 | { 2391 | "title": "Greek", 2392 | "value": "el" 2393 | }, 2394 | { 2395 | "title": "Gujarati", 2396 | "value": "gu" 2397 | }, 2398 | { 2399 | "title": "Haitian Creole", 2400 | "value": "ht" 2401 | }, 2402 | { 2403 | "title": "Hausa", 2404 | "value": "ha" 2405 | }, 2406 | { 2407 | "title": "Hawaiian", 2408 | "value": "haw" 2409 | }, 2410 | { 2411 | "title": "Hebrew", 2412 | "value": "he" 2413 | }, 2414 | { 2415 | "title": "Hindi", 2416 | "value": "hi" 2417 | }, 2418 | { 2419 | "title": "Hmong", 2420 | "value": "hmn" 2421 | }, 2422 | { 2423 | "title": "Hungarian", 2424 | "value": "hu" 2425 | }, 2426 | { 2427 | "title": "Icelandic", 2428 | "value": "is" 2429 | }, 2430 | { 2431 | "title": "Igbo", 2432 | "value": "ig" 2433 | }, 2434 | { 2435 | "title": "Indonesian", 2436 | "value": "id" 2437 | }, 2438 | { 2439 | "title": "Irish", 2440 | "value": "ga" 2441 | }, 2442 | { 2443 | "title": "Italian", 2444 | "value": "it" 2445 | }, 2446 | { 2447 | "title": "Japanese", 2448 | "value": "ja" 2449 | }, 2450 | { 2451 | "title": "Javanese", 2452 | "value": "jv" 2453 | }, 2454 | { 2455 | "title": "Kannada", 2456 | "value": "kn" 2457 | }, 2458 | { 2459 | "title": "Kazakh", 2460 | "value": "kk" 2461 | }, 2462 | { 2463 | "title": "Khmer", 2464 | "value": "km" 2465 | }, 2466 | { 2467 | "title": "Kinyarwanda", 2468 | "value": "rw" 2469 | }, 2470 | { 2471 | "title": "Korean", 2472 | "value": "ko" 2473 | }, 2474 | { 2475 | "title": "Kurdish", 2476 | "value": "ku" 2477 | }, 2478 | { 2479 | "title": "Kyrgyz", 2480 | "value": "ky" 2481 | }, 2482 | { 2483 | "title": "Lao", 2484 | "value": "lo" 2485 | }, 2486 | { 2487 | "title": "Latvian", 2488 | "value": "lv" 2489 | }, 2490 | { 2491 | "title": "Lithuanian", 2492 | "value": "lt" 2493 | }, 2494 | { 2495 | "title": "Luxembourgish", 2496 | "value": "lb" 2497 | }, 2498 | { 2499 | "title": "Macedonian", 2500 | "value": "mk" 2501 | }, 2502 | { 2503 | "title": "Malagasy", 2504 | "value": "mg" 2505 | }, 2506 | { 2507 | "title": "Malay", 2508 | "value": "ms" 2509 | }, 2510 | { 2511 | "title": "Malayalam", 2512 | "value": "ml" 2513 | }, 2514 | { 2515 | "title": "Maltese", 2516 | "value": "mt" 2517 | }, 2518 | { 2519 | "title": "Maori", 2520 | "value": "mi" 2521 | }, 2522 | { 2523 | "title": "Marathi", 2524 | "value": "mr" 2525 | }, 2526 | { 2527 | "title": "Mongolian", 2528 | "value": "mn" 2529 | }, 2530 | { 2531 | "title": "Myanmar (Burmese)", 2532 | "value": "my" 2533 | }, 2534 | { 2535 | "title": "Nepali", 2536 | "value": "ne" 2537 | }, 2538 | { 2539 | "title": "Norwegian", 2540 | "value": "no" 2541 | }, 2542 | { 2543 | "title": "Nyanja (Chichewa)", 2544 | "value": "ny" 2545 | }, 2546 | { 2547 | "title": "Odia (Oriya)", 2548 | "value": "or" 2549 | }, 2550 | { 2551 | "title": "Pashto", 2552 | "value": "ps" 2553 | }, 2554 | { 2555 | "title": "Persian", 2556 | "value": "fa" 2557 | }, 2558 | { 2559 | "title": "Polish", 2560 | "value": "pl" 2561 | }, 2562 | { 2563 | "title": "Portuguese", 2564 | "value": "pt" 2565 | }, 2566 | { 2567 | "title": "Punjabi", 2568 | "value": "pa" 2569 | }, 2570 | { 2571 | "title": "Romanian", 2572 | "value": "ro" 2573 | }, 2574 | { 2575 | "title": "Russian", 2576 | "value": "ru" 2577 | }, 2578 | { 2579 | "title": "Samoan", 2580 | "value": "sm" 2581 | }, 2582 | { 2583 | "title": "Scots Gaelic", 2584 | "value": "gd" 2585 | }, 2586 | { 2587 | "title": "Serbian", 2588 | "value": "sr" 2589 | }, 2590 | { 2591 | "title": "Sesotho", 2592 | "value": "st" 2593 | }, 2594 | { 2595 | "title": "Shona", 2596 | "value": "sn" 2597 | }, 2598 | { 2599 | "title": "Sindhi", 2600 | "value": "sd" 2601 | }, 2602 | { 2603 | "title": "Sinhala (Sinhalese)", 2604 | "value": "si" 2605 | }, 2606 | { 2607 | "title": "Slovak", 2608 | "value": "sk" 2609 | }, 2610 | { 2611 | "title": "Slovenian", 2612 | "value": "sl" 2613 | }, 2614 | { 2615 | "title": "Somali", 2616 | "value": "so" 2617 | }, 2618 | { 2619 | "title": "Spanish", 2620 | "value": "es" 2621 | }, 2622 | { 2623 | "title": "Sundanese", 2624 | "value": "su" 2625 | }, 2626 | { 2627 | "title": "Swahili", 2628 | "value": "sw" 2629 | }, 2630 | { 2631 | "title": "Swedish", 2632 | "value": "sv" 2633 | }, 2634 | { 2635 | "title": "Tagalog (Filipino)", 2636 | "value": "tl" 2637 | }, 2638 | { 2639 | "title": "Tajik", 2640 | "value": "tg" 2641 | }, 2642 | { 2643 | "title": "Tamil", 2644 | "value": "ta" 2645 | }, 2646 | { 2647 | "title": "Tatar", 2648 | "value": "tt" 2649 | }, 2650 | { 2651 | "title": "Telugu", 2652 | "value": "te" 2653 | }, 2654 | { 2655 | "title": "Thai", 2656 | "value": "th" 2657 | }, 2658 | { 2659 | "title": "Turkish", 2660 | "value": "tr" 2661 | }, 2662 | { 2663 | "title": "Turkmen", 2664 | "value": "tk" 2665 | }, 2666 | { 2667 | "title": "Ukrainian", 2668 | "value": "uk" 2669 | }, 2670 | { 2671 | "title": "Urdu", 2672 | "value": "ur" 2673 | }, 2674 | { 2675 | "title": "Uyghur", 2676 | "value": "ug" 2677 | }, 2678 | { 2679 | "title": "Uzbek", 2680 | "value": "uz" 2681 | }, 2682 | { 2683 | "title": "Vietnamese", 2684 | "value": "vi" 2685 | }, 2686 | { 2687 | "title": "Welsh", 2688 | "value": "cy" 2689 | }, 2690 | { 2691 | "title": "Xhosa", 2692 | "value": "xh" 2693 | }, 2694 | { 2695 | "title": "Yiddish", 2696 | "value": "yi" 2697 | }, 2698 | { 2699 | "title": "Yoruba", 2700 | "value": "yo" 2701 | }, 2702 | { 2703 | "title": "Zulu", 2704 | "value": "zu" 2705 | } 2706 | ], 2707 | "required": false, 2708 | "default": "none" 2709 | }, 2710 | { 2711 | "name": "lang7", 2712 | "type": "dropdown", 2713 | "title": "Language 7", 2714 | "description": "Language 7", 2715 | "data": [ 2716 | { 2717 | "title": "-", 2718 | "value": "none" 2719 | }, 2720 | { 2721 | "title": "Afrikaans", 2722 | "value": "af" 2723 | }, 2724 | { 2725 | "title": "Albanian", 2726 | "value": "sq" 2727 | }, 2728 | { 2729 | "title": "Amharic", 2730 | "value": "am" 2731 | }, 2732 | { 2733 | "title": "Arabic", 2734 | "value": "ar" 2735 | }, 2736 | { 2737 | "title": "Armenian", 2738 | "value": "hy" 2739 | }, 2740 | { 2741 | "title": "Azerbaijani", 2742 | "value": "az" 2743 | }, 2744 | { 2745 | "title": "Basque", 2746 | "value": "eu" 2747 | }, 2748 | { 2749 | "title": "Belarusian", 2750 | "value": "be" 2751 | }, 2752 | { 2753 | "title": "Bengali", 2754 | "value": "bn" 2755 | }, 2756 | { 2757 | "title": "Bosnian", 2758 | "value": "bs" 2759 | }, 2760 | { 2761 | "title": "Bulgarian", 2762 | "value": "bg" 2763 | }, 2764 | { 2765 | "title": "Catalan", 2766 | "value": "ca" 2767 | }, 2768 | { 2769 | "title": "Cebuano", 2770 | "value": "ceb" 2771 | }, 2772 | { 2773 | "title": "Chinese Simplified", 2774 | "value": "zh-CN" 2775 | }, 2776 | { 2777 | "title": "Taiwanese Mandarin", 2778 | "value": "zh-TW" 2779 | }, 2780 | { 2781 | "title": "Corsican", 2782 | "value": "co" 2783 | }, 2784 | { 2785 | "title": "Croatian", 2786 | "value": "hr" 2787 | }, 2788 | { 2789 | "title": "Czech", 2790 | "value": "cs" 2791 | }, 2792 | { 2793 | "title": "Danish", 2794 | "value": "da" 2795 | }, 2796 | { 2797 | "title": "Dutch", 2798 | "value": "nl" 2799 | }, 2800 | { 2801 | "title": "English", 2802 | "value": "en" 2803 | }, 2804 | { 2805 | "title": "Esperanto", 2806 | "value": "eo" 2807 | }, 2808 | { 2809 | "title": "Estonian", 2810 | "value": "et" 2811 | }, 2812 | { 2813 | "title": "Finnish", 2814 | "value": "fi" 2815 | }, 2816 | { 2817 | "title": "French", 2818 | "value": "fr" 2819 | }, 2820 | { 2821 | "title": "Frisian", 2822 | "value": "fy" 2823 | }, 2824 | { 2825 | "title": "Galician", 2826 | "value": "gl" 2827 | }, 2828 | { 2829 | "title": "Georgian", 2830 | "value": "ka" 2831 | }, 2832 | { 2833 | "title": "German", 2834 | "value": "de" 2835 | }, 2836 | { 2837 | "title": "Greek", 2838 | "value": "el" 2839 | }, 2840 | { 2841 | "title": "Gujarati", 2842 | "value": "gu" 2843 | }, 2844 | { 2845 | "title": "Haitian Creole", 2846 | "value": "ht" 2847 | }, 2848 | { 2849 | "title": "Hausa", 2850 | "value": "ha" 2851 | }, 2852 | { 2853 | "title": "Hawaiian", 2854 | "value": "haw" 2855 | }, 2856 | { 2857 | "title": "Hebrew", 2858 | "value": "he" 2859 | }, 2860 | { 2861 | "title": "Hindi", 2862 | "value": "hi" 2863 | }, 2864 | { 2865 | "title": "Hmong", 2866 | "value": "hmn" 2867 | }, 2868 | { 2869 | "title": "Hungarian", 2870 | "value": "hu" 2871 | }, 2872 | { 2873 | "title": "Icelandic", 2874 | "value": "is" 2875 | }, 2876 | { 2877 | "title": "Igbo", 2878 | "value": "ig" 2879 | }, 2880 | { 2881 | "title": "Indonesian", 2882 | "value": "id" 2883 | }, 2884 | { 2885 | "title": "Irish", 2886 | "value": "ga" 2887 | }, 2888 | { 2889 | "title": "Italian", 2890 | "value": "it" 2891 | }, 2892 | { 2893 | "title": "Japanese", 2894 | "value": "ja" 2895 | }, 2896 | { 2897 | "title": "Javanese", 2898 | "value": "jv" 2899 | }, 2900 | { 2901 | "title": "Kannada", 2902 | "value": "kn" 2903 | }, 2904 | { 2905 | "title": "Kazakh", 2906 | "value": "kk" 2907 | }, 2908 | { 2909 | "title": "Khmer", 2910 | "value": "km" 2911 | }, 2912 | { 2913 | "title": "Kinyarwanda", 2914 | "value": "rw" 2915 | }, 2916 | { 2917 | "title": "Korean", 2918 | "value": "ko" 2919 | }, 2920 | { 2921 | "title": "Kurdish", 2922 | "value": "ku" 2923 | }, 2924 | { 2925 | "title": "Kyrgyz", 2926 | "value": "ky" 2927 | }, 2928 | { 2929 | "title": "Lao", 2930 | "value": "lo" 2931 | }, 2932 | { 2933 | "title": "Latvian", 2934 | "value": "lv" 2935 | }, 2936 | { 2937 | "title": "Lithuanian", 2938 | "value": "lt" 2939 | }, 2940 | { 2941 | "title": "Luxembourgish", 2942 | "value": "lb" 2943 | }, 2944 | { 2945 | "title": "Macedonian", 2946 | "value": "mk" 2947 | }, 2948 | { 2949 | "title": "Malagasy", 2950 | "value": "mg" 2951 | }, 2952 | { 2953 | "title": "Malay", 2954 | "value": "ms" 2955 | }, 2956 | { 2957 | "title": "Malayalam", 2958 | "value": "ml" 2959 | }, 2960 | { 2961 | "title": "Maltese", 2962 | "value": "mt" 2963 | }, 2964 | { 2965 | "title": "Maori", 2966 | "value": "mi" 2967 | }, 2968 | { 2969 | "title": "Marathi", 2970 | "value": "mr" 2971 | }, 2972 | { 2973 | "title": "Mongolian", 2974 | "value": "mn" 2975 | }, 2976 | { 2977 | "title": "Myanmar (Burmese)", 2978 | "value": "my" 2979 | }, 2980 | { 2981 | "title": "Nepali", 2982 | "value": "ne" 2983 | }, 2984 | { 2985 | "title": "Norwegian", 2986 | "value": "no" 2987 | }, 2988 | { 2989 | "title": "Nyanja (Chichewa)", 2990 | "value": "ny" 2991 | }, 2992 | { 2993 | "title": "Odia (Oriya)", 2994 | "value": "or" 2995 | }, 2996 | { 2997 | "title": "Pashto", 2998 | "value": "ps" 2999 | }, 3000 | { 3001 | "title": "Persian", 3002 | "value": "fa" 3003 | }, 3004 | { 3005 | "title": "Polish", 3006 | "value": "pl" 3007 | }, 3008 | { 3009 | "title": "Portuguese", 3010 | "value": "pt" 3011 | }, 3012 | { 3013 | "title": "Punjabi", 3014 | "value": "pa" 3015 | }, 3016 | { 3017 | "title": "Romanian", 3018 | "value": "ro" 3019 | }, 3020 | { 3021 | "title": "Russian", 3022 | "value": "ru" 3023 | }, 3024 | { 3025 | "title": "Samoan", 3026 | "value": "sm" 3027 | }, 3028 | { 3029 | "title": "Scots Gaelic", 3030 | "value": "gd" 3031 | }, 3032 | { 3033 | "title": "Serbian", 3034 | "value": "sr" 3035 | }, 3036 | { 3037 | "title": "Sesotho", 3038 | "value": "st" 3039 | }, 3040 | { 3041 | "title": "Shona", 3042 | "value": "sn" 3043 | }, 3044 | { 3045 | "title": "Sindhi", 3046 | "value": "sd" 3047 | }, 3048 | { 3049 | "title": "Sinhala (Sinhalese)", 3050 | "value": "si" 3051 | }, 3052 | { 3053 | "title": "Slovak", 3054 | "value": "sk" 3055 | }, 3056 | { 3057 | "title": "Slovenian", 3058 | "value": "sl" 3059 | }, 3060 | { 3061 | "title": "Somali", 3062 | "value": "so" 3063 | }, 3064 | { 3065 | "title": "Spanish", 3066 | "value": "es" 3067 | }, 3068 | { 3069 | "title": "Sundanese", 3070 | "value": "su" 3071 | }, 3072 | { 3073 | "title": "Swahili", 3074 | "value": "sw" 3075 | }, 3076 | { 3077 | "title": "Swedish", 3078 | "value": "sv" 3079 | }, 3080 | { 3081 | "title": "Tagalog (Filipino)", 3082 | "value": "tl" 3083 | }, 3084 | { 3085 | "title": "Tajik", 3086 | "value": "tg" 3087 | }, 3088 | { 3089 | "title": "Tamil", 3090 | "value": "ta" 3091 | }, 3092 | { 3093 | "title": "Tatar", 3094 | "value": "tt" 3095 | }, 3096 | { 3097 | "title": "Telugu", 3098 | "value": "te" 3099 | }, 3100 | { 3101 | "title": "Thai", 3102 | "value": "th" 3103 | }, 3104 | { 3105 | "title": "Turkish", 3106 | "value": "tr" 3107 | }, 3108 | { 3109 | "title": "Turkmen", 3110 | "value": "tk" 3111 | }, 3112 | { 3113 | "title": "Ukrainian", 3114 | "value": "uk" 3115 | }, 3116 | { 3117 | "title": "Urdu", 3118 | "value": "ur" 3119 | }, 3120 | { 3121 | "title": "Uyghur", 3122 | "value": "ug" 3123 | }, 3124 | { 3125 | "title": "Uzbek", 3126 | "value": "uz" 3127 | }, 3128 | { 3129 | "title": "Vietnamese", 3130 | "value": "vi" 3131 | }, 3132 | { 3133 | "title": "Welsh", 3134 | "value": "cy" 3135 | }, 3136 | { 3137 | "title": "Xhosa", 3138 | "value": "xh" 3139 | }, 3140 | { 3141 | "title": "Yiddish", 3142 | "value": "yi" 3143 | }, 3144 | { 3145 | "title": "Yoruba", 3146 | "value": "yo" 3147 | }, 3148 | { 3149 | "title": "Zulu", 3150 | "value": "zu" 3151 | } 3152 | ], 3153 | "required": false, 3154 | "default": "none" 3155 | } 3156 | ], 3157 | "scripts": { 3158 | "build": "ray build -e dist", 3159 | "dev": "ray develop", 3160 | "publish": "ray publish", 3161 | "postinstall": "pnpm run build", 3162 | "typecheck": "tsc --noEmit", 3163 | "lint": "eslint .", 3164 | "prepare": "esno scripts/prepare.ts" 3165 | }, 3166 | "dependencies": { 3167 | "@iamtraction/google-translate": "^2.0.1", 3168 | "@raycast/api": "^1.53.1", 3169 | "@raycast/utils": "^1.7.1", 3170 | "diff": "^5.1.0", 3171 | "execa": "^7.1.1", 3172 | "lru-cache": "^9.1.2", 3173 | "satori": "^0.10.1" 3174 | }, 3175 | "devDependencies": { 3176 | "@antfu/eslint-config": "^0.39.5", 3177 | "@types/diff": "^5.0.3", 3178 | "@types/fs-extra": "^11.0.1", 3179 | "@types/node": "~16.18.34", 3180 | "@types/react": "^18.2.9", 3181 | "eslint": "^8.42.0", 3182 | "eslint-plugin-react": "^7.32.2", 3183 | "esno": "^0.16.3", 3184 | "fs-extra": "^11.1.1", 3185 | "typescript": "^5.1.3" 3186 | } 3187 | } 3188 | -------------------------------------------------------------------------------- /scripts/preference.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | 3 | (async () => { 4 | const pkg = JSON.parse(await fs.readFile('./package.json', 'utf-8')) 5 | const languages = JSON.parse(await fs.readFile('./src/data/languages.json', 'utf-8')) 6 | 7 | const LANGUAGES = 7 8 | 9 | const languageOptions = Object.values(languages).map((lang: any) => { 10 | return { 11 | title: lang.name, 12 | value: lang.code, 13 | } 14 | }).slice(1) 15 | 16 | const langs = Array.from({ length: LANGUAGES }, (_, i) => { 17 | return { 18 | name: `lang${i + 1}`, 19 | type: 'dropdown', 20 | title: `Language ${i + 1}`, 21 | description: `Language ${i + 1}`, 22 | data: i === 0 23 | ? languageOptions 24 | : [ 25 | { 26 | title: '-', 27 | value: 'none', 28 | }, 29 | ...languageOptions, 30 | ], 31 | ...(i === 0 32 | ? { 33 | required: true, 34 | default: 'en', 35 | } 36 | : { 37 | required: false, 38 | default: 'none', 39 | }), 40 | } 41 | }) 42 | 43 | pkg.preferences = [ 44 | { 45 | name: 'getSystemSelection', 46 | type: 'checkbox', 47 | default: true, 48 | required: true, 49 | label: 'Read system text selection', 50 | description: 'Automatically get current text selection and translate', 51 | }, 52 | ...langs, 53 | ] 54 | 55 | await fs.writeFile('./package.json', JSON.stringify(pkg, null, 2)) 56 | })() 57 | -------------------------------------------------------------------------------- /scripts/prepare.ts: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa' 2 | 3 | (async () => { 4 | // Compile a native CLI to interact with the system dictionary 5 | await execa('swiftc', ['-o', 'spellcheck', 'spellcheck.swift'], { cwd: './assets' }) 6 | })() 7 | -------------------------------------------------------------------------------- /src/components/Main.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactElement } from 'react' 2 | import { useEffect, useState } from 'react' 3 | import { Action, ActionPanel, Icon, List, Toast, showToast } from '@raycast/api' 4 | import { usePromise } from '@raycast/utils' 5 | import type { LanguageCode } from '../data/languages' 6 | import { getLanguageName, languages, languagesByCode } from '../data/languages' 7 | import { translateAll } from '../logic/translator' 8 | import { targetLanguages, useDebouncedValue, useSystemSelection } from '../logic/hooks' 9 | import { unicodeTransform } from '../logic/text' 10 | import { spellcheck } from '../logic/spellcheck' 11 | import { webDictionaries } from '../data/web-dictionaries' 12 | import { SpellcheckItem } from './SpellcheckItem' 13 | import { TranslateDetail } from './TranslateDetail' 14 | 15 | const langReg = new RegExp(`[>:/](${Object.keys(languagesByCode).join('|')})$`, 'i') 16 | 17 | export function Main(): ReactElement { 18 | const langs = targetLanguages 19 | const [isShowingDetail, setIsShowingDetail] = useState(true) 20 | const [input, setInput] = useState('') 21 | const [systemSelection] = useSystemSelection() 22 | const [selectedId, setSelectedId] = useState() 23 | 24 | const [userLangFrom, setUserLangFrom] = useState() 25 | const [inlineLangFrom, setInlineLangFrom] = useState('auto') 26 | 27 | const rawSourceText = (input.trim() || systemSelection) 28 | const sourceText = rawSourceText 29 | .replace(langReg, (_, lang) => { 30 | const _lang = lang.toLowerCase() 31 | if (_lang !== inlineLangFrom) 32 | setInlineLangFrom(_lang) 33 | return '' 34 | }) 35 | .trim() 36 | 37 | if (rawSourceText === sourceText && inlineLangFrom !== 'auto') 38 | setInlineLangFrom('auto') 39 | 40 | const langFrom = userLangFrom ?? inlineLangFrom 41 | 42 | const debouncedText = useDebouncedValue(sourceText, 500) 43 | 44 | const { data: results, isLoading } = usePromise( 45 | translateAll, 46 | [debouncedText, langFrom, langs], 47 | { 48 | onError(error) { 49 | showToast({ 50 | style: Toast.Style.Failure, 51 | title: 'Could not translate', 52 | message: error.toString(), 53 | }) 54 | }, 55 | }) 56 | 57 | const { data: correctedText } = usePromise(spellcheck, [debouncedText]) 58 | 59 | // reset selection when results change 60 | useEffect(() => { 61 | setSelectedId(undefined) 62 | }, [results]) 63 | 64 | const fromLangs = Array.from(new Set(results?.map(i => i.from))) 65 | const singleSource = fromLangs.length === 1 66 | 67 | return ( 68 | { 75 | if (v === 'auto') 76 | setUserLangFrom(undefined) 77 | else 78 | setUserLangFrom(v as LanguageCode) 79 | }} 80 | > 81 | 86 | 87 | {targetLanguages.map(lang => ( 88 | 93 | ))} 94 | 95 | 96 | {Object.values(languages) 97 | .filter(lang => lang.code !== 'auto' && !targetLanguages.includes(lang.code)) 98 | .map(lang => ( 99 | ))} 104 | 105 | 106 | } 107 | searchText={input} 108 | onSearchTextChange={setInput} 109 | isLoading={isLoading} 110 | isShowingDetail={isShowingDetail} 111 | throttle 112 | selectedItemId={selectedId} 113 | onSelectionChange={(item) => { 114 | setSelectedId(item ?? undefined) 115 | }} 116 | actions={ 117 | 118 | 123 | 124 | } 125 | > 126 | {correctedText ? : null} 127 | {results?.map((item, index) => { 128 | const webDicts = webDictionaries 129 | .filter(dic => (dic.sentence || !item.translated.includes(' ')) 130 | && (!dic.enables || dic.enables.some(i => targetLanguages.includes(i))) 131 | && ([item.from, item.to].includes(dic.lang)), 132 | ) 133 | .map((dic) => { 134 | const text = item.from === dic.lang ? item.original : item.translated 135 | return { 136 | title: dic.name, 137 | url: dic.url(text, item), 138 | } 139 | }) 140 | .filter(dic => dic.url) 141 | 142 | return ( 143 | ${unicodeTransform(item.to.toUpperCase(), 'small_caps')}`, 151 | }]} 152 | detail={} 153 | actions={ 154 | 155 | 156 | 157 | { 158 | webDicts.map((dic, idx) => ( 159 | 166 | )) 167 | } 168 | 173 | 177 | setIsShowingDetail(!isShowingDetail)} /> 178 | 179 | 180 | } 181 | /> 182 | ) 183 | })} 184 | 185 | ) 186 | } 187 | -------------------------------------------------------------------------------- /src/components/SpellcheckItem.tsx: -------------------------------------------------------------------------------- 1 | import { Action, ActionPanel, Icon, List } from '@raycast/api' 2 | import { usePromise } from '@raycast/utils' 3 | import { getDiffSvg } from '../logic/diff' 4 | 5 | export function SpellcheckItem({ text, corrected }: { text: string; corrected: string }) { 6 | const { data: diffSvg } = usePromise( 7 | async (from: string, to?: string) => { 8 | if (!to) 9 | return 10 | return await getDiffSvg(from, to) 11 | }, 12 | [text, corrected], 13 | ) 14 | 15 | const padding = '' 16 | let markdown = `###### ${padding}Did you mean\n\n${padding}${corrected}` 17 | if (diffSvg) 18 | markdown += `\n\n###### ${padding}Diff ![](${diffSvg})` 19 | 20 | return ( 21 | } 30 | actions={ 31 | 32 | 33 | 34 | 35 | 36 | } 37 | /> 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/components/TranslateDetail.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactElement } from 'react' 2 | import { Icon, List } from '@raycast/api' 3 | import { usePromise } from '@raycast/utils' 4 | import { getLanguageName } from '../data/languages' 5 | import type { TranslateResult } from '../types' 6 | import { translate } from '../logic/translator' 7 | 8 | export function TranslateDetail({ item }: { item: TranslateResult }): ReactElement { 9 | const { data: translatedBack, isLoading } = usePromise(translate, [item.translated, item.to, item.from]) 10 | 11 | let markdown = item.translated 12 | if (translatedBack) { 13 | if (translatedBack.translated !== item.original) 14 | markdown += `\n\n------\n\n${translatedBack.translated}` 15 | } 16 | 17 | const isTheSame = translatedBack && translatedBack.translated.trim().toLowerCase() === item.original.trim().toLowerCase() 18 | 19 | return ( 20 | 24 | 25 | 26 | 27 | 32 | 33 | } 34 | /> 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/data/languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "auto": { 3 | "code": "auto", 4 | "name": "Auto-detect", 5 | "flag": "🌐" 6 | }, 7 | "af": { 8 | "code": "af", 9 | "name": "Afrikaans", 10 | "flag": "🇿🇦" 11 | }, 12 | "sq": { 13 | "code": "sq", 14 | "name": "Albanian", 15 | "flag": "🇦🇱" 16 | }, 17 | "am": { 18 | "code": "am", 19 | "name": "Amharic" 20 | }, 21 | "ar": { 22 | "code": "ar", 23 | "name": "Arabic" 24 | }, 25 | "hy": { 26 | "code": "hy", 27 | "name": "Armenian", 28 | "flag": "🇦🇲" 29 | }, 30 | "az": { 31 | "code": "az", 32 | "name": "Azerbaijani", 33 | "flag": "🇦🇿" 34 | }, 35 | "eu": { 36 | "code": "eu", 37 | "name": "Basque" 38 | }, 39 | "be": { 40 | "code": "be", 41 | "name": "Belarusian", 42 | "flag": "🇧🇾" 43 | }, 44 | "bn": { 45 | "code": "bn", 46 | "name": "Bengali" 47 | }, 48 | "bs": { 49 | "code": "bs", 50 | "name": "Bosnian", 51 | "flag": "🇧🇦" 52 | }, 53 | "bg": { 54 | "code": "bg", 55 | "name": "Bulgarian", 56 | "flag": "🇧🇬" 57 | }, 58 | "ca": { 59 | "code": "ca", 60 | "name": "Catalan" 61 | }, 62 | "ceb": { 63 | "code": "ceb", 64 | "name": "Cebuano" 65 | }, 66 | "zh-CN": { 67 | "code": "zh-CN", 68 | "name": "Chinese Simplified", 69 | "flag": "🇨🇳" 70 | }, 71 | "zh-TW": { 72 | "code": "zh-TW", 73 | "name": "Taiwanese Mandarin", 74 | "flag": "🇹🇼" 75 | }, 76 | "co": { 77 | "code": "co", 78 | "name": "Corsican" 79 | }, 80 | "hr": { 81 | "code": "hr", 82 | "name": "Croatian", 83 | "flag": "🇭🇷" 84 | }, 85 | "cs": { 86 | "code": "cs", 87 | "name": "Czech", 88 | "flag": "🇨🇿" 89 | }, 90 | "da": { 91 | "code": "da", 92 | "name": "Danish", 93 | "flag": "🇩🇰" 94 | }, 95 | "nl": { 96 | "code": "nl", 97 | "name": "Dutch", 98 | "flag": "🇳🇱" 99 | }, 100 | "en": { 101 | "code": "en", 102 | "name": "English", 103 | "flag": "🇬🇧" 104 | }, 105 | "eo": { 106 | "code": "eo", 107 | "name": "Esperanto" 108 | }, 109 | "et": { 110 | "code": "et", 111 | "name": "Estonian", 112 | "flag": "🇪🇪" 113 | }, 114 | "fi": { 115 | "code": "fi", 116 | "name": "Finnish", 117 | "flag": "🇫🇮" 118 | }, 119 | "fr": { 120 | "code": "fr", 121 | "name": "French", 122 | "flag": "🇫🇷" 123 | }, 124 | "fy": { 125 | "code": "fy", 126 | "name": "Frisian" 127 | }, 128 | "gl": { 129 | "code": "gl", 130 | "name": "Galician" 131 | }, 132 | "ka": { 133 | "code": "ka", 134 | "name": "Georgian", 135 | "flag": "🇬🇪" 136 | }, 137 | "de": { 138 | "code": "de", 139 | "name": "German", 140 | "flag": "🇩🇪" 141 | }, 142 | "el": { 143 | "code": "el", 144 | "name": "Greek", 145 | "flag": "🇬🇷" 146 | }, 147 | "gu": { 148 | "code": "gu", 149 | "name": "Gujarati" 150 | }, 151 | "ht": { 152 | "code": "ht", 153 | "name": "Haitian Creole", 154 | "flag": "🇭🇹" 155 | }, 156 | "ha": { 157 | "code": "ha", 158 | "name": "Hausa" 159 | }, 160 | "haw": { 161 | "code": "haw", 162 | "name": "Hawaiian", 163 | "flag": "🌺" 164 | }, 165 | "he": { 166 | "code": "he", 167 | "name": "Hebrew", 168 | "flag": "🇮🇱" 169 | }, 170 | "hi": { 171 | "code": "hi", 172 | "name": "Hindi", 173 | "flag": "🇮🇳" 174 | }, 175 | "hmn": { 176 | "code": "hmn", 177 | "name": "Hmong" 178 | }, 179 | "hu": { 180 | "code": "hu", 181 | "name": "Hungarian", 182 | "flag": "🇭🇺" 183 | }, 184 | "is": { 185 | "code": "is", 186 | "name": "Icelandic", 187 | "flag": "🇮🇸" 188 | }, 189 | "ig": { 190 | "code": "ig", 191 | "name": "Igbo" 192 | }, 193 | "id": { 194 | "code": "id", 195 | "name": "Indonesian", 196 | "flag": "🇮🇩" 197 | }, 198 | "ga": { 199 | "code": "ga", 200 | "name": "Irish", 201 | "flag": "🇮🇪" 202 | }, 203 | "it": { 204 | "code": "it", 205 | "name": "Italian", 206 | "flag": "🇮🇹" 207 | }, 208 | "ja": { 209 | "code": "ja", 210 | "name": "Japanese", 211 | "flag": "🇯🇵" 212 | }, 213 | "jv": { 214 | "code": "jv", 215 | "name": "Javanese" 216 | }, 217 | "kn": { 218 | "code": "kn", 219 | "name": "Kannada" 220 | }, 221 | "kk": { 222 | "code": "kk", 223 | "name": "Kazakh", 224 | "flag": "🇰🇿" 225 | }, 226 | "km": { 227 | "code": "km", 228 | "name": "Khmer" 229 | }, 230 | "rw": { 231 | "code": "rw", 232 | "name": "Kinyarwanda" 233 | }, 234 | "ko": { 235 | "code": "ko", 236 | "name": "Korean", 237 | "flag": "🇰🇷" 238 | }, 239 | "ku": { 240 | "code": "ku", 241 | "name": "Kurdish" 242 | }, 243 | "ky": { 244 | "code": "ky", 245 | "name": "Kyrgyz" 246 | }, 247 | "lo": { 248 | "code": "lo", 249 | "name": "Lao" 250 | }, 251 | "lv": { 252 | "code": "lv", 253 | "name": "Latvian", 254 | "flag": "🇱🇻" 255 | }, 256 | "lt": { 257 | "code": "lt", 258 | "name": "Lithuanian", 259 | "flag": "🇱🇹" 260 | }, 261 | "lb": { 262 | "code": "lb", 263 | "name": "Luxembourgish", 264 | "flag": "🇱🇺" 265 | }, 266 | "mk": { 267 | "code": "mk", 268 | "name": "Macedonian", 269 | "flag": "🇲🇰" 270 | }, 271 | "mg": { 272 | "code": "mg", 273 | "name": "Malagasy" 274 | }, 275 | "ms": { 276 | "code": "ms", 277 | "name": "Malay", 278 | "flag": "🇲🇾" 279 | }, 280 | "ml": { 281 | "code": "ml", 282 | "name": "Malayalam" 283 | }, 284 | "mt": { 285 | "code": "mt", 286 | "name": "Maltese", 287 | "flag": "🇲🇹" 288 | }, 289 | "mi": { 290 | "code": "mi", 291 | "name": "Maori", 292 | "flag": "🇳🇿" 293 | }, 294 | "mr": { 295 | "code": "mr", 296 | "name": "Marathi" 297 | }, 298 | "mn": { 299 | "code": "mn", 300 | "name": "Mongolian", 301 | "flag": "🇲🇳" 302 | }, 303 | "my": { 304 | "code": "my", 305 | "name": "Myanmar (Burmese)", 306 | "flag": "🇲🇲" 307 | }, 308 | "ne": { 309 | "code": "ne", 310 | "name": "Nepali", 311 | "flag": "🇳🇵" 312 | }, 313 | "no": { 314 | "code": "no", 315 | "name": "Norwegian", 316 | "flag": "🇳🇴" 317 | }, 318 | "ny": { 319 | "code": "ny", 320 | "name": "Nyanja (Chichewa)" 321 | }, 322 | "or": { 323 | "code": "or", 324 | "name": "Odia (Oriya)" 325 | }, 326 | "ps": { 327 | "code": "ps", 328 | "name": "Pashto" 329 | }, 330 | "fa": { 331 | "code": "fa", 332 | "name": "Persian", 333 | "flag": "🇮🇷" 334 | }, 335 | "pl": { 336 | "code": "pl", 337 | "name": "Polish", 338 | "flag": "🇵🇱" 339 | }, 340 | "pt": { 341 | "code": "pt", 342 | "name": "Portuguese", 343 | "flag": "🇵🇹" 344 | }, 345 | "pa": { 346 | "code": "pa", 347 | "name": "Punjabi", 348 | "flag": "🇮🇳" 349 | }, 350 | "ro": { 351 | "code": "ro", 352 | "name": "Romanian", 353 | "flag": "🇷🇴" 354 | }, 355 | "ru": { 356 | "code": "ru", 357 | "name": "Russian", 358 | "flag": "🇷🇺" 359 | }, 360 | "sm": { 361 | "code": "sm", 362 | "name": "Samoan" 363 | }, 364 | "gd": { 365 | "code": "gd", 366 | "name": "Scots Gaelic", 367 | "flag": "🏴󠁧󠁢󠁳󠁣󠁴󠁿" 368 | }, 369 | "sr": { 370 | "code": "sr", 371 | "name": "Serbian", 372 | "flag": "🇷🇸" 373 | }, 374 | "st": { 375 | "code": "st", 376 | "name": "Sesotho" 377 | }, 378 | "sn": { 379 | "code": "sn", 380 | "name": "Shona" 381 | }, 382 | "sd": { 383 | "code": "sd", 384 | "name": "Sindhi" 385 | }, 386 | "si": { 387 | "code": "si", 388 | "name": "Sinhala (Sinhalese)" 389 | }, 390 | "sk": { 391 | "code": "sk", 392 | "name": "Slovak", 393 | "flag": "🇸🇰" 394 | }, 395 | "sl": { 396 | "code": "sl", 397 | "name": "Slovenian", 398 | "flag": "🇸🇮" 399 | }, 400 | "so": { 401 | "code": "so", 402 | "name": "Somali", 403 | "flag": "🇸🇴" 404 | }, 405 | "es": { 406 | "code": "es", 407 | "name": "Spanish", 408 | "flag": "🇪🇸" 409 | }, 410 | "su": { 411 | "code": "su", 412 | "name": "Sundanese", 413 | "flag": "🇸🇩" 414 | }, 415 | "sw": { 416 | "code": "sw", 417 | "name": "Swahili" 418 | }, 419 | "sv": { 420 | "code": "sv", 421 | "name": "Swedish", 422 | "flag": "🇸🇪" 423 | }, 424 | "tl": { 425 | "code": "tl", 426 | "name": "Tagalog (Filipino)", 427 | "flag": "🇵🇭" 428 | }, 429 | "tg": { 430 | "code": "tg", 431 | "name": "Tajik" 432 | }, 433 | "ta": { 434 | "code": "ta", 435 | "name": "Tamil" 436 | }, 437 | "tt": { 438 | "code": "tt", 439 | "name": "Tatar" 440 | }, 441 | "te": { 442 | "code": "te", 443 | "name": "Telugu" 444 | }, 445 | "th": { 446 | "code": "th", 447 | "name": "Thai" 448 | }, 449 | "tr": { 450 | "code": "tr", 451 | "name": "Turkish", 452 | "flag": "🇹🇷" 453 | }, 454 | "tk": { 455 | "code": "tk", 456 | "name": "Turkmen", 457 | "flag": "🇹🇲" 458 | }, 459 | "uk": { 460 | "code": "uk", 461 | "name": "Ukrainian", 462 | "flag": "🇺🇦" 463 | }, 464 | "ur": { 465 | "code": "ur", 466 | "name": "Urdu" 467 | }, 468 | "ug": { 469 | "code": "ug", 470 | "name": "Uyghur" 471 | }, 472 | "uz": { 473 | "code": "uz", 474 | "name": "Uzbek" 475 | }, 476 | "vi": { 477 | "code": "vi", 478 | "name": "Vietnamese", 479 | "flag": "🇻🇳" 480 | }, 481 | "cy": { 482 | "code": "cy", 483 | "name": "Welsh", 484 | "flag": "🏴󠁧󠁢󠁷󠁬󠁳󠁿" 485 | }, 486 | "xh": { 487 | "code": "xh", 488 | "name": "Xhosa" 489 | }, 490 | "yi": { 491 | "code": "yi", 492 | "name": "Yiddish", 493 | "flag": "🇮🇱" 494 | }, 495 | "yo": { 496 | "code": "yo", 497 | "name": "Yoruba" 498 | }, 499 | "zu": { 500 | "code": "zu", 501 | "name": "Zulu" 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /src/data/languages.ts: -------------------------------------------------------------------------------- 1 | import _languages from './languages.json' 2 | 3 | export type LanguageCode = keyof typeof _languages 4 | 5 | export interface LanguagesItem { 6 | code: LanguageCode 7 | name: string 8 | flag?: string 9 | } 10 | 11 | export const languagesByCode = _languages as Record 12 | export const languages: LanguagesItem[] = Object.values(languagesByCode) 13 | 14 | export function getLanguageName(code: LanguageCode) { 15 | return languagesByCode[code]?.name || code 16 | } 17 | -------------------------------------------------------------------------------- /src/data/web-dictionaries.ts: -------------------------------------------------------------------------------- 1 | import type { WebDictionary } from '../types' 2 | 3 | export const webDictionaries: WebDictionary[] = [ 4 | { 5 | name: 'Cambridge Dictionary', 6 | lang: 'en', 7 | url: text => `https://dictionary.cambridge.org/dictionary/english/${encodeURIComponent(text)}`, 8 | }, 9 | { 10 | name: '法语助手', 11 | lang: 'fr', 12 | enables: ['zh-CN'], 13 | url: text => `https://www.frdic.com/dicts/fr/${encodeURIComponent(text)}`, 14 | }, 15 | ] 16 | -------------------------------------------------------------------------------- /src/logic/diff.tsx: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'node:buffer' 2 | import fs from 'node:fs/promises' 3 | import { join } from 'node:path' 4 | import satori from 'satori' 5 | import { diffChars } from 'diff' 6 | import { environment } from '@raycast/api' 7 | 8 | const diffCache = new Map>() 9 | const font = fs.readFile(join(environment.assetsPath, 'RobotoMono-Regular.ttf')) 10 | 11 | export async function getDiffSvg(from: string, to: string): Promise { 12 | const cacheKey = `${from}=>${to}` 13 | 14 | if (!diffCache.has(cacheKey)) { 15 | diffCache.set(cacheKey, (async () => { 16 | const diffs = diffChars(from, to) 17 | const foregroundColor = environment.appearance === 'light' 18 | ? 'black' 19 | : 'white' 20 | 21 | const svg = await satori( 22 |
15 29 | ? to.length > 24 30 | ? 18 31 | : 35 32 | : 50, 33 | }}> 34 | {diffs 35 | .filter(diff => diff.value) 36 | .map((diff, idx) => { 37 | const color = diff.added 38 | ? '#7AA874' 39 | : diff.removed 40 | ? '#F96666' 41 | : foregroundColor 42 | const background = diff.added 43 | ? '#7AA87430' 44 | : diff.removed 45 | ? '#F9666620' 46 | : 'transparent' 47 | return ( 48 | {diff.value} 56 | ) 57 | })} 58 |
, 59 | { 60 | width: 520, 61 | height: 200, 62 | fonts: [ 63 | { 64 | name: 'Roboto', 65 | data: await font, 66 | weight: 400, 67 | style: 'normal', 68 | }, 69 | ], 70 | }, 71 | ) 72 | return `data:image/svg+xml;base64,${Buffer.from(svg, 'utf8').toString('base64')}` 73 | })()) 74 | } 75 | 76 | return diffCache.get(cacheKey)! 77 | } 78 | -------------------------------------------------------------------------------- /src/logic/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { getPreferenceValues, getSelectedText } from '@raycast/api' 3 | import type { LanguageCode } from '../data/languages' 4 | 5 | // preferences won't change at runtime. 6 | export const preferences = getPreferenceValues() 7 | 8 | export const targetLanguages = (() => { 9 | const pref = getPreferenceValues() 10 | const langs = Object.entries(pref) 11 | .filter(([key]) => key.startsWith('lang')) 12 | .sort(([key1], [key2]) => key1.localeCompare(key2)) 13 | .map(([_, value]) => value) 14 | .filter(i => i && i !== 'none') 15 | return Array.from(new Set(langs)) as LanguageCode[] 16 | })() 17 | 18 | export function useSystemSelection() { 19 | const [text, setText] = useState('') 20 | useEffect(() => { 21 | if (!preferences.getSystemSelection) 22 | return 23 | 24 | let isCancelled = false 25 | getSelectedText() 26 | .then((cbText) => { 27 | if (!isCancelled) 28 | setText((cbText ?? '').trim()) 29 | }) 30 | .catch(() => {}) 31 | 32 | return () => { 33 | isCancelled = true 34 | } 35 | }, [preferences.getSystemSelection]) 36 | 37 | return [text, setText] as const 38 | } 39 | 40 | export function useDebouncedValue(value: T, delay: number) { 41 | const [debouncedValue, setDebouncedValue] = useState(value) 42 | 43 | useEffect(() => { 44 | const handler = setTimeout(() => { 45 | setDebouncedValue(value) 46 | }, delay) 47 | 48 | return () => { 49 | clearTimeout(handler) 50 | } 51 | }, [value, delay]) 52 | 53 | return debouncedValue 54 | } 55 | -------------------------------------------------------------------------------- /src/logic/spellcheck.ts: -------------------------------------------------------------------------------- 1 | import { chmodSync } from 'node:fs' 2 | import { join } from 'node:path' 3 | import { execa } from 'execa' 4 | import { environment } from '@raycast/api' 5 | 6 | const filepath = join(environment.assetsPath, 'spellcheck') 7 | // Raycast will copy assets without execute permission 8 | chmodSync(filepath, 0o755) 9 | 10 | export async function spellcheck(text: string): Promise { 11 | // We use a sub process here because Raycast 12 | // does not support bundling native bindings 13 | try { 14 | const { stdout: corrected } = await execa(filepath, [text]) 15 | return text === corrected ? false : corrected 16 | } 17 | catch (e) { 18 | console.error(e) 19 | return false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/logic/text.ts: -------------------------------------------------------------------------------- 1 | const fonts = { 2 | plain: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-', 3 | monospace: '𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉', 4 | math_monospace: '𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉-', 5 | math_sans: '𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹-', 6 | math_sans_bold: '𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙-', 7 | math_sans_italic: '𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡-', 8 | math_sans_bold_italic: '𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕-', 9 | square: 'abcdefghijklmnopqrstuvwxyz🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉-', 10 | small_caps: 'abcdefghijklmnopqrstuvwxyzᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘꞯʀꜱᴛᴜᴠᴡXʏᴢ-', 11 | } 12 | 13 | type FontNames = keyof typeof fonts 14 | 15 | export function unicodeTransform(text: string, to: FontNames, from: FontNames = 'plain') { 16 | const FromFont = Array.from(fonts[from]) 17 | const ToFont = Array.from(fonts[to]) 18 | return Array.from(text) 19 | .map((c) => { 20 | if (FromFont.includes(c)) 21 | return ToFont[FromFont.indexOf(c)] 22 | return c 23 | }).join('') 24 | } 25 | -------------------------------------------------------------------------------- /src/logic/translator.ts: -------------------------------------------------------------------------------- 1 | import googleTranslate from '@iamtraction/google-translate' 2 | import { LRUCache } from 'lru-cache' 3 | import type { LanguageCode } from '../data/languages' 4 | import type { TranslateResult } from '../types' 5 | 6 | export const AUTO_DETECT = 'auto' 7 | 8 | const cache = new LRUCache({ 9 | max: 1000, 10 | }) 11 | 12 | export class TranslateError extends Error { 13 | constructor(message?: string | Error, name?: string) { 14 | if (message instanceof Error) { 15 | super(message.message) 16 | this.name = name || message.name 17 | } 18 | else { 19 | super(message) 20 | this.name = name || this.name 21 | } 22 | } 23 | } 24 | 25 | export async function translate(text: string, from: LanguageCode, to: LanguageCode): Promise { 26 | if (!text) { 27 | return { 28 | original: text, 29 | translated: '', 30 | from, 31 | to, 32 | } 33 | } 34 | 35 | const key = `${from}:${to}:${text}` 36 | const cached = cache.get(key) 37 | if (cached) 38 | return cached 39 | 40 | try { 41 | const translated = await googleTranslate(text, { 42 | from, 43 | to, 44 | }) 45 | 46 | const result = { 47 | original: text, 48 | translated: translated.text, 49 | from: translated?.from?.language?.didYouMean 50 | ? from 51 | : translated?.from?.language?.iso as LanguageCode, 52 | to, 53 | } 54 | cache.set(key, result) 55 | return result 56 | } 57 | catch (err) { 58 | if (err instanceof Error) { 59 | switch (err.name) { 60 | case 'TooManyRequestsError': 61 | throw new TranslateError('please try again later', 'Too many requests') 62 | default: 63 | throw new TranslateError(err) 64 | } 65 | } 66 | 67 | throw err 68 | } 69 | } 70 | 71 | export async function translateAll(text: string, from: LanguageCode = 'auto', languages: LanguageCode[]) { 72 | if (!text) 73 | return [] 74 | 75 | const result = (await Promise.all(languages.map(async to => translate(text, from, to)))).filter(i => i.translated) 76 | 77 | const fromLangs = new Set(result?.map(i => i.from)) 78 | const singleSource = fromLangs.size === 1 79 | if (singleSource) 80 | return result.filter(i => i.from !== i.to && i.translated.trim().toLowerCase() !== i.original.trim().toLowerCase()) 81 | return result 82 | } 83 | -------------------------------------------------------------------------------- /src/translate.tsx: -------------------------------------------------------------------------------- 1 | import { Main } from './components/Main' 2 | 3 | export default Main 4 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { LanguageCode } from './data/languages' 2 | 3 | export { LanguageCode } 4 | 5 | export interface TranslateResult { 6 | original: string 7 | translated: string 8 | from: LanguageCode 9 | to: LanguageCode 10 | } 11 | 12 | export interface WebDictionary { 13 | name: string 14 | lang: LanguageCode 15 | /** 16 | * Enable this dictionary when user have one of the target languages 17 | */ 18 | enables?: LanguageCode[] 19 | /** 20 | * Supports sentence or not 21 | * @default false 22 | */ 23 | sentence?: boolean 24 | url: (text: string, result: TranslateResult) => string | void 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "lib": ["es2020"], 5 | "module": "commonjs", 6 | "target": "es2020", 7 | "strict": true, 8 | "isolatedModules": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "jsx": "react-jsx", 13 | "resolveJsonModule": true 14 | } 15 | } 16 | --------------------------------------------------------------------------------