From e4a49b6eabcf34fb4adfa7ccd4024e5ddda93d54 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Thu, 18 Aug 2022 19:11:19 +0000 Subject: [PATCH] Tag posts and comments with language (fixes #440) (#2269) * 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 Co-authored-by: Dessalines --- Cargo.lock | 276 ++++++------ crates/api/src/local_user/save_settings.rs | 17 +- crates/api/src/site/leave_admin.rs | 4 + crates/api/src/site/search.rs | 16 +- crates/api_common/src/person.rs | 12 +- crates/api_common/src/post.rs | 4 +- crates/api_common/src/site.rs | 3 + crates/api_common/src/utils.rs | 20 +- crates/api_crud/src/post/create.rs | 11 + crates/api_crud/src/post/list.rs | 16 +- crates/api_crud/src/post/update.rs | 15 +- crates/api_crud/src/private_message/create.rs | 4 +- crates/api_crud/src/site/read.rs | 15 +- crates/api_crud/src/user/create.rs | 10 +- crates/api_crud/src/user/read.rs | 11 +- .../create_or_update/create_page.json | 4 + crates/apub/assets/lemmy/objects/page.json | 4 + crates/apub/src/lib.rs | 2 +- crates/apub/src/objects/post.rs | 15 +- crates/apub/src/protocol/objects/page.rs | 23 +- crates/db_schema/src/impls/language.rs | 54 +++ crates/db_schema/src/impls/local_user.rs | 4 +- .../src/impls/local_user_language.rs | 42 ++ crates/db_schema/src/impls/mod.rs | 2 + crates/db_schema/src/impls/post.rs | 1 + crates/db_schema/src/newtypes.rs | 8 + crates/db_schema/src/schema.rs | 26 +- crates/db_schema/src/source/language.rs | 15 + crates/db_schema/src/source/local_user.rs | 6 +- .../src/source/local_user_language.rs | 23 + crates/db_schema/src/source/mod.rs | 2 + crates/db_schema/src/source/post.rs | 4 +- crates/db_views/src/comment_view.rs | 1 + crates/db_views/src/lib.rs | 2 + .../local_user_discussion_language_view.rs | 32 ++ crates/db_views/src/post_view.rs | 393 +++++++++++++----- .../src/registration_application_view.rs | 2 +- crates/db_views/src/structs.rs | 8 + crates/websocket/src/send.rs | 8 +- .../2022-06-21-123144_language-tags/down.sql | 6 + .../2022-06-21-123144_language-tags/up.sql | 200 +++++++++ 41 files changed, 1007 insertions(+), 314 deletions(-) create mode 100644 crates/db_schema/src/impls/language.rs create mode 100644 crates/db_schema/src/impls/local_user_language.rs create mode 100644 crates/db_schema/src/source/language.rs create mode 100644 crates/db_schema/src/source/local_user_language.rs create mode 100644 crates/db_views/src/local_user_discussion_language_view.rs create mode 100644 migrations/2022-06-21-123144_language-tags/down.sql create mode 100644 migrations/2022-06-21-123144_language-tags/up.sql diff --git a/Cargo.lock b/Cargo.lock index 18ac1edcc..c1bfba5ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index 9929fc0c7..e8a94ff60 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -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, diff --git a/crates/api/src/site/leave_admin.rs b/crates/api/src/site/leave_admin.rs index 19cca5ccc..2a5fa590f 100644 --- a/crates/api/src/site/leave_admin.rs +++ b/crates/api/src/site/leave_admin.rs @@ -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, }) } } diff --git a/crates/api/src/site/search.rs b/crates/api/src/site/search.rs index 7c118a0b2..bd04b053c 100644 --- a/crates/api/src/site/search.rs +++ b/crates/api/src/site/search.rs @@ -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) diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 865597521..79e3041d8 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -14,7 +14,14 @@ pub struct Login { pub password: Sensitive, } 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, pub default_sort_type: Option, pub default_listing_type: Option, - pub lang: Option, + pub interface_language: Option, pub avatar: Option, pub banner: Option, pub display_name: Option, @@ -69,6 +76,7 @@ pub struct SaveUserSettings { pub show_bot_accounts: Option, pub show_read_posts: Option, pub show_new_post_notifs: Option, + pub discussion_languages: Option>, pub auth: Sensitive, } diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 4884c15da..c9d92380d 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -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, pub honeypot: Option, pub nsfw: Option, + pub language_id: Option, pub auth: Sensitive, } @@ -71,6 +72,7 @@ pub struct EditPost { pub url: Option, pub body: Option, pub nsfw: Option, + pub language_id: Option, pub auth: Sensitive, } diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index f5098a5ee..eb520e29e 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -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, pub federated_instances: Option, // Federation may be disabled + pub all_languages: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -177,6 +179,7 @@ pub struct MyUserInfo { pub moderates: Vec, pub community_blocks: Vec, pub person_blocks: Vec, + pub discussion_languages: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index bf99b7b94..d2a009787 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -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 }) - .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(®istration_denied_message)); } else { diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 1a6c100db..ef74ece86 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -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() }; diff --git a/crates/api_crud/src/post/list.rs b/crates/api_crud/src/post/list.rs index 468910b24..9d9b156c6 100644 --- a/crates/api_crud/src/post/list.rs +++ b/crates/api_crud/src/post/list.rs @@ -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) diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index fdb41186a..d96a12d5e 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -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() }; diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index 979c575de..56d68c6e5 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -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, diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs index d34be2cad..95ecf6c1b 100644 --- a/crates/api_crud/src/site/read.rs +++ b/crates/api_crud/src/site/read.rs @@ -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, }) } } diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index 7d35f3207..9fc4fe5b1 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -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() diff --git a/crates/api_crud/src/user/read.rs b/crates/api_crud/src/user/read.rs index 7ec52501e..15630924a 100644 --- a/crates/api_crud/src/user/read.rs +++ b/crates/api_crud/src/user/read.rs @@ -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) diff --git a/crates/apub/assets/lemmy/activities/create_or_update/create_page.json b/crates/apub/assets/lemmy/activities/create_or_update/create_page.json index 37718b234..4fff07c8b 100644 --- a/crates/apub/assets/lemmy/activities/create_or_update/create_page.json +++ b/crates/apub/assets/lemmy/activities/create_or_update/create_page.json @@ -28,6 +28,10 @@ "commentsEnabled": true, "sensitive": false, "stickied": false, + "language": { + "identifier": "ko", + "name": "한국어" + }, "published": "2021-10-29T15:10:51.557399+00:00" }, "cc": [ diff --git a/crates/apub/assets/lemmy/objects/page.json b/crates/apub/assets/lemmy/objects/page.json index b90ee549a..816c32495 100644 --- a/crates/apub/assets/lemmy/objects/page.json +++ b/crates/apub/assets/lemmy/objects/page.json @@ -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" } diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 64cfa0f83..8fd8588b8 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -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( diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index bc1b44f54..ea536e51f 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -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 diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index 9a47324ef..10e30a770 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -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, pub(crate) published: Option>, pub(crate) updated: Option>, + pub(crate) language: Option, } +#[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 { + // 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 index 000000000..f2895e807 --- /dev/null +++ b/crates/db_schema/src/impls/language.rs @@ -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, Error> { + use crate::schema::language::dsl::*; + language.load::(conn) + } + + pub fn read_from_id(conn: &PgConnection, id_: LanguageId) -> Result { + use crate::schema::language::dsl::*; + language.filter(id.eq(id_)).first::(conn) + } + + pub fn read_id_from_code(conn: &PgConnection, code_: &str) -> Result { + use crate::schema::language::dsl::*; + Ok(language.filter(code.eq(code_)).first::(conn)?.id) + } + + pub fn read_id_from_code_opt( + conn: &PgConnection, + code_: Option<&str>, + ) -> Result, 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 { + use crate::schema::language::dsl::*; + Ok(language.filter(code.eq("und")).first::(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); + } +} diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index 419ab7928..8d5370d0a 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -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 index 000000000..d38a08691 --- /dev/null +++ b/crates/db_schema/src/impls/local_user_language.rs @@ -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>, + 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::(conn)?; + } + Ok(()) + }) + } +} diff --git a/crates/db_schema/src/impls/mod.rs b/crates/db_schema/src/impls/mod.rs index 1219f50f3..bd5df9042 100644 --- a/crates/db_schema/src/impls/mod.rs +++ b/crates/db_schema/src/impls/mod.rs @@ -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; diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 9018cdadf..07c652e31 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -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 diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index 22c4aeb89..e91bfea65 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -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); diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 4c2caaf06..fcd99c1b1 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -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, 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 index 000000000..7b2a730b8 --- /dev/null +++ b/crates/db_schema/src/source/language.rs @@ -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, +} diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index 57767bb17..7c24d61bc 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -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, pub default_sort_type: Option, pub default_listing_type: Option, - pub lang: Option, + pub interface_language: Option, pub show_avatars: Option, pub send_notifications_to_email: Option, pub show_bot_accounts: Option, @@ -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 index 000000000..64492ee37 --- /dev/null +++ b/crates/db_schema/src/source/local_user_language.rs @@ -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, +} diff --git a/crates/db_schema/src/source/mod.rs b/crates/db_schema/src/source/mod.rs index 302c9f638..f142bbaec 100644 --- a/crates/db_schema/src/source/mod.rs +++ b/crates/db_schema/src/source/mod.rs @@ -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; diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index a69e6a2ca..7e192a72a 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -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, pub ap_id: DbUrl, pub local: bool, + pub language_id: LanguageId, } #[derive(Default)] @@ -51,6 +52,7 @@ pub struct PostForm { pub thumbnail_url: Option>, pub ap_id: Option, pub local: Option, + pub language_id: Option, } #[derive(PartialEq, Debug)] diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 9fa543d03..2516b45e4 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -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, diff --git a/crates/db_views/src/lib.rs b/crates/db_views/src/lib.rs index b5567f87b..507d37f75 100644 --- a/crates/db_views/src/lib.rs +++ b/crates/db_views/src/lib.rs @@ -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 index 000000000..5c88a2f58 --- /dev/null +++ b/crates/db_views/src/local_user_discussion_language_view.rs @@ -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, 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::(conn)?; + + Ok(res.into_iter().map(|a| a.1).collect::>()) + } +} diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index c9b472172..761925a9c 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -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, Option, Option, + Language, ); impl PostView { @@ -51,7 +55,6 @@ impl PostView { ) -> Result { // 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::(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, community_actor_id: Option, my_person_id: Option, + my_local_user_id: Option, search_term: Option, url_search: Option, show_nsfw: Option, @@ -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::>() } @@ -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); } } diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index 77ad416bc..c4f040f4a 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -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, diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index 3dec23d3c..a3c111712 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -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, // 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, +} diff --git a/crates/websocket/src/send.rs b/crates/websocket/src/send.rs index f518f4c15..83e638193 100644 --- a/crates/websocket/src/send.rs +++ b/crates/websocket/src/send.rs @@ -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 index 000000000..22ce261fa --- /dev/null +++ b/migrations/2022-06-21-123144_language-tags/down.sql @@ -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 index 000000000..0792d45b4 --- /dev/null +++ b/migrations/2022-06-21-123144_language-tags/up.sql @@ -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');