fix: A/B test 21: music album recommendations

This commit is contained in:
ThetaDev 2025-02-26 15:20:25 +01:00
parent 544782f8de
commit 6737512f5f
No known key found for this signature in database
GPG key ID: E319D3C5148D65B6
13 changed files with 8680 additions and 113 deletions

View file

@ -40,13 +40,11 @@ pub enum ABTest {
MusicPlaylistFacepile = 18,
MusicAlbumGroupsReordered = 19,
MusicContinuationItemRenderer = 20,
AlbumRecommends = 21,
}
/// List of active A/B tests that are run when none is manually specified
const TESTS_TO_RUN: &[ABTest] = &[
ABTest::MusicAlbumGroupsReordered,
ABTest::MusicContinuationItemRenderer,
];
const TESTS_TO_RUN: &[ABTest] = &[ABTest::MusicAlbumGroupsReordered, ABTest::AlbumRecommends];
#[derive(Debug, Serialize, Deserialize)]
pub struct ABTestRes {
@ -121,6 +119,7 @@ pub async fn run_test(
ABTest::MusicContinuationItemRenderer => {
music_continuation_item_renderer(&query).await
}
ABTest::AlbumRecommends => album_recommends(&query).await,
}
.unwrap();
pb.inc(1);
@ -443,3 +442,18 @@ pub async fn music_continuation_item_renderer(rp: &RustyPipeQuery) -> Result<boo
.await?;
Ok(res.contains("\"continuationItemRenderer\""))
}
pub async fn album_recommends(rp: &RustyPipeQuery) -> Result<bool> {
let id = "MPREb_u1I69lSAe5v";
let res = rp
.raw(
ClientType::DesktopMusic,
"browse",
&QBrowse {
browse_id: id,
params: None,
},
)
.await?;
Ok(res.contains("\"musicCarouselShelfRenderer\""))
}

View file

