]> Untitled Git - lemmy.git/commitdiff
Tag posts and comments with language (fixes #440) (#2269)
authorNutomic <me@nutomic.com>
Thu, 18 Aug 2022 19:11:19 +0000 (19:11 +0000)
committerGitHub <noreply@github.com>
Thu, 18 Aug 2022 19:11:19 +0000 (15:11 -0400)
* Tag posts and comments with language (fixes #440)

* Untangle PostView tests

* Implement test for PostView language query

* Store languages directly in database

* finish moving languages into db, it compiles

* update post_view

* serde skip Language.id field

* add local_user_language table, other changes suggested in review

* add code for local_user_discussion_language_view

* Remove unnecessary clones in db view converteres

* Fixing up some table and join issues.

* Clearing the current languages.

* Fix formatting.

* update user languages in single transaction

* proper test for user language queries

* Some fixes for all / missing user languages. (#2404)

* Some fixes for all / missing user languages.

* Adding back in transaction.

* fix test

Co-authored-by: Dessalines <tyhou13@gmx.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
41 files changed:
Cargo.lock
crates/api/src/local_user/save_settings.rs
crates/api/src/site/leave_admin.rs
crates/api/src/site/search.rs
crates/api_common/src/person.rs
crates/api_common/src/post.rs
crates/api_common/src/site.rs
crates/api_common/src/utils.rs
crates/api_crud/src/post/create.rs
crates/api_crud/src/post/list.rs
crates/api_crud/src/post/update.rs
crates/api_crud/src/private_message/create.rs
crates/api_crud/src/site/read.rs
crates/api_crud/src/user/create.rs
crates/api_crud/src/user/read.rs
crates/apub/assets/lemmy/activities/create_or_update/create_page.json
crates/apub/assets/lemmy/objects/page.json
crates/apub/src/lib.rs
crates/apub/src/objects/post.rs
crates/apub/src/protocol/objects/page.rs
crates/db_schema/src/impls/language.rs [new file with mode: 0644]
crates/db_schema/src/impls/local_user.rs
crates/db_schema/src/impls/local_user_language.rs [new file with mode: 0644]
crates/db_schema/src/impls/mod.rs
crates/db_schema/src/impls/post.rs
crates/db_schema/src/newtypes.rs
crates/db_schema/src/schema.rs
crates/db_schema/src/source/language.rs [new file with mode: 0644]
crates/db_schema/src/source/local_user.rs
crates/db_schema/src/source/local_user_language.rs [new file with mode: 0644]
crates/db_schema/src/source/mod.rs
crates/db_schema/src/source/post.rs
crates/db_views/src/comment_view.rs
crates/db_views/src/lib.rs
crates/db_views/src/local_user_discussion_language_view.rs [new file with mode: 0644]
crates/db_views/src/post_view.rs
crates/db_views/src/registration_application_view.rs
crates/db_views/src/structs.rs
crates/websocket/src/send.rs
migrations/2022-06-21-123144_language-tags/down.sql [new file with mode: 0644]
migrations/2022-06-21-123144_language-tags/up.sql [new file with mode: 0644]

index 18ac1edcc5ce644d1829266133c0ea489126b98c..c1bfba5addcdd64d87505d2e9c95d347ea7b5f68 100644 (file)
@@ -38,9 +38,9 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07520b54fc0f22ad30b90399b2a2689c6e5c113df0642ca3fa2f7ee823e54126"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -135,8 +135,8 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
 dependencies = [
- "quote 1.0.18",
- "syn 1.0.96",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -286,9 +286,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc"
 dependencies = [
  "actix-router",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -297,9 +297,9 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -345,9 +345,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.57"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
+checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
 
 [[package]]
 name = "arrayvec"
@@ -390,9 +390,9 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -401,9 +401,9 @@ version = "0.1.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -860,10 +860,10 @@ checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
 dependencies = [
  "fnv",
  "ident_case",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "strsim",
- "syn 1.0.96",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -874,10 +874,10 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
 dependencies = [
  "fnv",
  "ident_case",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "strsim",
- "syn 1.0.96",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -888,10 +888,10 @@ checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
 dependencies = [
  "fnv",
  "ident_case",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "strsim",
- "syn 1.0.96",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -901,8 +901,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
 dependencies = [
  "darling_core 0.12.4",
- "quote 1.0.18",
- "syn 1.0.96",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -912,8 +912,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
 dependencies = [
  "darling_core 0.13.4",
- "quote 1.0.18",
- "syn 1.0.96",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -923,8 +923,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
 dependencies = [
  "darling_core 0.14.1",
- "quote 1.0.18",
- "syn 1.0.96",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -961,9 +961,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
 dependencies = [
  "darling 0.12.4",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -973,9 +973,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4"
 dependencies = [
  "darling 0.14.1",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -985,7 +985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
 dependencies = [
  "derive_builder_core 0.10.2",
- "syn 1.0.96",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -995,7 +995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68"
 dependencies = [
  "derive_builder_core 0.11.2",
- "syn 1.0.96",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -1005,10 +1005,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
 dependencies = [
  "convert_case",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "rustc_version",
- "syn 1.0.96",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -1053,9 +1053,9 @@ version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -1145,9 +1145,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6430bef5fcbfa22f3c431f05a14254d45f41ab634cabe09fad82e98d4f9fdc8b"
 dependencies = [
  "darling 0.13.4",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -1158,9 +1158,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
 
 [[package]]
 name = "email-encoding"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75b91dddc343e7eaa27f9764e5bffe57370d957017fdd75244f5045e829a8441"
+checksum = "827e1fb86d24d558ab0454ca3fa084f8a6144ade1e3e6982f697c586bf96b41b"
 dependencies = [
  "base64",
  "memchr",
@@ -1406,9 +1406,9 @@ version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -1598,9 +1598,9 @@ dependencies = [
  "log",
  "mac",
  "markup5ever",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -2341,9 +2341,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
 dependencies = [
  "migrations_internals",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -2582,9 +2582,9 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -2758,9 +2758,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
 dependencies = [
  "pest",
  "pest_meta",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -2856,9 +2856,9 @@ version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -2919,9 +2919,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
 dependencies = [
  "proc-macro-error-attr",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
  "version_check",
 ]
 
@@ -2931,8 +2931,8 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "version_check",
 ]
 
@@ -2947,9 +2947,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.39"
+version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
 dependencies = [
  "unicode-ident",
 ]
@@ -2992,9 +2992,9 @@ checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
 dependencies = [
  "anyhow",
  "itertools",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -3037,11 +3037,11 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.18"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
 dependencies = [
- "proc-macro2 1.0.39",
+ "proc-macro2 1.0.40",
 ]
 
 [[package]]
@@ -3336,8 +3336,8 @@ checksum = "6f697b8b3f19bee20f30dc87213d05ce091c43bc733ab1bfc98b0e5cdd9943f3"
 dependencies = [
  "convert_case",
  "lazy_static",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "regex",
  "tinyjson",
 ]
@@ -3492,9 +3492,9 @@ version = "1.0.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -3538,9 +3538,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
 dependencies = [
  "darling 0.13.4",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -3561,10 +3561,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2881bccd7d60fb32dfa3d7b3136385312f8ad75e2674aab2852867a09790cae8"
 dependencies = [
  "proc-macro-error",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "rustversion",
- "syn 1.0.96",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -3661,9 +3661,9 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -3710,8 +3710,8 @@ checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
 dependencies = [
  "phf_generator 0.10.0",
  "phf_shared 0.10.0",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
 ]
 
 [[package]]
@@ -3733,10 +3733,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef"
 dependencies = [
  "heck 0.4.0",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "rustversion",
- "syn 1.0.96",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -3752,12 +3752,12 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.96"
+version = "1.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
+checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
  "unicode-ident",
 ]
 
@@ -3816,9 +3816,9 @@ version = "1.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -3884,9 +3884,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.18.2"
+version = "1.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
+checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee"
 dependencies = [
  "bytes",
  "libc",
@@ -3919,9 +3919,9 @@ version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -4021,10 +4021,10 @@ version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757"
 dependencies = [
- "proc-macro2 1.0.39",
+ "proc-macro2 1.0.40",
  "prost-build",
- "quote 1.0.18",
- "syn 1.0.96",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -4061,9 +4061,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
 
 [[package]]
 name = "tracing"
-version = "0.1.34"
+version = "0.1.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
+checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
 dependencies = [
  "cfg-if",
  "log",
@@ -4091,18 +4091,18 @@ version = "0.1.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
 name = "tracing-core"
-version = "0.1.23"
+version = "0.1.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c"
+checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921"
 dependencies = [
- "lazy_static",
+ "once_cell",
  "valuable",
 ]
 
@@ -4209,9 +4209,9 @@ version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
 ]
 
 [[package]]
@@ -4249,9 +4249,9 @@ checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
+checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
 
 [[package]]
 name = "unicode-normalization"
@@ -4397,9 +4397,9 @@ dependencies = [
  "bumpalo",
  "lazy_static",
  "log",
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
  "wasm-bindgen-shared",
 ]
 
@@ -4421,7 +4421,7 @@ version = "0.2.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
 dependencies = [
- "quote 1.0.18",
+ "quote 1.0.20",
  "wasm-bindgen-macro-support",
 ]
 
@@ -4431,9 +4431,9 @@ version = "0.2.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
 dependencies = [
- "proc-macro2 1.0.39",
- "quote 1.0.18",
- "syn 1.0.96",
+ "proc-macro2 1.0.40",
+ "quote 1.0.20",
+ "syn 1.0.98",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
index 9929fc0c76f0c32053ec50f2b4cc9a781c4a7cca..e8a94ff60cc00ecc9d81c965362188a636122604 100644 (file)
@@ -7,6 +7,7 @@ use lemmy_api_common::{
 use lemmy_db_schema::{
   source::{
     local_user::{LocalUser, LocalUserForm},
+    local_user_language::LocalUserLanguage,
     person::{Person, PersonForm},
     site::Site,
   },
@@ -118,6 +119,20 @@ impl Perform for SaveUserSettings {
     .await?
     .map_err(|e| LemmyError::from_error_message(e, "user_already_exists"))?;
 
+    if let Some(discussion_languages) = data.discussion_languages.clone() {
+      // An empty array is a "clear" / set all languages
+      let languages = if discussion_languages.is_empty() {
+        None
+      } else {
+        Some(discussion_languages)
+      };
+
+      blocking(context.pool(), move |conn| {
+        LocalUserLanguage::update_user_languages(conn, languages, local_user_id)
+      })
+      .await??;
+    }
+
     let local_user_form = LocalUserForm {
       person_id: Some(person_id),
       email,
@@ -128,7 +143,7 @@ impl Perform for SaveUserSettings {
       theme: data.theme.to_owned(),
       default_sort_type,
       default_listing_type,
-      lang: data.lang.to_owned(),
+      interface_language: data.interface_language.to_owned(),
       show_avatars: data.show_avatars,
       show_read_posts: data.show_read_posts,
       show_new_post_notifs: data.show_new_post_notifs,
index 19cca5ccc60a5abad351c6e33a6eab53ef090e50..2a5fa590f5bcb5f306fcc1fbad3659c7b7e1ede6 100644 (file)
@@ -6,6 +6,7 @@ use lemmy_api_common::{
 };
 use lemmy_db_schema::{
   source::{
+    language::Language,
     moderator::{ModAdd, ModAddForm},
     person::Person,
   },
@@ -59,6 +60,8 @@ impl Perform for LeaveAdmin {
 
     let federated_instances = build_federated_instances(context.pool(), context.settings()).await?;
 
+    let all_languages = blocking(context.pool(), Language::read_all).await??;
+
     Ok(GetSiteResponse {
       site_view: Some(site_view),
       admins,
@@ -66,6 +69,7 @@ impl Perform for LeaveAdmin {
       version: version::VERSION.to_string(),
       my_user: None,
       federated_instances,
+      all_languages,
     })
   }
 }
index 7c118a0b27885a76cd35a447759ae4374a1568ef..bd04b053c2077b6af96ae5cc5a7b27e41e491cbb 100644 (file)
@@ -34,15 +34,11 @@ impl Perform for Search {
 
     check_private_instance(&local_user_view, context.pool()).await?;
 
-    let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
     let show_bot_accounts = local_user_view
       .as_ref()
       .map(|t| t.local_user.show_bot_accounts);
-    let show_read_posts = local_user_view
-      .as_ref()
-      .map(|t| t.local_user.show_read_posts);
 
-    let person_id = local_user_view.map(|u| u.person.id);
+    let person_id = local_user_view.as_ref().map(|u| u.person.id);
 
     let mut posts = Vec::new();
     let mut comments = Vec::new();
@@ -73,9 +69,6 @@ impl Perform for Search {
           PostQuery::builder()
             .conn(conn)
             .sort(sort)
-            .show_nsfw(show_nsfw)
-            .show_bot_accounts(show_bot_accounts)
-            .show_read_posts(show_read_posts)
             .listing_type(listing_type)
             .community_id(community_id)
             .community_actor_id(community_actor_id)
@@ -146,9 +139,6 @@ impl Perform for Search {
           PostQuery::builder()
             .conn(conn)
             .sort(sort)
-            .show_nsfw(show_nsfw)
-            .show_bot_accounts(show_bot_accounts)
-            .show_read_posts(show_read_posts)
             .listing_type(listing_type)
             .community_id(community_id)
             .community_actor_id(community_actor_id_2)
@@ -226,11 +216,7 @@ impl Perform for Search {
           PostQuery::builder()
             .conn(conn)
             .sort(sort)
-            .show_nsfw(show_nsfw)
-            .show_bot_accounts(show_bot_accounts)
-            .show_read_posts(show_read_posts)
             .listing_type(listing_type)
-            .my_person_id(person_id)
             .community_id(community_id)
             .community_actor_id(community_actor_id)
             .creator_id(creator_id)
index 865597521186c86e9092afca39a9e4958e29df27..79e3041d8e8c8ab8ca0147b2bb52b6ae0eab442e 100644 (file)
@@ -14,7 +14,14 @@ pub struct Login {
   pub password: Sensitive<String>,
 }
 use lemmy_db_schema::{
-  newtypes::{CommentReplyId, CommunityId, PersonId, PersonMentionId, PrivateMessageId},
+  newtypes::{
+    CommentReplyId,
+    CommunityId,
+    LanguageId,
+    PersonId,
+    PersonMentionId,
+    PrivateMessageId,
+  },
   CommentSortType,
   SortType,
 };
@@ -56,7 +63,7 @@ pub struct SaveUserSettings {
   pub theme: Option<String>,
   pub default_sort_type: Option<i16>,
   pub default_listing_type: Option<i16>,
-  pub lang: Option<String>,
+  pub interface_language: Option<String>,
   pub avatar: Option<String>,
   pub banner: Option<String>,
   pub display_name: Option<String>,
@@ -69,6 +76,7 @@ pub struct SaveUserSettings {
   pub show_bot_accounts: Option<bool>,
   pub show_read_posts: Option<bool>,
   pub show_new_post_notifs: Option<bool>,
+  pub discussion_languages: Option<Vec<LanguageId>>,
   pub auth: Sensitive<String>,
 }
 
index 4884c15dae60c6707e45c89733ea9b6070b75ee0..c9d92380d5bee26c5da0b857d01e7469cf8c24e4 100644 (file)
@@ -1,6 +1,6 @@
 use crate::sensitive::Sensitive;
 use lemmy_db_schema::{
-  newtypes::{CommentId, CommunityId, DbUrl, PostId, PostReportId},
+  newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
   ListingType,
   SortType,
 };
@@ -17,6 +17,7 @@ pub struct CreatePost {
   pub body: Option<String>,
   pub honeypot: Option<String>,
   pub nsfw: Option<bool>,
+  pub language_id: Option<LanguageId>,
   pub auth: Sensitive<String>,
 }
 
@@ -71,6 +72,7 @@ pub struct EditPost {
   pub url: Option<Url>,
   pub body: Option<String>,
   pub nsfw: Option<bool>,
+  pub language_id: Option<LanguageId>,
   pub auth: Sensitive<String>,
 }
 
index f5098a5eec35b6e1c01a0c7de8359c9ed81ecf42..eb520e29e89091122f19b6b061a7f6d56a16849c 100644 (file)
@@ -1,6 +1,7 @@
 use crate::sensitive::Sensitive;
 use lemmy_db_schema::{
   newtypes::{CommentId, CommunityId, PersonId, PostId},
+  source::language::Language,
   ListingType,
   ModlogActionType,
   SearchType,
@@ -168,6 +169,7 @@ pub struct GetSiteResponse {
   pub version: String,
   pub my_user: Option<MyUserInfo>,
   pub federated_instances: Option<FederatedInstances>, // Federation may be disabled
+  pub all_languages: Vec<Language>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -177,6 +179,7 @@ pub struct MyUserInfo {
   pub moderates: Vec<CommunityModeratorView>,
   pub community_blocks: Vec<CommunityBlockView>,
   pub person_blocks: Vec<PersonBlockView>,
+  pub discussion_languages: Vec<Language>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
index bf99b7b94eba962fac1869e910cb4e7c8a668343..d2a009787745609e1faaaa7063c3a0d00bbf24da 100644 (file)
@@ -47,16 +47,14 @@ where
 {
   let pool = pool.clone();
   let blocking_span = tracing::info_span!("blocking operation");
-  let res = actix_web::web::block(move || {
+  actix_web::web::block(move || {
     let entered = blocking_span.enter();
     let conn = pool.get()?;
     let res = (f)(&conn);
     drop(entered);
     Ok(res) as Result<T, LemmyError>
   })
-  .await?;
-
-  res
+  .await?
 }
 
 #[tracing::instrument(skip_all)]
@@ -407,7 +405,7 @@ pub async fn send_password_reset_email(
   .await??;
 
   let email = &user.local_user.email.to_owned().expect("email");
-  let lang = get_user_lang(user);
+  let lang = get_interface_language(user);
   let subject = &lang.password_reset_subject(&user.person.name);
   let protocol_and_hostname = settings.get_protocol_and_hostname();
   let reset_link = format!("{}/password_change/{}", protocol_and_hostname, &token);
@@ -434,7 +432,7 @@ pub async fn send_verification_email(
   );
   blocking(pool, move |conn| EmailVerification::create(conn, &form)).await??;
 
-  let lang = get_user_lang(user);
+  let lang = get_interface_language(user);
   let subject = lang.verify_email_subject(&settings.hostname);
   let body = lang.verify_email_body(&settings.hostname, &user.person.name, verify_link);
   send_email(&subject, new_email, &user.person.name, &body, settings)?;
@@ -447,14 +445,14 @@ pub fn send_email_verification_success(
   settings: &Settings,
 ) -> Result<(), LemmyError> {
   let email = &user.local_user.email.to_owned().expect("email");
-  let lang = get_user_lang(user);
+  let lang = get_interface_language(user);
   let subject = &lang.email_verified_subject(&user.person.actor_id);
   let body = &lang.email_verified_body();
   send_email(subject, email, &user.person.name, body, settings)
 }
 
-pub fn get_user_lang(user: &LocalUserView) -> Lang {
-  let user_lang = LanguageId::new(user.local_user.lang.clone());
+pub fn get_interface_language(user: &LocalUserView) -> Lang {
+  let user_lang = LanguageId::new(user.local_user.interface_language.clone());
   Lang::from_language_id(&user_lang).unwrap_or_else(|| {
     let en = LanguageId::new("en");
     Lang::from_language_id(&en).expect("default language")
@@ -466,7 +464,7 @@ pub fn send_application_approved_email(
   settings: &Settings,
 ) -> Result<(), LemmyError> {
   let email = &user.local_user.email.to_owned().expect("email");
-  let lang = get_user_lang(user);
+  let lang = get_interface_language(user);
   let subject = lang.registration_approved_subject(&user.person.actor_id);
   let body = lang.registration_approved_body(&settings.hostname);
   send_email(&subject, email, &user.person.name, &body, settings)
@@ -488,7 +486,7 @@ pub async fn check_registration_application(
     })
     .await??;
     if let Some(deny_reason) = registration.deny_reason {
-      let lang = get_user_lang(local_user_view);
+      let lang = get_interface_language(local_user_view);
       let registration_denied_message = format!("{}: {}", lang.registration_denied(), &deny_reason);
       return Err(LemmyError::from_message(&registration_denied_message));
     } else {
index 1a6c100db4bb92f4e900931142dff37aec363419..ef74ece866d7bf4c8e5d993c58dd8cdfd2a9da1d 100644 (file)
@@ -21,6 +21,7 @@ use lemmy_apub::{
 use lemmy_db_schema::{
   source::{
     community::Community,
+    language::Language,
     post::{Post, PostForm, PostLike, PostLikeForm},
   },
   traits::{Crud, Likeable},
@@ -90,6 +91,15 @@ impl PerformCrud for CreatePost {
       .map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url)))
       .unwrap_or_default();
 
+    let language_id = Some(
+      data.language_id.unwrap_or(
+        blocking(context.pool(), move |conn| {
+          Language::read_undetermined(conn)
+        })
+        .await??,
+      ),
+    );
+
     let post_form = PostForm {
       name: data.name.trim().to_owned(),
       url,
@@ -100,6 +110,7 @@ impl PerformCrud for CreatePost {
       embed_title,
       embed_description,
       embed_video_url,
+      language_id,
       thumbnail_url: Some(thumbnail_url),
       ..PostForm::default()
     };
index 468910b24c697802af0e5f8c5cc8892387b51022..9d9b156c617979adfdb18ddc5c3aa8b6e16702ab 100644 (file)
@@ -32,15 +32,7 @@ impl PerformCrud for GetPosts {
 
     check_private_instance(&local_user_view, context.pool()).await?;
 
-    let person_id = local_user_view.to_owned().map(|l| l.person.id);
-
-    let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
-    let show_bot_accounts = local_user_view
-      .as_ref()
-      .map(|t| t.local_user.show_bot_accounts);
-    let show_read_posts = local_user_view
-      .as_ref()
-      .map(|t| t.local_user.show_read_posts);
+    let is_logged_in = local_user_view.is_some();
 
     let sort = data.sort;
     let listing_type = listing_type_with_site_default(data.type_, context.pool()).await?;
@@ -63,13 +55,9 @@ impl PerformCrud for GetPosts {
         .conn(conn)
         .listing_type(Some(listing_type))
         .sort(sort)
-        .show_nsfw(show_nsfw)
-        .show_bot_accounts(show_bot_accounts)
-        .show_read_posts(show_read_posts)
         .community_id(community_id)
         .community_actor_id(community_actor_id)
         .saved_only(saved_only)
-        .my_person_id(person_id)
         .page(page)
         .limit(limit)
         .build()
@@ -79,7 +67,7 @@ impl PerformCrud for GetPosts {
     .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?;
 
     // Blank out deleted or removed info for non-logged in users
-    if person_id.is_none() {
+    if !is_logged_in {
       for pv in posts
         .iter_mut()
         .filter(|p| p.post.deleted || p.post.removed)
index fdb41186a0370e2ab7c54640a85339e5c93f9b12..d96a12d5e3ff4b1cdf0c8180139b7e5adbbe926c 100644 (file)
@@ -14,7 +14,10 @@ use lemmy_apub::protocol::activities::{
   CreateOrUpdateType,
 };
 use lemmy_db_schema::{
-  source::post::{Post, PostForm},
+  source::{
+    language::Language,
+    post::{Post, PostForm},
+  },
   traits::Crud,
   utils::{diesel_option_overwrite, naive_now},
 };
@@ -82,6 +85,15 @@ impl PerformCrud for EditPost {
       .map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url)))
       .unwrap_or_default();
 
+    let language_id = Some(
+      data.language_id.unwrap_or(
+        blocking(context.pool(), move |conn| {
+          Language::read_undetermined(conn)
+        })
+        .await??,
+      ),
+    );
+
     let post_form = PostForm {
       creator_id: orig_post.creator_id.to_owned(),
       community_id: orig_post.community_id,
@@ -93,6 +105,7 @@ impl PerformCrud for EditPost {
       embed_title,
       embed_description,
       embed_video_url,
+      language_id,
       thumbnail_url: Some(thumbnail_url),
       ..PostForm::default()
     };
index 979c575ded9f0b09cb15e1a0071ee24c7db73d90..56d68c6e57d62d8df861c5d2e1f8dfc3d8aa50f2 100644 (file)
@@ -5,8 +5,8 @@ use lemmy_api_common::{
   utils::{
     blocking,
     check_person_block,
+    get_interface_language,
     get_local_user_view_from_jwt,
-    get_user_lang,
     send_email_to_user,
   },
 };
@@ -109,7 +109,7 @@ impl PerformCrud for CreatePrivateMessage {
         LocalUserView::read_person(conn, recipient_id)
       })
       .await??;
-      let lang = get_user_lang(&local_recipient);
+      let lang = get_interface_language(&local_recipient);
       let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
       send_email_to_user(
         &local_recipient,
index d34be2cad17e2a2bc8d36239a771057ab489b7da..95ecf6c1b901d7189ccd5c7e7dd55ce722a264db 100644 (file)
@@ -5,7 +5,8 @@ use lemmy_api_common::{
   site::{CreateSite, GetSite, GetSiteResponse, MyUserInfo},
   utils::{blocking, build_federated_instances, get_local_user_settings_view_from_jwt_opt},
 };
-use lemmy_db_views::structs::SiteView;
+use lemmy_db_schema::source::language::Language;
+use lemmy_db_views::structs::{LocalUserDiscussionLanguageView, SiteView};
 use lemmy_db_views_actor::structs::{
   CommunityBlockView,
   CommunityFollowerView,
@@ -83,6 +84,8 @@ impl PerformCrud for GetSite {
     .await?
     {
       let person_id = local_user_view.person.id;
+      let local_user_id = local_user_view.local_user.id;
+
       let follows = blocking(context.pool(), move |conn| {
         CommunityFollowerView::for_person(conn, person_id)
       })
@@ -109,12 +112,19 @@ impl PerformCrud for GetSite {
       .await?
       .map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?;
 
+      let discussion_languages = blocking(context.pool(), move |conn| {
+        LocalUserDiscussionLanguageView::read_languages(conn, local_user_id)
+      })
+      .await?
+      .map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?;
+
       Some(MyUserInfo {
         local_user_view,
         follows,
         moderates,
         community_blocks,
         person_blocks,
+        discussion_languages,
       })
     } else {
       None
@@ -122,6 +132,8 @@ impl PerformCrud for GetSite {
 
     let federated_instances = build_federated_instances(context.pool(), context.settings()).await?;
 
+    let all_languages = blocking(context.pool(), Language::read_all).await??;
+
     Ok(GetSiteResponse {
       site_view,
       admins,
@@ -129,6 +141,7 @@ impl PerformCrud for GetSite {
       version: version::VERSION.to_string(),
       my_user,
       federated_instances,
+      all_languages,
     })
   }
 }
index 7d35f3207e0d36d53eed69590807afc911d06611..9fc4fe5b111c3cc86b42d1f1fd9c8996a1363617 100644 (file)
@@ -15,6 +15,7 @@ use lemmy_db_schema::{
   aggregates::structs::PersonAggregates,
   source::{
     local_user::{LocalUser, LocalUserForm},
+    local_user_language::LocalUserLanguage,
     person::{Person, PersonForm},
     registration_application::{RegistrationApplication, RegistrationApplicationForm},
     site::Site,
@@ -167,10 +168,17 @@ impl PerformCrud for Register {
       }
     };
 
+    // Update the users languages to all by default
+    let local_user_id = inserted_local_user.id;
+    blocking(context.pool(), move |conn| {
+      LocalUserLanguage::update_user_languages(conn, None, local_user_id)
+    })
+    .await??;
+
     if require_application {
       // Create the registration application
       let form = RegistrationApplicationForm {
-        local_user_id: Some(inserted_local_user.id),
+        local_user_id: Some(local_user_id),
         // We already made sure answer was not null above
         answer: data.answer.to_owned(),
         ..RegistrationApplicationForm::default()
index 7ec52501e98933b789d76e2e0479ceb370f9f390..15630924ae08f78cf5d4e81e07b9dceee48c809a 100644 (file)
@@ -34,13 +34,9 @@ impl PerformCrud for GetPersonDetails {
 
     check_private_instance(&local_user_view, context.pool()).await?;
 
-    let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
     let show_bot_accounts = local_user_view
       .as_ref()
       .map(|t| t.local_user.show_bot_accounts);
-    let show_read_posts = local_user_view
-      .as_ref()
-      .map(|t| t.local_user.show_read_posts);
 
     let person_details_id = match data.person_id {
       Some(id) => id,
@@ -58,8 +54,6 @@ impl PerformCrud for GetPersonDetails {
       }
     };
 
-    let person_id = local_user_view.map(|uv| uv.person.id);
-
     // You don't need to return settings for the user, since this comes back with GetSite
     // `my_user`
     let person_view = blocking(context.pool(), move |conn| {
@@ -77,15 +71,12 @@ impl PerformCrud for GetPersonDetails {
       let posts_query = PostQuery::builder()
         .conn(conn)
         .sort(sort)
-        .show_nsfw(show_nsfw)
-        .show_bot_accounts(show_bot_accounts)
-        .show_read_posts(show_read_posts)
         .saved_only(saved_only)
         .community_id(community_id)
-        .my_person_id(person_id)
         .page(page)
         .limit(limit);
 
+      let person_id = local_user_view.map(|uv| uv.person.id);
       let comments_query = CommentQuery::builder()
         .conn(conn)
         .my_person_id(person_id)
index 37718b234da04888e4e6205d50279f6e3856098e..4fff07c8b751334627c406663967929ba15ad25e 100644 (file)
     "commentsEnabled": true,
     "sensitive": false,
     "stickied": false,
+    "language": {
+      "identifier": "ko",
+      "name": "한국어"
+    },
     "published": "2021-10-29T15:10:51.557399+00:00"
   },
   "cc": [
index b90ee549a12ee4c012c0d6288cdb043f19181a9d..816c32495fd2a23feff79b504101cf110111a04f 100644 (file)
@@ -27,5 +27,9 @@
   "sensitive": false,
   "commentsEnabled": true,
   "stickied": true,
+  "language": {
+    "identifier": "fr",
+    "name": "Français"
+  },
   "published": "2021-02-26T12:35:34.292626+00:00"
 }
index 64cfa0f831def669b2b8764bc913fa83ca17d301..8fd8588b87ad7ae3bc75f959548724a7ad28e67f 100644 (file)
@@ -40,7 +40,7 @@ fn local_instance(context: &LemmyContext) -> &'static LocalInstance {
       .debug(context.settings().federation.debug)
       // TODO No idea why, but you can't pass context.settings() to the verify_url_function closure
       // without the value getting captured.
-      .verify_url_function(|url| check_apub_id_valid(url, &SETTINGS.to_owned()))
+      .verify_url_function(|url| check_apub_id_valid(url, &SETTINGS))
       .build()
       .expect("configure federation");
     LocalInstance::new(
index bc1b44f54a4ef656cf38e06611f835f5a976dba4..ea536e51fb8f138b5daf4662113111067b8711c8 100644 (file)
@@ -4,7 +4,7 @@ use crate::{
   local_instance,
   objects::{read_from_string_or_source_opt, verify_is_remote_object},
   protocol::{
-    objects::page::{Attachment, AttributedTo, Page, PageType},
+    objects::page::{Attachment, AttributedTo, LanguageTag, Page, PageType},
     ImageObject,
     Source,
   },
@@ -22,6 +22,7 @@ use lemmy_db_schema::{
   self,
   source::{
     community::Community,
+    language::Language,
     moderator::{ModLockPost, ModLockPostForm, ModStickyPost, ModStickyPostForm},
     person::Person,
     post::{Post, PostForm},
@@ -98,6 +99,11 @@ impl ApubObject for ApubPost {
       Community::read(conn, community_id)
     })
     .await??;
+    let language = self.language_id;
+    let language = blocking(context.pool(), move |conn| {
+      Language::read_from_id(conn, language)
+    })
+    .await??;
 
     let page = Page {
       kind: PageType::Page,
@@ -115,6 +121,7 @@ impl ApubObject for ApubPost {
       comments_enabled: Some(!self.locked),
       sensitive: Some(self.nsfw),
       stickied: Some(self.stickied),
+      language: LanguageTag::new(language),
       published: Some(convert_datetime(self.published)),
       updated: self.updated.map(convert_datetime),
     };
@@ -181,6 +188,11 @@ impl ApubObject for ApubPost {
       let body_slurs_removed =
         read_from_string_or_source_opt(&page.content, &page.media_type, &page.source)
           .map(|s| Some(remove_slurs(&s, &context.settings().slur_regex())));
+      let language = page.language.map(|l| l.identifier);
+      let language = blocking(context.pool(), move |conn| {
+        Language::read_id_from_code_opt(conn, language.as_deref())
+      })
+      .await??;
 
       PostForm {
         name: page.name.clone(),
@@ -201,6 +213,7 @@ impl ApubObject for ApubPost {
         thumbnail_url: Some(thumbnail_url),
         ap_id: Some(page.id.clone().into()),
         local: Some(false),
+        language_id: language,
       }
     } else {
       // if is mod action, only update locked/stickied fields, nothing else
index 9a47324ef586541c4a3b21eb88f73226c5f65bcd..10e30a770ea19ba4d5778f13f92917b0987be557 100644 (file)
@@ -16,7 +16,7 @@ use activitypub_federation::{
 use activitystreams_kinds::{link::LinkType, object::ImageType};
 use chrono::{DateTime, FixedOffset};
 use itertools::Itertools;
-use lemmy_db_schema::newtypes::DbUrl;
+use lemmy_db_schema::{newtypes::DbUrl, source::language::Language};
 use lemmy_utils::error::LemmyError;
 use lemmy_websocket::LemmyContext;
 use serde::{Deserialize, Serialize};
@@ -62,8 +62,29 @@ pub struct Page {
   pub(crate) stickied: Option<bool>,
   pub(crate) published: Option<DateTime<FixedOffset>>,
   pub(crate) updated: Option<DateTime<FixedOffset>>,
+  pub(crate) language: Option<LanguageTag>,
 }
 
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct LanguageTag {
+  pub(crate) identifier: String,
+  pub(crate) name: String,
+}
+
+impl LanguageTag {
+  pub(crate) fn new(lang: Language) -> Option<LanguageTag> {
+    // undetermined
+    if lang.code == "und" {
+      None
+    } else {
+      Some(LanguageTag {
+        identifier: lang.code,
+        name: lang.name,
+      })
+    }
+  }
+}
 #[derive(Clone, Debug, Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub(crate) struct Link {
diff --git a/crates/db_schema/src/impls/language.rs b/crates/db_schema/src/impls/language.rs
new file mode 100644 (file)
index 0000000..f2895e8
--- /dev/null
@@ -0,0 +1,54 @@
+use crate::{newtypes::LanguageId, source::language::Language};
+use diesel::{result::Error, PgConnection, RunQueryDsl, *};
+
+impl Language {
+  pub fn read_all(conn: &PgConnection) -> Result<Vec<Language>, Error> {
+    use crate::schema::language::dsl::*;
+    language.load::<Self>(conn)
+  }
+
+  pub fn read_from_id(conn: &PgConnection, id_: LanguageId) -> Result<Language, Error> {
+    use crate::schema::language::dsl::*;
+    language.filter(id.eq(id_)).first::<Self>(conn)
+  }
+
+  pub fn read_id_from_code(conn: &PgConnection, code_: &str) -> Result<LanguageId, Error> {
+    use crate::schema::language::dsl::*;
+    Ok(language.filter(code.eq(code_)).first::<Self>(conn)?.id)
+  }
+
+  pub fn read_id_from_code_opt(
+    conn: &PgConnection,
+    code_: Option<&str>,
+  ) -> Result<Option<LanguageId>, Error> {
+    if let Some(code_) = code_ {
+      Ok(Some(Language::read_id_from_code(conn, code_)?))
+    } else {
+      Ok(None)
+    }
+  }
+
+  pub fn read_undetermined(conn: &PgConnection) -> Result<LanguageId, Error> {
+    use crate::schema::language::dsl::*;
+    Ok(language.filter(code.eq("und")).first::<Self>(conn)?.id)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{source::language::Language, utils::establish_unpooled_connection};
+  use serial_test::serial;
+
+  #[test]
+  #[serial]
+  fn test_languages() {
+    let conn = establish_unpooled_connection();
+
+    let all = Language::read_all(&conn).unwrap();
+
+    assert_eq!(184, all.len());
+    assert_eq!("ak", all[5].code);
+    assert_eq!("lv", all[99].code);
+    assert_eq!("yi", all[179].code);
+  }
+}
index 419ab7928947de937fd67aee201c0c634d946db4..8d5370d0ab854741c7e94b4da29085330468c2b7 100644 (file)
@@ -23,7 +23,7 @@ mod safe_settings_type {
     theme,
     default_sort_type,
     default_listing_type,
-    lang,
+    interface_language,
     show_avatars,
     send_notifications_to_email,
     validator_time,
@@ -48,7 +48,7 @@ mod safe_settings_type {
         theme,
         default_sort_type,
         default_listing_type,
-        lang,
+        interface_language,
         show_avatars,
         send_notifications_to_email,
         validator_time,
diff --git a/crates/db_schema/src/impls/local_user_language.rs b/crates/db_schema/src/impls/local_user_language.rs
new file mode 100644 (file)
index 0000000..d38a086
--- /dev/null
@@ -0,0 +1,42 @@
+use crate::{
+  newtypes::{LanguageId, LocalUserId},
+  source::{language::Language, local_user_language::*},
+};
+use diesel::{result::Error, PgConnection, RunQueryDsl, *};
+
+impl LocalUserLanguage {
+  /// Update the user's languages.
+  ///
+  /// If no language_id vector is given, it will show all languages
+  pub fn update_user_languages(
+    conn: &PgConnection,
+    language_ids: Option<Vec<LanguageId>>,
+    for_local_user_id: LocalUserId,
+  ) -> Result<(), Error> {
+    use crate::schema::local_user_language::dsl::*;
+
+    // If no language is given, read all languages
+    let lang_ids = language_ids.unwrap_or(
+      Language::read_all(conn)?
+        .into_iter()
+        .map(|l| l.id)
+        .collect(),
+    );
+
+    conn.build_transaction().read_write().run(|| {
+      // Clear the current user languages
+      delete(local_user_language.filter(local_user_id.eq(for_local_user_id))).execute(conn)?;
+
+      for l in lang_ids {
+        let form = LocalUserLanguageForm {
+          local_user_id: for_local_user_id,
+          language_id: l,
+        };
+        insert_into(local_user_language)
+          .values(form)
+          .get_result::<Self>(conn)?;
+      }
+      Ok(())
+    })
+  }
+}
index 1219f50f3604d26742a04aa916a64324c8254c8e..bd5df90420eb619c7329276ac97d7ed6df2782c4 100644 (file)
@@ -5,7 +5,9 @@ pub mod comment_report;
 pub mod community;
 pub mod community_block;
 pub mod email_verification;
+pub mod language;
 pub mod local_user;
+pub mod local_user_language;
 pub mod moderator;
 pub mod password_reset_request;
 pub mod person;
index 9018cdadfd4311773ea6329641ce5aed5377b251..07c652e3148265b32d117500803f3c29cf60351b 100644 (file)
@@ -387,6 +387,7 @@ mod tests {
       thumbnail_url: None,
       ap_id: inserted_post.ap_id.to_owned(),
       local: true,
+      language_id: Default::default(),
     };
 
     // Post Like
index 22c4aeb896efbd49cdfb5b07efec38b8a11c39df..e91bfea656280abea48e905152cd5afb54d49e17 100644 (file)
@@ -69,6 +69,14 @@ pub struct CommentReportId(i32);
 #[cfg_attr(feature = "full", derive(DieselNewType))]
 pub struct PostReportId(i32);
 
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
+#[cfg_attr(feature = "full", derive(DieselNewType))]
+pub struct LanguageId(pub i32);
+
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
+#[cfg_attr(feature = "full", derive(DieselNewType))]
+pub struct LocalUserLanguageId(pub i32);
+
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
 #[cfg_attr(feature = "full", derive(DieselNewType))]
 pub struct CommentReplyId(i32);
index 4c2caaf06c738071bae18d3e1b9fb2251052edb8..fcd99c1b18a351bc1e4a855f4db9c01eb25611b1 100644 (file)
@@ -156,7 +156,7 @@ table! {
         theme -> Varchar,
         default_sort_type -> Int2,
         default_listing_type -> Int2,
-        lang -> Varchar,
+        interface_language -> Varchar,
         show_avatars -> Bool,
         send_notifications_to_email -> Bool,
         validator_time -> Timestamp,
@@ -375,6 +375,7 @@ table! {
         thumbnail_url -> Nullable<Text>,
         ap_id -> Varchar,
         local -> Bool,
+        language_id -> Int4,
     }
 }
 
@@ -645,6 +646,22 @@ table! {
     }
 }
 
+table! {
+    language (id) {
+        id -> Int4,
+        code -> Text,
+        name -> Text,
+    }
+}
+
+table! {
+    local_user_language(id) {
+        id -> Int4,
+        local_user_id -> Int4,
+        language_id -> Int4,
+    }
+}
+
 joinable!(person_mention -> person_alias_1 (recipient_id));
 joinable!(comment_reply -> person_alias_1 (recipient_id));
 joinable!(post -> person_alias_1 (creator_id));
@@ -711,6 +728,9 @@ joinable!(registration_application -> local_user (local_user_id));
 joinable!(registration_application -> person (admin_id));
 joinable!(mod_hide_community -> person (mod_person_id));
 joinable!(mod_hide_community -> community (community_id));
+joinable!(post -> language (language_id));
+joinable!(local_user_language -> language (language_id));
+joinable!(local_user_language -> local_user (local_user_id));
 
 joinable!(admin_purge_comment -> person (admin_person_id));
 joinable!(admin_purge_comment -> post (post_id));
@@ -767,5 +787,7 @@ allow_tables_to_appear_in_same_query!(
   admin_purge_person,
   admin_purge_post,
   email_verification,
-  registration_application
+  registration_application,
+  language,
+  local_user_language
 );
diff --git a/crates/db_schema/src/source/language.rs b/crates/db_schema/src/source/language.rs
new file mode 100644 (file)
index 0000000..7b2a730
--- /dev/null
@@ -0,0 +1,15 @@
+use crate::newtypes::LanguageId;
+use serde::{Deserialize, Serialize};
+
+#[cfg(feature = "full")]
+use crate::schema::language;
+
+#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
+#[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
+#[cfg_attr(feature = "full", table_name = "language")]
+pub struct Language {
+  #[serde(skip)]
+  pub id: LanguageId,
+  pub code: String,
+  pub name: String,
+}
index 57767bb179700fc49156f78950c57b659d671699..7c24d61bcea37a563b9c5a767daf3274f8901f95 100644 (file)
@@ -16,7 +16,7 @@ pub struct LocalUser {
   pub theme: String,
   pub default_sort_type: i16,
   pub default_listing_type: i16,
-  pub lang: String,
+  pub interface_language: String,
   pub show_avatars: bool,
   pub send_notifications_to_email: bool,
   pub validator_time: chrono::NaiveDateTime,
@@ -40,7 +40,7 @@ pub struct LocalUserForm {
   pub theme: Option<String>,
   pub default_sort_type: Option<i16>,
   pub default_listing_type: Option<i16>,
-  pub lang: Option<String>,
+  pub interface_language: Option<String>,
   pub show_avatars: Option<bool>,
   pub send_notifications_to_email: Option<bool>,
   pub show_bot_accounts: Option<bool>,
@@ -63,7 +63,7 @@ pub struct LocalUserSettings {
   pub theme: String,
   pub default_sort_type: i16,
   pub default_listing_type: i16,
-  pub lang: String,
+  pub interface_language: String,
   pub show_avatars: bool,
   pub send_notifications_to_email: bool,
   pub validator_time: chrono::NaiveDateTime,
diff --git a/crates/db_schema/src/source/local_user_language.rs b/crates/db_schema/src/source/local_user_language.rs
new file mode 100644 (file)
index 0000000..64492ee
--- /dev/null
@@ -0,0 +1,23 @@
+use crate::newtypes::{LanguageId, LocalUserId, LocalUserLanguageId};
+use serde::{Deserialize, Serialize};
+
+#[cfg(feature = "full")]
+use crate::schema::local_user_language;
+
+#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
+#[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
+#[cfg_attr(feature = "full", table_name = "local_user_language")]
+pub struct LocalUserLanguage {
+  #[serde(skip)]
+  pub id: LocalUserLanguageId,
+  pub local_user_id: LocalUserId,
+  pub language_id: LanguageId,
+}
+
+#[derive(Clone)]
+#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
+#[cfg_attr(feature = "full", table_name = "local_user_language")]
+pub struct LocalUserLanguageForm {
+  pub local_user_id: LocalUserId,
+  pub language_id: LanguageId,
+}
index 302c9f6381d2cc51f2ff88ad97213a80c3e688be..f142bbaecc30c2edb6872fcd63577d100af877d9 100644 (file)
@@ -6,7 +6,9 @@ pub mod comment_report;
 pub mod community;
 pub mod community_block;
 pub mod email_verification;
+pub mod language;
 pub mod local_user;
+pub mod local_user_language;
 pub mod moderator;
 pub mod password_reset_request;
 pub mod person;
index a69e6a2caea19c2852b580754189b4d7875a3788..7e192a72aeecc4d32f6326a9193423ef9f9ee95f 100644 (file)
@@ -1,4 +1,4 @@
-use crate::newtypes::{CommunityId, DbUrl, PersonId, PostId};
+use crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId};
 use serde::{Deserialize, Serialize};
 
 #[cfg(feature = "full")]
@@ -27,6 +27,7 @@ pub struct Post {
   pub thumbnail_url: Option<DbUrl>,
   pub ap_id: DbUrl,
   pub local: bool,
+  pub language_id: LanguageId,
 }
 
 #[derive(Default)]
@@ -51,6 +52,7 @@ pub struct PostForm {
   pub thumbnail_url: Option<Option<DbUrl>>,
   pub ap_id: Option<DbUrl>,
   pub local: Option<bool>,
+  pub language_id: Option<LanguageId>,
 }
 
 #[derive(PartialEq, Debug)]
index 9fa543d03c144d20c2cbbcc514f5dea2fbf8f77d..2516b45e42ef7a542d160060ae8d7674fd967256 100644 (file)
@@ -574,6 +574,7 @@ mod tests {
         thumbnail_url: None,
         ap_id: inserted_post.ap_id.to_owned(),
         local: true,
+        language_id: Default::default(),
       },
       community: CommunitySafe {
         id: inserted_community.id,
index b5567f87bb1edb1b9a353895216fb01d02b482cb..507d37f7542e6aabafea52df03d3b40b8cfdfb61 100644 (file)
@@ -6,6 +6,8 @@ pub mod comment_report_view;
 #[cfg(feature = "full")]
 pub mod comment_view;
 #[cfg(feature = "full")]
+pub mod local_user_discussion_language_view;
+#[cfg(feature = "full")]
 pub mod local_user_view;
 #[cfg(feature = "full")]
 pub mod post_report_view;
diff --git a/crates/db_views/src/local_user_discussion_language_view.rs b/crates/db_views/src/local_user_discussion_language_view.rs
new file mode 100644 (file)
index 0000000..5c88a2f
--- /dev/null
@@ -0,0 +1,32 @@
+use crate::structs::LocalUserDiscussionLanguageView;
+use diesel::{result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
+use lemmy_db_schema::{
+  newtypes::LocalUserId,
+  schema::{language, local_user, local_user_language},
+  source::{
+    language::Language,
+    local_user::{LocalUser, LocalUserSettings},
+  },
+  traits::ToSafeSettings,
+};
+
+type LocalUserDiscussionLanguageViewTuple = (LocalUserSettings, Language);
+
+impl LocalUserDiscussionLanguageView {
+  pub fn read_languages(
+    conn: &PgConnection,
+    local_user_id: LocalUserId,
+  ) -> Result<Vec<Language>, Error> {
+    let res = local_user_language::table
+      .inner_join(local_user::table)
+      .inner_join(language::table)
+      .select((
+        LocalUser::safe_settings_columns_tuple(),
+        language::all_columns,
+      ))
+      .filter(local_user::id.eq(local_user_id))
+      .load::<LocalUserDiscussionLanguageViewTuple>(conn)?;
+
+    Ok(res.into_iter().map(|a| a.1).collect::<Vec<Language>>())
+  }
+}
index c9b4721723b297c4b706e7c5d3bff4c9af1e19fe..761925a9cd36ec6ce534db8abcee9c93f897fbab 100644 (file)
@@ -2,12 +2,14 @@ use crate::structs::PostView;
 use diesel::{dsl::*, pg::Pg, result::Error, *};
 use lemmy_db_schema::{
   aggregates::structs::PostAggregates,
-  newtypes::{CommunityId, DbUrl, PersonId, PostId},
+  newtypes::{CommunityId, DbUrl, LocalUserId, PersonId, PostId},
   schema::{
     community,
     community_block,
     community_follower,
     community_person_ban,
+    language,
+    local_user_language,
     person,
     person_block,
     post,
@@ -18,6 +20,7 @@ use lemmy_db_schema::{
   },
   source::{
     community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
+    language::Language,
     person::{Person, PersonSafe},
     person_block::PersonBlock,
     post::{Post, PostRead, PostSaved},
@@ -41,6 +44,7 @@ type PostViewTuple = (
   Option<PostRead>,
   Option<PersonBlock>,
   Option<i16>,
+  Language,
 );
 
 impl PostView {
@@ -51,7 +55,6 @@ impl PostView {
   ) -> Result<Self, Error> {
     // The left join below will return None in this case
     let person_id_join = my_person_id.unwrap_or(PersonId(-1));
-
     let (
       post,
       creator,
@@ -63,6 +66,7 @@ impl PostView {
       read,
       creator_blocked,
       post_like,
+      language,
     ) = post::table
       .find(post_id)
       .inner_join(person::table)
@@ -115,6 +119,7 @@ impl PostView {
             .and(post_like::person_id.eq(person_id_join)),
         ),
       )
+      .inner_join(language::table)
       .select((
         post::all_columns,
         Person::safe_columns_tuple(),
@@ -126,6 +131,7 @@ impl PostView {
         post_read::all_columns.nullable(),
         person_block::all_columns.nullable(),
         post_like::score.nullable(),
+        language::all_columns,
       ))
       .first::<PostViewTuple>(conn)?;
 
@@ -148,6 +154,7 @@ impl PostView {
       read: read.is_some(),
       creator_blocked: creator_blocked.is_some(),
       my_vote,
+      language,
     })
   }
 }
@@ -163,6 +170,7 @@ pub struct PostQuery<'a> {
   community_id: Option<CommunityId>,
   community_actor_id: Option<DbUrl>,
   my_person_id: Option<PersonId>,
+  my_local_user_id: Option<LocalUserId>,
   search_term: Option<String>,
   url_search: Option<String>,
   show_nsfw: Option<bool>,
@@ -179,6 +187,7 @@ impl<'a> PostQuery<'a> {
 
     // The left join below will return None in this case
     let person_id_join = self.my_person_id.unwrap_or(PersonId(-1));
+    let local_user_id_join = self.my_local_user_id.unwrap_or(LocalUserId(-1));
 
     let mut query = post::table
       .inner_join(person::table)
@@ -238,6 +247,14 @@ impl<'a> PostQuery<'a> {
             .and(post_like::person_id.eq(person_id_join)),
         ),
       )
+      .inner_join(language::table)
+      .left_join(
+        local_user_language::table.on(
+          post::language_id
+            .eq(local_user_language::language_id)
+            .and(local_user_language::local_user_id.eq(local_user_id_join)),
+        ),
+      )
       .select((
         post::all_columns,
         Person::safe_columns_tuple(),
@@ -249,6 +266,7 @@ impl<'a> PostQuery<'a> {
         post_read::all_columns.nullable(),
         person_block::all_columns.nullable(),
         post_like::score.nullable(),
+        language::all_columns,
       ))
       .into_boxed();
 
@@ -323,6 +341,11 @@ impl<'a> PostQuery<'a> {
       query = query.filter(post_read::id.is_null());
     }
 
+    // Filter out the rows with missing languages
+    if self.my_local_user_id.is_some() {
+      query = query.filter(local_user_language::id.is_not_null());
+    }
+
     // Don't show blocked communities or persons
     if self.my_person_id.is_some() {
       query = query.filter(community_block::person_id.is_null());
@@ -403,6 +426,7 @@ impl ViewToVec for PostView {
         read: a.7.is_some(),
         creator_blocked: a.8.is_some(),
         my_vote: a.9,
+        language: a.10,
       })
       .collect::<Vec<Self>>()
   }
@@ -411,11 +435,16 @@ impl ViewToVec for PostView {
 #[cfg(test)]
 mod tests {
   use crate::post_view::{PostQuery, PostView};
+  use diesel::PgConnection;
   use lemmy_db_schema::{
     aggregates::structs::PostAggregates,
+    newtypes::LanguageId,
     source::{
       community::*,
       community_block::{CommunityBlock, CommunityBlockForm},
+      language::Language,
+      local_user::{LocalUser, LocalUserForm},
+      local_user_language::LocalUserLanguage,
       person::*,
       person_block::{PersonBlock, PersonBlockForm},
       post::*,
@@ -427,15 +456,16 @@ mod tests {
   };
   use serial_test::serial;
 
-  #[test]
-  #[serial]
-  fn test_crud() {
-    let conn = establish_unpooled_connection();
+  struct Data {
+    inserted_person: Person,
+    inserted_blocked_person: Person,
+    inserted_bot: Person,
+    inserted_community: Community,
+    inserted_post: Post,
+  }
 
+  fn init_data(conn: &PgConnection) -> Data {
     let person_name = "tegan".to_string();
-    let community_name = "test_community_3".to_string();
-    let post_name = "test post 3".to_string();
-    let bot_post_name = "test bot post".to_string();
 
     let new_person = PersonForm {
       name: person_name.to_owned(),
@@ -443,43 +473,44 @@ mod tests {
       ..PersonForm::default()
     };
 
-    let inserted_person = Person::create(&conn, &new_person).unwrap();
+    let inserted_person = Person::create(conn, &new_person).unwrap();
 
     let new_bot = PersonForm {
-      name: person_name.to_owned(),
+      name: "mybot".to_string(),
       bot_account: Some(true),
       public_key: Some("pubkey".to_string()),
       ..PersonForm::default()
     };
 
-    let inserted_bot = Person::create(&conn, &new_bot).unwrap();
+    let inserted_bot = Person::create(conn, &new_bot).unwrap();
 
     let new_community = CommunityForm {
-      name: community_name.to_owned(),
+      name: "test_community_3".to_string(),
       title: "nada".to_owned(),
       public_key: Some("pubkey".to_string()),
       ..CommunityForm::default()
     };
 
-    let inserted_community = Community::create(&conn, &new_community).unwrap();
+    let inserted_community = Community::create(conn, &new_community).unwrap();
 
     // Test a person block, make sure the post query doesn't include their post
     let blocked_person = PersonForm {
-      name: person_name.to_owned(),
+      name: person_name,
       public_key: Some("pubkey".to_string()),
       ..PersonForm::default()
     };
 
-    let inserted_blocked_person = Person::create(&conn, &blocked_person).unwrap();
+    let inserted_blocked_person = Person::create(conn, &blocked_person).unwrap();
 
     let post_from_blocked_person = PostForm {
       name: "blocked_person_post".to_string(),
       creator_id: inserted_blocked_person.id,
       community_id: inserted_community.id,
+      language_id: Some(LanguageId(1)),
       ..PostForm::default()
     };
 
-    Post::create(&conn, &post_from_blocked_person).unwrap();
+    Post::create(conn, &post_from_blocked_person).unwrap();
 
     // block that person
     let person_block = PersonBlockForm {
@@ -487,72 +518,58 @@ mod tests {
       target_id: inserted_blocked_person.id,
     };
 
-    PersonBlock::block(&conn, &person_block).unwrap();
+    PersonBlock::block(conn, &person_block).unwrap();
 
     // A sample post
     let new_post = PostForm {
-      name: post_name.to_owned(),
+      name: "test post 3".to_string(),
       creator_id: inserted_person.id,
       community_id: inserted_community.id,
+      language_id: Some(LanguageId(47)),
       ..PostForm::default()
     };
 
-    let inserted_post = Post::create(&conn, &new_post).unwrap();
+    let inserted_post = Post::create(conn, &new_post).unwrap();
 
     let new_bot_post = PostForm {
-      name: bot_post_name,
+      name: "test bot post".to_string(),
       creator_id: inserted_bot.id,
       community_id: inserted_community.id,
       ..PostForm::default()
     };
 
-    let _inserted_bot_post = Post::create(&conn, &new_bot_post).unwrap();
-
-    let post_like_form = PostLikeForm {
-      post_id: inserted_post.id,
-      person_id: inserted_person.id,
-      score: 1,
-    };
-
-    let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
+    let _inserted_bot_post = Post::create(conn, &new_bot_post).unwrap();
 
-    let expected_post_like = PostLike {
-      id: inserted_post_like.id,
-      post_id: inserted_post.id,
-      person_id: inserted_person.id,
-      published: inserted_post_like.published,
-      score: 1,
-    };
-
-    let read_post_listings_with_person = PostQuery::builder()
-      .conn(&conn)
-      .sort(Some(SortType::New))
-      .show_bot_accounts(Some(false))
-      .community_id(Some(inserted_community.id))
-      .my_person_id(Some(inserted_person.id))
-      .build()
-      .list()
-      .unwrap();
-
-    let read_post_listings_no_person = PostQuery::builder()
-      .conn(&conn)
-      .sort(Some(SortType::New))
-      .community_id(Some(inserted_community.id))
-      .build()
-      .list()
-      .unwrap();
+    Data {
+      inserted_person,
+      inserted_blocked_person,
+      inserted_bot,
+      inserted_community,
+      inserted_post,
+    }
+  }
 
-    let read_post_listing_no_person = PostView::read(&conn, inserted_post.id, None).unwrap();
-    let read_post_listing_with_person =
-      PostView::read(&conn, inserted_post.id, Some(inserted_person.id)).unwrap();
+  fn cleanup(data: Data, conn: &PgConnection) {
+    let num_deleted = Post::delete(conn, data.inserted_post.id).unwrap();
+    Community::delete(conn, data.inserted_community.id).unwrap();
+    Person::delete(conn, data.inserted_person.id).unwrap();
+    Person::delete(conn, data.inserted_bot.id).unwrap();
+    Person::delete(conn, data.inserted_blocked_person.id).unwrap();
+    assert_eq!(1, num_deleted);
+  }
 
-    let agg = PostAggregates::read(&conn, inserted_post.id).unwrap();
+  fn expected_post_listing(data: &Data, conn: &PgConnection) -> PostView {
+    let (inserted_person, inserted_community, inserted_post) = (
+      &data.inserted_person,
+      &data.inserted_community,
+      &data.inserted_post,
+    );
+    let agg = PostAggregates::read(conn, inserted_post.id).unwrap();
 
-    // the non person version
-    let expected_post_listing_no_person = PostView {
+    PostView {
       post: Post {
         id: inserted_post.id,
-        name: post_name,
+        name: inserted_post.name.clone(),
         creator_id: inserted_person.id,
         url: None,
         body: None,
@@ -570,11 +587,12 @@ mod tests {
         thumbnail_url: None,
         ap_id: inserted_post.ap_id.to_owned(),
         local: true,
+        language_id: LanguageId(47),
       },
       my_vote: None,
       creator: PersonSafe {
         id: inserted_person.id,
-        name: person_name,
+        name: inserted_person.name.clone(),
         display_name: None,
         published: inserted_person.published,
         avatar: None,
@@ -595,7 +613,7 @@ mod tests {
       creator_banned_from_community: false,
       community: CommunitySafe {
         id: inserted_community.id,
-        name: community_name,
+        name: inserted_community.name.clone(),
         icon: None,
         removed: false,
         deleted: false,
@@ -614,8 +632,8 @@ mod tests {
         id: agg.id,
         post_id: inserted_post.id,
         comments: 0,
-        score: 1,
-        upvotes: 1,
+        score: 0,
+        upvotes: 0,
         downvotes: 0,
         stickied: false,
         published: agg.published,
@@ -626,66 +644,239 @@ mod tests {
       read: false,
       saved: false,
       creator_blocked: false,
-    };
+      language: Language {
+        id: LanguageId(47),
+        code: "fr".to_string(),
+        name: "Français".to_string(),
+      },
+    }
+  }
 
-    // Test a community block
-    let community_block = CommunityBlockForm {
-      person_id: inserted_person.id,
-      community_id: inserted_community.id,
-    };
-    CommunityBlock::block(&conn, &community_block).unwrap();
+  #[test]
+  #[serial]
+  fn post_listing_with_person() {
+    let conn = establish_unpooled_connection();
+    let data = init_data(&conn);
 
-    let read_post_listings_with_person_after_block = PostQuery::builder()
+    let read_post_listing = PostQuery::builder()
       .conn(&conn)
       .sort(Some(SortType::New))
+      .community_id(Some(data.inserted_community.id))
       .show_bot_accounts(Some(false))
-      .community_id(Some(inserted_community.id))
-      .my_person_id(Some(inserted_person.id))
+      .my_person_id(Some(data.inserted_person.id))
       .build()
       .list()
       .unwrap();
 
-    // TODO More needs to be added here
-    let mut expected_post_listing_with_user = expected_post_listing_no_person.to_owned();
-    expected_post_listing_with_user.my_vote = Some(1);
+    let post_listing_single_with_person =
+      PostView::read(&conn, data.inserted_post.id, Some(data.inserted_person.id)).unwrap();
 
-    let like_removed = PostLike::remove(&conn, inserted_person.id, inserted_post.id).unwrap();
-    let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
-    PersonBlock::unblock(&conn, &person_block).unwrap();
-    CommunityBlock::unblock(&conn, &community_block).unwrap();
-    Community::delete(&conn, inserted_community.id).unwrap();
-    Person::delete(&conn, inserted_person.id).unwrap();
-    Person::delete(&conn, inserted_bot.id).unwrap();
-    Person::delete(&conn, inserted_blocked_person.id).unwrap();
+    let mut expected_post_listing_with_user = expected_post_listing(&data, &conn);
 
-    // The with user
-    assert_eq!(
-      expected_post_listing_with_user,
-      read_post_listings_with_person[0]
-    );
+    // Should be only one person, IE the bot post, and blocked should be missing
+    assert_eq!(1, read_post_listing.len());
+
+    assert_eq!(expected_post_listing_with_user, read_post_listing[0]);
+    expected_post_listing_with_user.my_vote = Some(0);
     assert_eq!(
       expected_post_listing_with_user,
-      read_post_listing_with_person
+      post_listing_single_with_person
     );
 
-    // Should be only one person, IE the bot post, and blocked should be missing
-    assert_eq!(1, read_post_listings_with_person.len());
+    let post_listings_with_bots = PostQuery::builder()
+      .conn(&conn)
+      .sort(Some(SortType::New))
+      .community_id(Some(data.inserted_community.id))
+      .show_bot_accounts(Some(true))
+      .my_person_id(Some(data.inserted_person.id))
+      .build()
+      .list()
+      .unwrap();
+    // should include bot post which has "undetermined" language
+    assert_eq!(2, post_listings_with_bots.len());
+
+    cleanup(data, &conn);
+  }
+
+  #[test]
+  #[serial]
+  fn post_listing_no_person() {
+    let conn = establish_unpooled_connection();
+    let data = init_data(&conn);
+
+    let read_post_listing_multiple_no_person = PostQuery::builder()
+      .conn(&conn)
+      .sort(Some(SortType::New))
+      .community_id(Some(data.inserted_community.id))
+      .build()
+      .list()
+      .unwrap();
+
+    let read_post_listing_single_no_person =
+      PostView::read(&conn, data.inserted_post.id, None).unwrap();
 
-    // Without the user
+    let expected_post_listing_no_person = expected_post_listing(&data, &conn);
+
+    // Should be 2 posts, with the bot post, and the blocked
+    assert_eq!(3, read_post_listing_multiple_no_person.len());
+
+    assert_eq!(
+      expected_post_listing_no_person,
+      read_post_listing_multiple_no_person[1]
+    );
     assert_eq!(
       expected_post_listing_no_person,
-      read_post_listings_no_person[1]
+      read_post_listing_single_no_person
     );
-    assert_eq!(expected_post_listing_no_person, read_post_listing_no_person);
 
-    // Should be 2 posts, with the bot post, and the blocked
-    assert_eq!(3, read_post_listings_no_person.len());
+    cleanup(data, &conn);
+  }
+
+  #[test]
+  #[serial]
+  fn post_listing_block_community() {
+    let conn = establish_unpooled_connection();
+    let data = init_data(&conn);
+
+    let community_block = CommunityBlockForm {
+      person_id: data.inserted_person.id,
+      community_id: data.inserted_community.id,
+    };
+    CommunityBlock::block(&conn, &community_block).unwrap();
 
+    let read_post_listings_with_person_after_block = PostQuery::builder()
+      .conn(&conn)
+      .sort(Some(SortType::New))
+      .community_id(Some(data.inserted_community.id))
+      .show_bot_accounts(Some(true))
+      .my_person_id(Some(data.inserted_person.id))
+      .build()
+      .list()
+      .unwrap();
     // Should be 0 posts after the community block
     assert_eq!(0, read_post_listings_with_person_after_block.len());
 
+    CommunityBlock::unblock(&conn, &community_block).unwrap();
+    cleanup(data, &conn);
+  }
+
+  #[test]
+  #[serial]
+  fn post_listing_like() {
+    let conn = establish_unpooled_connection();
+    let data = init_data(&conn);
+
+    let post_like_form = PostLikeForm {
+      post_id: data.inserted_post.id,
+      person_id: data.inserted_person.id,
+      score: 1,
+    };
+
+    let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
+
+    let expected_post_like = PostLike {
+      id: inserted_post_like.id,
+      post_id: data.inserted_post.id,
+      person_id: data.inserted_person.id,
+      published: inserted_post_like.published,
+      score: 1,
+    };
     assert_eq!(expected_post_like, inserted_post_like);
+
+    let like_removed =
+      PostLike::remove(&conn, data.inserted_person.id, data.inserted_post.id).unwrap();
     assert_eq!(1, like_removed);
-    assert_eq!(1, num_deleted);
+    cleanup(data, &conn);
+  }
+
+  #[test]
+  #[serial]
+  fn post_listing_person_language() {
+    let conn = establish_unpooled_connection();
+    let data = init_data(&conn);
+
+    let spanish_id = Language::read_id_from_code(&conn, "es").unwrap();
+    let post_spanish = PostForm {
+      name: "asffgdsc".to_string(),
+      creator_id: data.inserted_person.id,
+      community_id: data.inserted_community.id,
+      language_id: Some(spanish_id),
+      ..PostForm::default()
+    };
+
+    Post::create(&conn, &post_spanish).unwrap();
+
+    let my_person_form = PersonForm {
+      name: "Reverie Toiba".to_string(),
+      public_key: Some("pubkey".to_string()),
+      ..PersonForm::default()
+    };
+    let my_person = Person::create(&conn, &my_person_form).unwrap();
+    let local_user_form = LocalUserForm {
+      person_id: Some(my_person.id),
+      password_encrypted: Some("".to_string()),
+      ..Default::default()
+    };
+    let local_user = LocalUser::create(&conn, &local_user_form).unwrap();
+
+    // Update the users languages to all
+    LocalUserLanguage::update_user_languages(&conn, None, local_user.id).unwrap();
+
+    let post_listings_all = PostQuery::builder()
+      .conn(&conn)
+      .sort(Some(SortType::New))
+      .show_bot_accounts(Some(true))
+      .my_person_id(Some(my_person.id))
+      .my_local_user_id(Some(local_user.id))
+      .build()
+      .list()
+      .unwrap();
+
+    // no language filters specified, all posts should be returned
+    assert_eq!(4, post_listings_all.len());
+
+    let french_id = Language::read_id_from_code(&conn, "fr").unwrap();
+    LocalUserLanguage::update_user_languages(&conn, Some(vec![french_id]), local_user.id).unwrap();
+
+    let post_listing_french = PostQuery::builder()
+      .conn(&conn)
+      .sort(Some(SortType::New))
+      .show_bot_accounts(Some(true))
+      .my_person_id(Some(my_person.id))
+      .my_local_user_id(Some(local_user.id))
+      .build()
+      .list()
+      .unwrap();
+
+    // only one french language post should be returned
+    assert_eq!(1, post_listing_french.len());
+    assert_eq!(french_id, post_listing_french[0].post.language_id);
+
+    let undetermined_id = Language::read_id_from_code(&conn, "und").unwrap();
+    LocalUserLanguage::update_user_languages(
+      &conn,
+      Some(vec![french_id, undetermined_id]),
+      local_user.id,
+    )
+    .unwrap();
+    let post_listings_french_und = PostQuery::builder()
+      .conn(&conn)
+      .sort(Some(SortType::New))
+      .show_bot_accounts(Some(true))
+      .my_person_id(Some(my_person.id))
+      .my_local_user_id(Some(local_user.id))
+      .build()
+      .list()
+      .unwrap();
+
+    // french post and undetermined language post should be returned
+    assert_eq!(2, post_listings_french_und.len());
+    assert_eq!(
+      undetermined_id,
+      post_listings_french_und[0].post.language_id
+    );
+    assert_eq!(french_id, post_listings_french_und[1].post.language_id);
+
+    cleanup(data, &conn);
   }
 }
index 77ad416bc52d787003dee40991586dba32d96ccc..c4f040f4ab81e766db1c359a86808f9f45612cc5 100644 (file)
@@ -239,7 +239,7 @@ mod tests {
         theme: inserted_sara_local_user.theme,
         default_sort_type: inserted_sara_local_user.default_sort_type,
         default_listing_type: inserted_sara_local_user.default_listing_type,
-        lang: inserted_sara_local_user.lang,
+        interface_language: inserted_sara_local_user.interface_language,
         show_avatars: inserted_sara_local_user.show_avatars,
         send_notifications_to_email: inserted_sara_local_user.send_notifications_to_email,
         validator_time: inserted_sara_local_user.validator_time,
index 3dec23d3c622522b45f419571bacade19528b3c3..a3c11171228ef258a3b36bd8cac377964fd529b3 100644 (file)
@@ -4,6 +4,7 @@ use lemmy_db_schema::{
     comment::Comment,
     comment_report::CommentReport,
     community::CommunitySafe,
+    language::Language,
     local_user::{LocalUser, LocalUserSettings},
     person::{Person, PersonSafe, PersonSafeAlias1, PersonSafeAlias2},
     post::Post,
@@ -83,6 +84,7 @@ pub struct PostView {
   pub read: bool,                 // Left join to PostRead
   pub creator_blocked: bool,      // Left join to PersonBlock
   pub my_vote: Option<i16>,       // Left join to PostLike
+  pub language: Language,
 }
 
 #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@@ -105,3 +107,9 @@ pub struct SiteView {
   pub site: Site,
   pub counts: SiteAggregates,
 }
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct LocalUserDiscussionLanguageView {
+  pub local_user: LocalUserSettings,
+  pub language: Language,
+}
index f518f4c15322ae6ef1bce7a6609a407fde4707c9..83e638193f8eaa62be13049480f6b1552b88fabc 100644 (file)
@@ -8,7 +8,7 @@ use lemmy_api_common::{
   community::CommunityResponse,
   person::PrivateMessageResponse,
   post::PostResponse,
-  utils::{blocking, check_person_block, get_user_lang, send_email_to_user},
+  utils::{blocking, check_person_block, get_interface_language, send_email_to_user},
 };
 use lemmy_db_schema::{
   newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
@@ -213,7 +213,7 @@ pub async fn send_local_notifs(
 
       // Send an email to those local users that have notifications on
       if do_send_email {
-        let lang = get_user_lang(&mention_user_view);
+        let lang = get_interface_language(&mention_user_view);
         send_email_to_user(
           &mention_user_view,
           &lang.notification_mentioned_by_subject(&person.name),
@@ -263,7 +263,7 @@ pub async fn send_local_notifs(
         .ok();
 
         if do_send_email {
-          let lang = get_user_lang(&parent_user_view);
+          let lang = get_interface_language(&parent_user_view);
           send_email_to_user(
             &parent_user_view,
             &lang.notification_comment_reply_subject(&person.name),
@@ -304,7 +304,7 @@ pub async fn send_local_notifs(
         .ok();
 
         if do_send_email {
-          let lang = get_user_lang(&parent_user_view);
+          let lang = get_interface_language(&parent_user_view);
           send_email_to_user(
             &parent_user_view,
             &lang.notification_post_reply_subject(&person.name),
diff --git a/migrations/2022-06-21-123144_language-tags/down.sql b/migrations/2022-06-21-123144_language-tags/down.sql
new file mode 100644 (file)
index 0000000..22ce261
--- /dev/null
@@ -0,0 +1,6 @@
+alter table post drop column language_id;
+drop table local_user_language;
+drop table language;
+
+alter table local_user rename column interface_language to lang;
+
diff --git a/migrations/2022-06-21-123144_language-tags/up.sql b/migrations/2022-06-21-123144_language-tags/up.sql
new file mode 100644 (file)
index 0000000..0792d45
--- /dev/null
@@ -0,0 +1,200 @@
+create table language (
+  id serial primary key,
+  code varchar(3),
+  name text
+);
+create table local_user_language (
+  id serial primary key,
+  local_user_id int references local_user on update cascade on delete cascade not null,
+  language_id int references language on update cascade on delete cascade not null,
+  unique (local_user_id, language_id)
+);
+
+alter table local_user rename column lang to interface_language;
+
+alter table post add column language_id integer references language not null default 0;
+
+insert into language(id, code, name) values (0, 'und', 'Undetermined');
+insert into language(code, name) values ('aa', 'Afaraf');
+insert into language(code, name) values ('ab', 'аҧсуа бызшәа');
+insert into language(code, name) values ('ae', 'avesta');
+insert into language(code, name) values ('af', 'Afrikaans');
+insert into language(code, name) values ('ak', 'Akan');
+insert into language(code, name) values ('am', 'አማርኛ');
+insert into language(code, name) values ('an', 'aragonés');
+insert into language(code, name) values ('ar', 'اَلْعَرَبِيَّةُ');
+insert into language(code, name) values ('as', 'অসমীয়া');
+insert into language(code, name) values ('av', 'авар мацӀ');
+insert into language(code, name) values ('ay', 'aymar aru');
+insert into language(code, name) values ('az', 'azərbaycan dili');
+insert into language(code, name) values ('ba', 'башҡорт теле');
+insert into language(code, name) values ('be', 'беларуская мова');
+insert into language(code, name) values ('bg', 'български език');
+insert into language(code, name) values ('bi', 'Bislama');
+insert into language(code, name) values ('bm', 'bamanankan');
+insert into language(code, name) values ('bn', 'বাংলা');
+insert into language(code, name) values ('bo', 'བོད་ཡིག');
+insert into language(code, name) values ('br', 'brezhoneg');
+insert into language(code, name) values ('bs', 'bosanski jezik');
+insert into language(code, name) values ('ca', 'Català');
+insert into language(code, name) values ('ce', 'нохчийн мотт');
+insert into language(code, name) values ('ch', 'Chamoru');
+insert into language(code, name) values ('co', 'corsu');
+insert into language(code, name) values ('cr', 'ᓀᐦᐃᔭᐍᐏᐣ');
+insert into language(code, name) values ('cs', 'čeština');
+insert into language(code, name) values ('cu', 'ѩзыкъ словѣньскъ');
+insert into language(code, name) values ('cv', 'чӑваш чӗлхи');
+insert into language(code, name) values ('cy', 'Cymraeg');
+insert into language(code, name) values ('da', 'dansk');
+insert into language(code, name) values ('de', 'Deutsch');
+insert into language(code, name) values ('dv', 'ދިވެހި');
+insert into language(code, name) values ('dz', 'རྫོང་ཁ');
+insert into language(code, name) values ('ee', 'Eʋegbe');
+insert into language(code, name) values ('el', 'Ελληνικά');
+insert into language(code, name) values ('en', 'English');
+insert into language(code, name) values ('eo', 'Esperanto');
+insert into language(code, name) values ('es', 'Español');
+insert into language(code, name) values ('et', 'eesti');
+insert into language(code, name) values ('eu', 'euskara');
+insert into language(code, name) values ('fa', 'فارسی');
+insert into language(code, name) values ('ff', 'Fulfulde');
+insert into language(code, name) values ('fi', 'suomi');
+insert into language(code, name) values ('fj', 'vosa Vakaviti');
+insert into language(code, name) values ('fo', 'føroyskt');
+insert into language(code, name) values ('fr', 'Français');
+insert into language(code, name) values ('fy', 'Frysk');
+insert into language(code, name) values ('ga', 'Gaeilge');
+insert into language(code, name) values ('gd', 'Gàidhlig');
+insert into language(code, name) values ('gl', 'galego');
+insert into language(code, name) values ('gn', E'Avañe\'ẽ');
+insert into language(code, name) values ('gu', 'ગુજરાતી');
+insert into language(code, name) values ('gv', 'Gaelg');
+insert into language(code, name) values ('ha', 'هَوُسَ');
+insert into language(code, name) values ('he', 'עברית');
+insert into language(code, name) values ('hi', 'हिन्दी');
+insert into language(code, name) values ('ho', 'Hiri Motu');
+insert into language(code, name) values ('hr', 'Hrvatski');
+insert into language(code, name) values ('ht', 'Kreyòl ayisyen');
+insert into language(code, name) values ('hu', 'magyar');
+insert into language(code, name) values ('hy', 'Հայերեն');
+insert into language(code, name) values ('hz', 'Otjiherero');
+insert into language(code, name) values ('ia', 'Interlingua');
+insert into language(code, name) values ('id', 'Bahasa Indonesia');
+insert into language(code, name) values ('ie', 'Interlingue');
+insert into language(code, name) values ('ig', 'Asụsụ Igbo');
+insert into language(code, name) values ('ii', 'ꆈꌠ꒿ Nuosuhxop');
+insert into language(code, name) values ('ik', 'Iñupiaq');
+insert into language(code, name) values ('io', 'Ido');
+insert into language(code, name) values ('is', 'Íslenska');
+insert into language(code, name) values ('it', 'Italiano');
+insert into language(code, name) values ('iu', 'ᐃᓄᒃᑎᑐᑦ');
+insert into language(code, name) values ('ja', '日本語');
+insert into language(code, name) values ('jv', 'basa Jawa');
+insert into language(code, name) values ('ka', 'ქართული');
+insert into language(code, name) values ('kg', 'Kikongo');
+insert into language(code, name) values ('ki', 'Gĩkũyũ');
+insert into language(code, name) values ('kj', 'Kuanyama');
+insert into language(code, name) values ('kk', 'қазақ тілі');
+insert into language(code, name) values ('kl', 'kalaallisut');
+insert into language(code, name) values ('km', 'ខេមរភាសា');
+insert into language(code, name) values ('kn', 'ಕನ್ನಡ');
+insert into language(code, name) values ('ko', '한국어');
+insert into language(code, name) values ('kr', 'Kanuri');
+insert into language(code, name) values ('ks', 'कश्मीरी');
+insert into language(code, name) values ('ku', 'Kurdî');
+insert into language(code, name) values ('kv', 'коми кыв');
+insert into language(code, name) values ('kw', 'Kernewek');
+insert into language(code, name) values ('ky', 'Кыргызча');
+insert into language(code, name) values ('la', 'latine');
+insert into language(code, name) values ('lb', 'Lëtzebuergesch');
+insert into language(code, name) values ('lg', 'Luganda');
+insert into language(code, name) values ('li', 'Limburgs');
+insert into language(code, name) values ('ln', 'Lingála');
+insert into language(code, name) values ('lo', 'ພາສາລາວ');
+insert into language(code, name) values ('lt', 'lietuvių kalba');
+insert into language(code, name) values ('lu', 'Kiluba');
+insert into language(code, name) values ('lv', 'latviešu valoda');
+insert into language(code, name) values ('mg', 'fiteny malagasy');
+insert into language(code, name) values ('mh', 'Kajin M̧ajeļ');
+insert into language(code, name) values ('mi', 'te reo Māori');
+insert into language(code, name) values ('mk', 'македонски јазик');
+insert into language(code, name) values ('ml', 'മലയാളം');
+insert into language(code, name) values ('mn', 'Монгол хэл');
+insert into language(code, name) values ('mr', 'मराठी');
+insert into language(code, name) values ('ms', 'Bahasa Melayu');
+insert into language(code, name) values ('mt', 'Malti');
+insert into language(code, name) values ('my', 'ဗမာစာ');
+insert into language(code, name) values ('na', 'Dorerin Naoero');
+insert into language(code, name) values ('nb', 'Norsk bokmål');
+insert into language(code, name) values ('nd', 'isiNdebele');
+insert into language(code, name) values ('ne', 'नेपाली');
+insert into language(code, name) values ('ng', 'Owambo');
+insert into language(code, name) values ('nl', 'Nederlands');
+insert into language(code, name) values ('nn', 'Norsk nynorsk');
+insert into language(code, name) values ('no', 'Norsk');
+insert into language(code, name) values ('nr', 'isiNdebele');
+insert into language(code, name) values ('nv', 'Diné bizaad');
+insert into language(code, name) values ('ny', 'chiCheŵa');
+insert into language(code, name) values ('oc', 'occitan');
+insert into language(code, name) values ('oj', 'ᐊᓂᔑᓈᐯᒧᐎᓐ');
+insert into language(code, name) values ('om', 'Afaan Oromoo');
+insert into language(code, name) values ('or', 'ଓଡ଼ିଆ');
+insert into language(code, name) values ('os', 'ирон æвзаг');
+insert into language(code, name) values ('pa', 'ਪੰਜਾਬੀ');
+insert into language(code, name) values ('pi', 'पाऴि');
+insert into language(code, name) values ('pl', 'Polski');
+insert into language(code, name) values ('ps', 'پښتو');
+insert into language(code, name) values ('pt', 'Português');
+insert into language(code, name) values ('qu', 'Runa Simi');
+insert into language(code, name) values ('rm', 'rumantsch grischun');
+insert into language(code, name) values ('rn', 'Ikirundi');
+insert into language(code, name) values ('ro', 'Română');
+insert into language(code, name) values ('ru', 'Русский');
+insert into language(code, name) values ('rw', 'Ikinyarwanda');
+insert into language(code, name) values ('sa', 'संस्कृतम्');
+insert into language(code, name) values ('sc', 'sardu');
+insert into language(code, name) values ('sd', 'सिन्धी');
+insert into language(code, name) values ('se', 'Davvisámegiella');
+insert into language(code, name) values ('sg', 'yângâ tî sängö');
+insert into language(code, name) values ('si', 'සිංහල');
+insert into language(code, name) values ('sk', 'slovenčina');
+insert into language(code, name) values ('sl', 'slovenščina');
+insert into language(code, name) values ('sm', E'gagana fa\'a Samoa');
+insert into language(code, name) values ('sn', 'chiShona');
+insert into language(code, name) values ('so', 'Soomaaliga');
+insert into language(code, name) values ('sq', 'Shqip');
+insert into language(code, name) values ('sr', 'српски језик');
+insert into language(code, name) values ('ss', 'SiSwati');
+insert into language(code, name) values ('st', 'Sesotho');
+insert into language(code, name) values ('su', 'Basa Sunda');
+insert into language(code, name) values ('sv', 'Svenska');
+insert into language(code, name) values ('sw', 'Kiswahili');
+insert into language(code, name) values ('ta', 'தமிழ்');
+insert into language(code, name) values ('te', 'తెలుగు');
+insert into language(code, name) values ('tg', 'тоҷикӣ');
+insert into language(code, name) values ('th', 'ไทย');
+insert into language(code, name) values ('ti', 'ትግርኛ');
+insert into language(code, name) values ('tk', 'Türkmençe');
+insert into language(code, name) values ('tl', 'Wikang Tagalog');
+insert into language(code, name) values ('tn', 'Setswana');
+insert into language(code, name) values ('to', 'faka Tonga');
+insert into language(code, name) values ('tr', 'Türkçe');
+insert into language(code, name) values ('ts', 'Xitsonga');
+insert into language(code, name) values ('tt', 'татар теле');
+insert into language(code, name) values ('tw', 'Twi');
+insert into language(code, name) values ('ty', 'Reo Tahiti');
+insert into language(code, name) values ('ug', 'ئۇيغۇرچە‎');
+insert into language(code, name) values ('uk', 'Українська');
+insert into language(code, name) values ('ur', 'اردو');
+insert into language(code, name) values ('uz', 'Ўзбек');
+insert into language(code, name) values ('ve', 'Tshivenḓa');
+insert into language(code, name) values ('vi', 'Tiếng Việt');
+insert into language(code, name) values ('vo', 'Volapük');
+insert into language(code, name) values ('wa', 'walon');
+insert into language(code, name) values ('wo', 'Wollof');
+insert into language(code, name) values ('xh', 'isiXhosa');
+insert into language(code, name) values ('yi', 'ייִדיש');
+insert into language(code, name) values ('yo', 'Yorùbá');
+insert into language(code, name) values ('za', 'Saɯ cueŋƅ');
+insert into language(code, name) values ('zh', '中文');
+insert into language(code, name) values ('zu', 'isiZulu');