@ -0,0 +1,130 @@
use std::{collections::BTreeMap, fs::File, io::BufReader};
use path_macro::path;
use rustypipe::{
client::{ClientType, RustyPipe},
param::{Language, LANGUAGES},
};
use serde::Deserialize;
use serde_with::rust::deserialize_ignore_any;
use crate::{
model::{QBrowse, SectionList, TextRuns},
util::{self, DICT_DIR},
};
pub async fn collect_album_versions_titles() {
let json_path = path!(*DICT_DIR / "other_versions_titles.json");
let mut res = BTreeMap::new();
let rp = RustyPipe::new();
for lang in LANGUAGES {
let query = QBrowse {
browse_id: "MPREb_nlBWQROfvjo",
params: None,
};
let raw_resp = rp
.query()
.lang(lang)
.raw(ClientType::DesktopMusic, "browse", &query)
.await
.unwrap();
let data = serde_json::from_str::<AlbumData>(&raw_resp).unwrap();
let title = data
.contents
.two_column_browse_results_renderer
.secondary_contents
.section_list_renderer
.contents
.into_iter()
.find_map(|x| match x {
ItemSection::MusicCarouselShelfRenderer(music_carousel_shelf) => {
Some(music_carousel_shelf)
}
ItemSection::None => None,
})
.expect("other versions")
.header
.expect("header")
.music_carousel_shelf_basic_header_renderer
.title
.runs
.into_iter()
.next()
.unwrap()
.text;
println!("{lang}: {title}");
res.insert(lang, title);
}
let file = File::create(json_path).unwrap();
serde_json::to_writer_pretty(file, &res).unwrap();
}
pub fn write_samples_to_dict() {
let json_path = path!(*DICT_DIR / "other_versions_titles.json");
let json_file = File::open(json_path).unwrap();
let collected: BTreeMap<Language, String> =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let mut dict = util::read_dict();
let langs = dict.keys().copied().collect::<Vec<_>>();
for lang in langs {
let dict_entry = dict.entry(lang).or_default();
let e = collected.get(&lang).unwrap();
assert_eq!(e, e.trim());
dict_entry.album_versions_title = e.to_owned();
for lang in &dict_entry.equivalent {
let ee = collected.get(lang).unwrap();
if ee != e {
panic!("equivalent lang conflict, lang: {lang}");
}
}
}
util::write_dict(dict);
}
#[derive(Debug, Deserialize)]
struct AlbumData {
contents: AlbumDataContents,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct AlbumDataContents {
two_column_browse_results_renderer: X1,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct X1 {
secondary_contents: SectionList<ItemSection>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
enum ItemSection {
MusicCarouselShelfRenderer(MusicCarouselShelf),
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}
#[derive(Debug, Deserialize)]
struct MusicCarouselShelf {
header: Option<MusicCarouselShelfHeader>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MusicCarouselShelfHeader {
music_carousel_shelf_basic_header_renderer: MusicCarouselShelfHeaderRenderer,
}
#[derive(Debug, Deserialize)]
struct MusicCarouselShelfHeaderRenderer {
title: TextRuns,
}

View file

@ -90,6 +90,8 @@ pub(crate) struct Entry {
pub chan_prefix: &'static str,
/// Channel name suffix on playlist pages
pub chan_suffix: &'static str,
/// "Other versions" title on album pages
pub album_versions_title: &'static str,
}
"#;
@ -178,8 +180,8 @@ pub(crate) fn entry(lang: Language) -> Entry {
.to_string()
.replace('\n', "\n ");
write!(code_timeago_tokens, "{} => Entry {{\n timeago_tokens: {},\n month_before_day: {:?},\n months: {},\n timeago_nd_tokens: {},\n comma_decimal: {:?},\n number_tokens: {},\n number_nd_tokens: {},\n album_types: {},\n chan_prefix: {:?},\n chan_suffix: {:?},\n }},\n ",
selector, code_ta_tokens, entry.month_before_day, code_months, code_ta_nd_tokens, entry.comma_decimal, code_number_tokens, code_number_nd_tokens, code_album_types, entry.chan_prefix, entry.chan_suffix).unwrap();
write!(code_timeago_tokens, "{} => Entry {{\n timeago_tokens: {},\n month_before_day: {:?},\n months: {},\n timeago_nd_tokens: {},\n comma_decimal: {:?},\n number_tokens: {},\n number_nd_tokens: {},\n album_types: {},\n chan_prefix: {:?},\n chan_suffix: {:?},\n album_versions_title: {:?},\n }},\n ",
selector, code_ta_tokens, entry.month_before_day, code_months, code_ta_nd_tokens, entry.comma_decimal, code_number_tokens, code_number_nd_tokens, code_album_types, entry.chan_prefix, entry.chan_suffix, entry.album_versions_title).unwrap();
}
code_timeago_tokens = code_timeago_tokens.trim_end().to_owned() + "\n }\n}\n";

View file

@ -2,6 +2,7 @@
mod abtest;
mod collect_album_types;
mod collect_album_versions_titles;
mod collect_chan_prefixes;
mod collect_history_dates;
mod collect_large_numbers;
@ -34,12 +35,14 @@ enum Commands {
CollectHistoryDates,
CollectMusicHistoryDates,
CollectChanPrefixes,
CollectAlbumVersionsTitles,
ParsePlaylistDates,
ParseHistoryDates,
ParseLargeNumbers,
ParseAlbumTypes,
ParseVideoDurations,
ParseChanPrefixes,
ParseAlbumVersionsTitles,
GenLocales,
GenDict,
DownloadTestfiles,
@ -58,28 +61,25 @@ async fn main() {
match cli.command {
Commands::CollectPlaylistDates => {
collect_playlist_dates::collect_dates(cli.concurrency).await;
collect_playlist_dates::collect_dates(cli.concurrency).await
}
Commands::CollectLargeNumbers => {
collect_large_numbers::collect_large_numbers(cli.concurrency).await;
collect_large_numbers::collect_large_numbers(cli.concurrency).await
}
Commands::CollectAlbumTypes => {
collect_album_types::collect_album_types(cli.concurrency).await;
collect_album_types::collect_album_types(cli.concurrency).await
}
Commands::CollectVideoDurations => {
collect_video_durations::collect_video_durations(cli.concurrency).await;
collect_video_durations::collect_video_durations(cli.concurrency).await
}
Commands::CollectVideoDates => {
collect_video_dates::collect_video_dates(cli.concurrency).await;
collect_video_dates::collect_video_dates(cli.concurrency).await
}
Commands::CollectHistoryDates => {
collect_history_dates::collect_dates().await;
}
Commands::CollectMusicHistoryDates => {
collect_history_dates::collect_dates_music().await;
}
Commands::CollectChanPrefixes => {
collect_chan_prefixes::collect_chan_prefixes().await;
Commands::CollectHistoryDates => collect_history_dates::collect_dates().await,
Commands::CollectMusicHistoryDates => collect_history_dates::collect_dates_music().await,
Commands::CollectChanPrefixes => collect_chan_prefixes::collect_chan_prefixes().await,
Commands::CollectAlbumVersionsTitles => {
collect_album_versions_titles::collect_album_versions_titles().await
}
Commands::ParsePlaylistDates => collect_playlist_dates::write_samples_to_dict(),
Commands::ParseHistoryDates => collect_history_dates::write_samples_to_dict(),
@ -87,9 +87,10 @@ async fn main() {
Commands::ParseAlbumTypes => collect_album_types::write_samples_to_dict(),
Commands::ParseVideoDurations => collect_video_durations::parse_video_durations(),
Commands::ParseChanPrefixes => collect_chan_prefixes::write_samples_to_dict(),
Commands::GenLocales => {
gen_locales::generate_locales().await;
Commands::ParseAlbumVersionsTitles => {
collect_album_versions_titles::write_samples_to_dict()
}
Commands::GenLocales => gen_locales::generate_locales().await,
Commands::GenDict => gen_dictionary::generate_dictionary(),
Commands::DownloadTestfiles => download_testfiles::download_testfiles().await,
Commands::AbTest { id, n } => {

View file

@ -61,6 +61,8 @@ pub struct DictEntry {
pub chan_prefix: String,
/// Channel name suffix on playlist pages
pub chan_suffix: String,
/// "Other versions" title on album pages
pub album_versions_title: String,
}
/// Parsed TimeAgo string, contains amount and time unit.

View file

@ -3,13 +3,13 @@
When YouTube introduces a new feature, it does so gradually. When a user creates a new
session, YouTube decided randomly which new features should be enabled.
YouTube sessions are identified by the visitor data ID. This cookie is sent with
every API request using the `context.client.visitor_data` JSON parameter. It is also
returned in the `responseContext.visitorData` response parameter and stored as the
`__SECURE-YEC` cookie.
YouTube sessions are identified by the visitor data ID. This cookie is sent with every
API request using the `context.client.visitor_data` JSON parameter. It is also returned
in the `responseContext.visitorData` response parameter and stored as the `__SECURE-YEC`
cookie.
By sending the same visitor data ID, A/B tests can be reproduced, which is important
for testing alternative YouTube clients.
By sending the same visitor data ID, A/B tests can be reproduced, which is important for
testing alternative YouTube clients.
This page lists all A/B tests that were encountered while maintaining the RustyPipe
client.
@ -1042,7 +1042,7 @@ omitted for albums in their group, while singles and EPs have a label with their
- **Encountered on:** 25.01.2025
- **Impact:** 🟢 Low
- **Endpoint:** browse (YTM)
- **Status:** Common (4%)
- **Status:** Stabilized
YouTube Music now uses a `continuationItemRenderer` for music playlists instead of
putting the continuations in a separate attribute of the MusicShelf.
@ -1052,3 +1052,18 @@ items.
YouTube Music now also sends a random 16-character string as a `clientScreenNonce` in
the request context. This is not mandatory though.
## [21] Music album recommendations
- **Encountered on:** 26.02.2025
- **Impact:** 🟢 Low
- **Endpoint:** browse (YTM)
- **Status:** Common (15%)
![A/B test 21 screenshot](./_img/ab_21.png)
YouTube Music has added "Recommended" and "More from \<Artist\>" carousels to album
pages. The difficulty is distinguishing them reliably for parsing the album variants.
The current solution is adding the "Other versions" title in all languages to the
dictionary and comparing it.

BIN
notes/_img/ab_21.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View file

@ -9,7 +9,7 @@ use crate::{
AlbumId, ChannelId, MusicAlbum, MusicPlaylist, TrackItem, TrackType,
},
serializer::{text::TextComponents, MapResult},
util::{self, TryRemove, DOT_SEPARATOR},
util::{self, dictionary, TryRemove, DOT_SEPARATOR},
};
use self::response::url_endpoint::MusicPageType;
@ -382,7 +382,18 @@ impl MapResponse<MusicAlbum> for response::MusicPlaylist {
match section {
response::music_item::ItemSection::MusicShelfRenderer(sh) => shelf = Some(sh),
response::music_item::ItemSection::MusicCarouselShelfRenderer(sh) => {
album_variants = Some(sh.contents);
if sh
.header
.map(|h| {
h.music_carousel_shelf_basic_header_renderer
.title
.first_str()
== dictionary::entry(ctx.lang).album_versions_title
})
.unwrap_or_default()
{
album_variants = Some(sh.contents);
}
}
_ => (),
}
@ -572,8 +583,8 @@ mod tests {
#[case::single("single", "MPREb_bHfHGoy7vuv")]
#[case::description("description", "MPREb_PiyfuVl6aYd")]
#[case::unavailable("unavailable", "MPREb_AzuWg8qAVVl")]
#[case::unavailable("unavailable", "MPREb_AzuWg8qAVVl")]
#[case::two_columns("20240228_twoColumns", "MPREb_bHfHGoy7vuv")]
#[case::recommends("20250225_recommends", "MPREb_u1I69lSAe5v")]
fn map_music_album(#[case] name: &str, #[case] id: &str) {
let json_path = path!(*TESTFILES / "music_playlist" / format!("album_{name}.json"));
let json_file = File::open(json_path).unwrap();

View file

@ -0,0 +1,151 @@
---
source: src/client/music_playlist.rs
expression: map_res.c
---
MusicAlbum(
id: "MPREb_u1I69lSAe5v",
playlist_id: Some("OLAK5uy_lGP_zv0vJDUlecQDzugUJmjcF7pvyVNyY"),
name: "Waldbrand",
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/IYxE8yTIpFUu0OayA5SaxFEn6zQ7T21hpkvI8CODY9NEH1XIhyoUhGohkZuaK-xSu22BC4wjp6srNjIW=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/IYxE8yTIpFUu0OayA5SaxFEn6zQ7T21hpkvI8CODY9NEH1XIhyoUhGohkZuaK-xSu22BC4wjp6srNjIW=w120-h120-l90-rj",
width: 120,
height: 120,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/IYxE8yTIpFUu0OayA5SaxFEn6zQ7T21hpkvI8CODY9NEH1XIhyoUhGohkZuaK-xSu22BC4wjp6srNjIW=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/IYxE8yTIpFUu0OayA5SaxFEn6zQ7T21hpkvI8CODY9NEH1XIhyoUhGohkZuaK-xSu22BC4wjp6srNjIW=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
artists: [
ArtistId(
id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
name: "Madeline Juno",
),
],
artist_id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
description: None,
album_type: ep,
year: Some(2016),
by_va: false,
track_count: 5,
tracks: [
TrackItem(
id: "aGd3VKSOTxY",
name: "Ich wache auf",
duration: Some(222),
cover: [],
artists: [
ArtistId(
id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
name: "Madeline Juno",
),
],
artist_id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
album: Some(AlbumId(
id: "MPREb_u1I69lSAe5v",
name: "Waldbrand",
)),
view_count: Some(208000),
track_type: track,
track_nr: Some(1),
by_va: false,
),
TrackItem(
id: "lhPOMUjV4rE",
name: "Waldbrand",
duration: Some(209),
cover: [],
artists: [
ArtistId(
id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
name: "Madeline Juno",
),
],
artist_id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
album: Some(AlbumId(
id: "MPREb_u1I69lSAe5v",
name: "Waldbrand",
)),
view_count: Some(6000000),
track_type: video,
track_nr: Some(2),
by_va: false,
),
TrackItem(
id: "Bu26uFtpt58",
name: "Verlernt",
duration: Some(224),
cover: [],
artists: [
ArtistId(
id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
name: "Madeline Juno",
),
],
artist_id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
album: Some(AlbumId(
id: "MPREb_u1I69lSAe5v",
name: "Waldbrand",
)),
view_count: Some(418000),
track_type: track,
track_nr: Some(3),
by_va: false,
),
TrackItem(
id: "RgwNqqiVqdY",
name: "In Farbe",
duration: Some(222),
cover: [],
artists: [
ArtistId(
id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
name: "Madeline Juno",
),
],
artist_id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
album: Some(AlbumId(
id: "MPREb_u1I69lSAe5v",
name: "Waldbrand",
)),
view_count: Some(127000),
track_type: track,
track_nr: Some(4),
by_va: false,
),
TrackItem(
id: "2TuOh30XbCI",
name: "Stadt im Hinterland",
duration: Some(198),
cover: [],
artists: [
ArtistId(
id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
name: "Madeline Juno",
),
],
artist_id: Some("UCpJyCbFbdTrx0M90HCNBHFQ"),
album: Some(AlbumId(
id: "MPREb_u1I69lSAe5v",
name: "Waldbrand",
)),
view_count: Some(79000),
track_type: track,
track_nr: Some(5),
by_va: false,
),
],
variants: [],
)

View file

@ -53,6 +53,8 @@ pub(crate) struct Entry {
pub chan_prefix: &'static str,
/// Channel name suffix on playlist pages
pub chan_suffix: &'static str,
/// "Other versions" title on album pages
pub album_versions_title: &'static str,
}
#[rustfmt::skip]
@ -183,6 +185,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "deur",
chan_suffix: "",
album_versions_title: "Ander weergawes",
},
Language::Am => Entry {
timeago_tokens: ::phf::Map {
@ -310,6 +313,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "",
album_versions_title: "ሌሎች ስሪቶች",
},
Language::Ar => Entry {
timeago_tokens: ::phf::Map {
@ -445,6 +449,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "بواسطة",
chan_suffix: "",
album_versions_title: "إصدارات أخرى",
},
Language::As => Entry {
timeago_tokens: ::phf::Map {
@ -567,6 +572,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "ৰ দ\u{9cd}\u{9be}\u{9be}",
album_versions_title: "অন\u{9cd}য সংস\u{9cd}কৰণ",
},
Language::Az => Entry {
timeago_tokens: ::phf::Map {
@ -682,6 +688,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "by",
chan_suffix: "",
album_versions_title: "Digər versiyalar",
},
Language::Be => Entry {
timeago_tokens: ::phf::Map {
@ -829,6 +836,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "ад",
chan_suffix: "",
album_versions_title: "Іншыя версіі",
},
Language::Bg => Entry {
timeago_tokens: ::phf::Map {
@ -945,6 +953,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "от",
chan_suffix: "",
album_versions_title: "Други версии",
},
Language::Bn => Entry {
timeago_tokens: ::phf::Map {
@ -1062,6 +1071,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: ",",
chan_suffix: "\u{9cd}\u{9be}\u{9be}",
album_versions_title: "অন\u{9cd}য সংস\u{9cd}করণগ\u{9c1}লি",
},
Language::Bs => Entry {
timeago_tokens: ::phf::Map {
@ -1201,6 +1211,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "od",
chan_suffix: "",
album_versions_title: "Druge verzije",
},
Language::Ca => Entry {
timeago_tokens: ::phf::Map {
@ -1325,6 +1336,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "de:",
chan_suffix: "",
album_versions_title: "Altres versions",
},
Language::Cs => Entry {
timeago_tokens: ::phf::Map {
@ -1455,6 +1467,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "autor:",
chan_suffix: "",
album_versions_title: "Jiné verze",
},
Language::Da => Entry {
timeago_tokens: ::phf::Map {
@ -1579,6 +1592,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "af",
chan_suffix: "",
album_versions_title: "Andre versioner",
},
Language::De => Entry {
timeago_tokens: ::phf::Map {
@ -1700,6 +1714,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "von",
chan_suffix: "",
album_versions_title: "Weitere Versionen",
},
Language::El => Entry {
timeago_tokens: ::phf::Map {
@ -1830,6 +1845,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "από το χρήστη",
chan_suffix: "",
album_versions_title: "Άλλες εκτελέσεις",
},
Language::En | Language::EnGb | Language::EnIn => Entry {
timeago_tokens: ::phf::Map {
@ -1971,6 +1987,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "by",
chan_suffix: "",
album_versions_title: "Other versions",
},
Language::Es => Entry {
timeago_tokens: ::phf::Map {
@ -2098,6 +2115,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "de",
chan_suffix: "",
album_versions_title: "Otras versiones",
},
Language::EsUs | Language::Es419 => Entry {
timeago_tokens: ::phf::Map {
@ -2226,6 +2244,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "de",
chan_suffix: "",
album_versions_title: "Otras versiones",
},
Language::Et => Entry {
timeago_tokens: ::phf::Map {
@ -2351,6 +2370,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "kanalilt",
chan_suffix: "",
album_versions_title: "Teised versioonid",
},
Language::Eu => Entry {
timeago_tokens: ::phf::Map {
@ -2467,6 +2487,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "egilea:",
chan_suffix: "",
album_versions_title: "Beste bertsio batzuk",
},
Language::Fa => Entry {
timeago_tokens: ::phf::Map {
@ -2574,6 +2595,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "توسط",
chan_suffix: "",
album_versions_title: "نسخه\u{200c}های دیگر",
},
Language::Fi => Entry {
timeago_tokens: ::phf::Map {
@ -2693,6 +2715,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "tekijä:",
chan_suffix: "",
album_versions_title: "Muut versiot",
},
Language::Fil => Entry {
timeago_tokens: ::phf::Map {
@ -2810,6 +2833,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "ni/ng",
chan_suffix: "",
album_versions_title: "Iba pang bersyon",
},
Language::Fr | Language::FrCa => Entry {
timeago_tokens: ::phf::Map {
@ -2941,6 +2965,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "de",
chan_suffix: "",
album_versions_title: "Autres versions",
},
Language::Gl => Entry {
timeago_tokens: ::phf::Map {
@ -3065,6 +3090,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "de",
chan_suffix: "",
album_versions_title: "Outras versións",
},
Language::Gu => Entry {
timeago_tokens: ::phf::Map {
@ -3170,6 +3196,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{acd}વારા",
album_versions_title: "અન\u{acd}ય વર\u{acd}ઝન",
},
Language::Hi => Entry {
timeago_tokens: ::phf::Map {
@ -3286,6 +3313,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{947}\u{93c}रिए",
album_versions_title: "अन\u{94d}य वर\u{94d}शन",
},
Language::Hr => Entry {
timeago_tokens: ::phf::Map {
@ -3425,6 +3453,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "omogućio kanal",
chan_suffix: "",
album_versions_title: "Druge verzije",
},
Language::Hu => Entry {
timeago_tokens: ::phf::Map {
@ -3554,6 +3583,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "csatornától",
album_versions_title: "Más verziók",
},
Language::Hy => Entry {
timeago_tokens: ::phf::Map {
@ -3676,6 +3706,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "հեղինակ՝",
chan_suffix: "",
album_versions_title: "Այլ տարբերակներ",
},
Language::Id => Entry {
timeago_tokens: ::phf::Map {
@ -3794,6 +3825,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "oleh",
chan_suffix: "",
album_versions_title: "Versi lainnya",
},
Language::Is => Entry {
timeago_tokens: ::phf::Map {
@ -3928,6 +3960,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "eftir",
chan_suffix: "",
album_versions_title: "Aðrar útgáfur",
},
Language::It => Entry {
timeago_tokens: ::phf::Map {
@ -4060,6 +4093,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "di",
chan_suffix: "",
album_versions_title: "Altre versioni",
},
Language::Iw => Entry {
timeago_tokens: ::phf::Map {
@ -4198,6 +4232,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "מאת",
chan_suffix: "",
album_versions_title: "גרסאות אחרות",
},
Language::Ja => Entry {
timeago_tokens: ::phf::Map {
@ -4278,6 +4313,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "作成者:",
chan_suffix: "",
album_versions_title: "他のバージョン",
},
Language::Ka => Entry {
timeago_tokens: ::phf::Map {
@ -4400,6 +4436,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "-ის მიერ",
album_versions_title: "სხვა ვერსიები",
},
Language::Kk => Entry {
timeago_tokens: ::phf::Map {
@ -4523,6 +4560,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "қосқан",
chan_suffix: "",
album_versions_title: "Басқа нұсқалары",
},
Language::Km => Entry {
timeago_tokens: ::phf::Map {
@ -4623,6 +4661,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "ដោយ",
chan_suffix: "",
album_versions_title: "\u{17d2}រភេទផ\u{17d2}សេងៗ",
},
Language::Kn => Entry {
timeago_tokens: ::phf::Map {
@ -4749,6 +4788,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "ಚಾನಲ\u{ccd}\u{200c}\u{cbf}ಂದ",
album_versions_title: "ಇತರ ಆವೃತ\u{ccd}\u{cbf}ಗಳು",
},
Language::Ko => Entry {
timeago_tokens: ::phf::Map {
@ -4832,6 +4872,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "게시자:",
chan_suffix: "",
album_versions_title: "다른 버전",
},
Language::Ky => Entry {
timeago_tokens: ::phf::Map {
@ -4950,6 +4991,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "каналы аркылуу",
album_versions_title: "Башка версиялар",
},
Language::Lo => Entry {
timeago_tokens: ::phf::Map {
@ -5076,6 +5118,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "ໂດຍ",
chan_suffix: "",
album_versions_title: "ເວ\u{eb5}\u{eb1}ນອ\u{eb7}\u{ec8}ນໆ",
},
Language::Lt => Entry {
timeago_tokens: ::phf::Map {
@ -5210,6 +5253,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "pridėjo",
chan_suffix: "",
album_versions_title: "Kitos versijos",
},
Language::Lv => Entry {
timeago_tokens: ::phf::Map {
@ -5344,6 +5388,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "autors:",
chan_suffix: "",
album_versions_title: "Citas versijas",
},
Language::Mk => Entry {
timeago_tokens: ::phf::Map {
@ -5471,6 +5516,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "од",
chan_suffix: "",
album_versions_title: "Други верзии",
},
Language::Ml => Entry {
timeago_tokens: ::phf::Map {
@ -5585,6 +5631,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{d41}ഖേന",
album_versions_title: "മറ\u{d4d}\u{d4d} പതിപ\u{d4d}\u{d41}കൾ",
},
Language::Mn => Entry {
timeago_tokens: ::phf::Map {
@ -5689,6 +5736,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "сувгийн нэр:",
chan_suffix: "",
album_versions_title: "Бусад хувилбар",
},
Language::Mr => Entry {
timeago_tokens: ::phf::Map {
@ -5813,6 +5861,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{94d}वार\u{947}",
album_versions_title: "इतर आव\u{943}\u{94d}\u{94d}या",
},
Language::Ms => Entry {
timeago_tokens: ::phf::Map {
@ -5926,6 +5975,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "oleh",
chan_suffix: "",
album_versions_title: "Versi lain",
},
Language::My => Entry {
timeago_tokens: ::phf::Map {
@ -6046,6 +6096,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{103e}",
album_versions_title: "အခြား ဗားရ\u{103e}\u{103a}းများ",
},
Language::Ne => Entry {
timeago_tokens: ::phf::Map {
@ -6149,6 +6200,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{94d}वारा",
album_versions_title: "अन\u{94d}य स\u{902}\u{94d}करणहर\u{942}",
},
Language::Nl => Entry {
timeago_tokens: ::phf::Map {
@ -6271,6 +6323,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "door",
chan_suffix: "",
album_versions_title: "Andere versies",
},
Language::No => Entry {
timeago_tokens: ::phf::Map {
@ -6399,6 +6452,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "av",
chan_suffix: "",
album_versions_title: "Andre versjoner",
},
Language::Or => Entry {
timeago_tokens: ::phf::Map {
@ -6514,6 +6568,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{b4d}\u{b3e}\u{b3e}",
album_versions_title: "ଅନ\u{b4d}ୟ ସଂସ\u{b4d}କରଣଗ\u{b41}\u{b3c}\u{b3f}",
},
Language::Pa => Entry {
timeago_tokens: ::phf::Map {
@ -6629,6 +6684,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{a71}\u{a4b}\u{a02}",
album_versions_title: "\u{a4b}ਰ ਵਰਜਨ",
},
Language::Pl => Entry {
timeago_tokens: ::phf::Map {
@ -6774,6 +6830,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "autor:",
chan_suffix: "",
album_versions_title: "Inne wersje",
},
Language::Pt => Entry {
timeago_tokens: ::phf::Map {
@ -6903,6 +6960,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "por",
chan_suffix: "",
album_versions_title: "Outras versões",
},
Language::PtPt => Entry {
timeago_tokens: ::phf::Map {
@ -7015,6 +7073,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "de",
chan_suffix: "",
album_versions_title: "Outras versões",
},
Language::Ro => Entry {
timeago_tokens: ::phf::Map {
@ -7143,6 +7202,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "de",
chan_suffix: "",
album_versions_title: "Alte versiuni",
},
Language::Ru => Entry {
timeago_tokens: ::phf::Map {
@ -7286,6 +7346,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "",
album_versions_title: "Другие версии",
},
Language::Si => Entry {
timeago_tokens: ::phf::Map {
@ -7397,6 +7458,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{dd2}\u{dd2}\u{dca}",
album_versions_title: "අනෙක\u{dd4}\u{dca} අන\u{dd4}\u{dcf}දයන\u{dca}",
},
Language::Sk => Entry {
timeago_tokens: ::phf::Map {
@ -7527,6 +7589,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "Autori:",
chan_suffix: "",
album_versions_title: "Ďalšie verzie",
},
Language::Sl => Entry {
timeago_tokens: ::phf::Map {
@ -7676,6 +7739,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "kanal",
chan_suffix: "",
album_versions_title: "Druge različice",
},
Language::Sq => Entry {
timeago_tokens: ::phf::Map {
@ -7796,6 +7860,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "nga",
chan_suffix: "",
album_versions_title: "Versione të tjera",
},
Language::Sr => Entry {
timeago_tokens: ::phf::Map {
@ -7926,6 +7991,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "са канала",
chan_suffix: "",
album_versions_title: "Друге верзије",
},
Language::SrLatn => Entry {
timeago_tokens: ::phf::Map {
@ -8056,6 +8122,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "sa kanala",
chan_suffix: "",
album_versions_title: "Druge verzije",
},
Language::Sv => Entry {
timeago_tokens: ::phf::Map {
@ -8178,6 +8245,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "från",
chan_suffix: "",
album_versions_title: "Andra versioner",
},
Language::Sw => Entry {
timeago_tokens: ::phf::Map {
@ -8291,6 +8359,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "kutoka",
chan_suffix: "",
album_versions_title: "Matoleo mengine",
},
Language::Ta => Entry {
timeago_tokens: ::phf::Map {
@ -8421,6 +8490,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "வழங\u{bcd}கியவர\u{bcd}:",
chan_suffix: "",
album_versions_title: "பிற பதிப\u{bcd}புகள\u{bcd}",
},
Language::Te => Entry {
timeago_tokens: ::phf::Map {
@ -8547,6 +8617,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "\u{c3e}\u{c46}\u{c4d}\u{c4d}\u{c3e}\u{c3e}",
album_versions_title: "ఇతర వ\u{c46}\u{c4d}షన\u{c4d}\u{200c}లు",
},
Language::Th => Entry {
timeago_tokens: ::phf::Map {
@ -8677,6 +8748,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "โดย",
chan_suffix: "",
album_versions_title: "เวอร\u{e4c}\u{e31}นอ\u{e37}\u{e48}นๆ",
},
Language::Tr => Entry {
timeago_tokens: ::phf::Map {
@ -8797,6 +8869,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "tarafından",
album_versions_title: "Diğer versiyonlar",
},
Language::Uk => Entry {
timeago_tokens: ::phf::Map {
@ -8945,6 +9018,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "власник:",
chan_suffix: "",
album_versions_title: "Інші версії",
},
Language::Ur => Entry {
timeago_tokens: ::phf::Map {
@ -9070,6 +9144,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "منجانب",
chan_suffix: "",
album_versions_title: "دیگر ورژنز",
},
Language::Uz => Entry {
timeago_tokens: ::phf::Map {
@ -9184,6 +9259,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "muallif:",
chan_suffix: "",
album_versions_title: "Boshqa versiyalari",
},
Language::Vi => Entry {
timeago_tokens: ::phf::Map {
@ -9265,6 +9341,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "của",
chan_suffix: "",
album_versions_title: "Các phiên bản khác",
},
Language::ZhCn => Entry {
timeago_tokens: ::phf::Map {
@ -9362,6 +9439,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "创建者:",
chan_suffix: "",
album_versions_title: "其他版本",
},
Language::ZhHk => Entry {
timeago_tokens: ::phf::Map {
@ -9443,6 +9521,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "來自",
chan_suffix: "",
album_versions_title: "其他版本",
},
Language::ZhTw => Entry {
timeago_tokens: ::phf::Map {
@ -9523,6 +9602,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "",
chan_suffix: "建立",
album_versions_title: "其他版本",
},
Language::Zu => Entry {
timeago_tokens: ::phf::Map {
@ -9658,6 +9738,7 @@ pub(crate) fn entry(lang: Language) -> Entry {
},
chan_prefix: "ka-",
chan_suffix: "",
album_versions_title: "Ezinye izinguqulo",
},
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,85 @@
{
"af": "Ander weergawes",
"am": "ሌሎች ስሪቶች",
"ar": "إصدارات أخرى",
"as": "অন্য সংস্কৰণ",
"az": "Digər versiyalar",
"be": "Іншыя версіі",
"bg": "Други версии",
"bn": "অন্য সংস্করণগুলি",
"bs": "Druge verzije",
"ca": "Altres versions",
"cs": "Jiné verze",
"da": "Andre versioner",
"de": "Weitere Versionen",
"el": "Άλλες εκτελέσεις",
"en": "Other versions",
"en-GB": "Other versions",
"en-IN": "Other versions",
"es": "Otras versiones",
"es-419": "Otras versiones",
"es-US": "Otras versiones",
"et": "Teised versioonid",
"eu": "Beste bertsio batzuk",
"fa": "نسخه‌های دیگر",
"fi": "Muut versiot",
"fil": "Iba pang bersyon",
"fr": "Autres versions",
"fr-CA": "Autres versions",
"gl": "Outras versións",
"gu": "અન્ય વર્ઝન",
"hi": "अन्य वर्शन",
"hr": "Druge verzije",
"hu": "Más verziók",
"hy": "Այլ տարբերակներ",
"id": "Versi lainnya",
"is": "Aðrar útgáfur",
"it": "Altre versioni",
"iw": "גרסאות אחרות",
"ja": "他のバージョン",
"ka": "სხვა ვერსიები",
"kk": "Басқа нұсқалары",
"km": "ប្រភេទផ្សេងៗ",
"kn": "ಇತರ ಆವೃತ್ತಿಗಳು",
"ko": "다른 버전",
"ky": "Башка версиялар",
"lo": "ເວີຊັນອື່ນໆ",
"lt": "Kitos versijos",
"lv": "Citas versijas",
"mk": "Други верзии",
"ml": "മറ്റ് പതിപ്പുകൾ",
"mn": "Бусад хувилбар",
"mr": "इतर आवृत्त्या",
"ms": "Versi lain",
"my": "အခြား ဗားရှင်းများ",
"ne": "अन्य संस्करणहरू",
"nl": "Andere versies",
"no": "Andre versjoner",
"or": "ଅନ୍ୟ ସଂସ୍କରଣଗୁଡ଼ିକ",
"pa": "ਹੋਰ ਵਰਜਨ",
"pl": "Inne wersje",
"pt": "Outras versões",
"pt-PT": "Outras versões",
"ro": "Alte versiuni",
"ru": "Другие версии",
"si": "අනෙකුත් අනුවාදයන්",
"sk": "Ďalšie verzie",
"sl": "Druge različice",
"sq": "Versione të tjera",
"sr": "Друге верзије",
"sr-Latn": "Druge verzije",
"sv": "Andra versioner",
"sw": "Matoleo mengine",
"ta": "பிற பதிப்புகள்",
"te": "ఇతర వెర్షన్‌లు",
"th": "เวอร์ชันอื่นๆ",
"tr": "Diğer versiyonlar",
"uk": "Інші версії",
"ur": "دیگر ورژنز",
"uz": "Boshqa versiyalari",
"vi": "Các phiên bản khác",
"zh-CN": "其他版本",
"zh-HK": "其他版本",
"zh-TW": "其他版本",
"zu": "Ezinye izinguqulo"
}

File diff suppressed because it is too large Load diff