From 60b6715b38d137f8d6d0ce3c537a546a507ecf1f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 20 Aug 2023 21:22:15 -0300 Subject: content: listArtistsByName: divide by main groups --- src/content/dependencies/listArtistsByName.js | 123 ++++++++++++++++++++------ src/data/things/artist.js | 17 ++++ src/strings-default.json | 2 + 3 files changed, 113 insertions(+), 29 deletions(-) diff --git a/src/content/dependencies/listArtistsByName.js b/src/content/dependencies/listArtistsByName.js index 6c0ad836..d83150e8 100644 --- a/src/content/dependencies/listArtistsByName.js +++ b/src/content/dependencies/listArtistsByName.js @@ -1,51 +1,116 @@ -import {stitchArrays} from '#sugar'; +import {empty, stitchArrays, unique} from '#sugar'; import {getArtistNumContributions, sortAlphabetically} from '#wiki-data'; export default { - contentDependencies: ['generateListingPage', 'linkArtist'], + contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], extraDependencies: ['language', 'wikiData'], - sprawl({artistData}) { - return {artistData}; + sprawl({artistData, wikiInfo}) { + return {artistData, wikiInfo}; }, - query({artistData}, spec) { - return { - spec, + query(sprawl, spec) { + const artists = sortAlphabetically(sprawl.artistData.slice()); + const groups = sprawl.wikiInfo.divideTrackListsByGroups; - artists: sortAlphabetically(artistData.slice()), - }; + if (empty(groups)) { + return {spec, artists}; + } + + const artistGroups = + artists.map(artist => + unique( + unique([ + ...artist.albumsAsAny, + ...artist.tracksAsAny.map(track => track.album), + ]).flatMap(album => album.groups))) + + const artistsByGroup = + groups.map(group => + artists.filter((artist, index) => artistGroups[index].includes(group))); + + return {spec, groups, artistsByGroup}; }, relations(relation, query) { - return { - page: relation('generateListingPage', query.spec), + const relations = {}; - artistLinks: + relations.page = + relation('generateListingPage', query.spec); + + if (query.artists) { + relations.artistLinks = query.artists - .map(artist => relation('linkArtist', artist)), - }; + .map(artist => relation('linkArtist', artist)); + } + + if (query.artistsByGroup) { + relations.groupLinks = + query.groups + .map(group => relation('linkGroup', group)); + + relations.artistLinksByGroup = + query.artistsByGroup + .map(artists => artists + .map(artist => relation('linkArtist', artist))); + } + + return relations; }, data(query) { - return { - counts: + const data = {}; + + if (query.artists) { + data.counts = query.artists - .map(artist => getArtistNumContributions(artist)), - }; + .map(artist => getArtistNumContributions(artist)); + } + + if (query.artistsByGroup) { + data.countsByGroup = + query.artistsByGroup + .map(artists => artists + .map(artist => getArtistNumContributions(artist))); + } + + return data; }, generate(data, relations, {language}) { - return relations.page.slots({ - type: 'rows', - rows: - stitchArrays({ - link: relations.artistLinks, - count: data.counts, - }).map(({link, count}) => ({ - artist: link, - contributions: language.countContributions(count, {unit: true}), - })), - }); + return ( + (relations.artistLinksByGroup + ? relations.page.slots({ + type: 'chunks', + + chunkTitles: + relations.groupLinks.map(groupLink => ({ + group: groupLink, + })), + + chunkRows: + stitchArrays({ + artistLinks: relations.artistLinksByGroup, + counts: data.countsByGroup, + }).map(({artistLinks, counts}) => + stitchArrays({ + link: artistLinks, + count: counts, + }).map(({link, count}) => ({ + artist: link, + contributions: language.countContributions(count, {unit: true}), + }))), + }) + : relations.page.slots({ + type: 'rows', + rows: + stitchArrays({ + link: relations.artistLinks, + count: data.counts, + }).map(({link, count}) => ({ + artist: link, + contributions: language.countContributions(count, {unit: true}), + })), + }))); }, }; diff --git a/src/data/things/artist.js b/src/data/things/artist.js index 522ca5f9..6d4f4a0d 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -99,6 +99,23 @@ export class Artist extends Thing { albumsAsBannerArtist: Artist.filterByContrib('albumData', 'bannerArtistContribs'), + albumsAsAny: { + flags: {expose: true}, + + expose: { + dependencies: ['albumData'], + + compute: ({albumData, [Artist.instance]: artist}) => + albumData?.filter((album) => + [ + ...album.artistContribs, + ...album.coverArtistContribs, + ...album.wallpaperArtistContribs, + ...album.bannerArtistContribs, + ].some(({who}) => who === artist)) ?? [], + }, + }, + albumsAsCommentator: { flags: {expose: true}, diff --git a/src/strings-default.json b/src/strings-default.json index 8d7756ad..2b4b0981 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -367,6 +367,8 @@ "listingPage.listArtists.byName.title": "Artists - by Name", "listingPage.listArtists.byName.title.short": "...by Name", "listingPage.listArtists.byName.item": "{ARTIST} ({CONTRIBUTIONS})", + "listingPage.listArtists.byName.chunk.title": "Contributed to {GROUP}:", + "listingPage.listArtists.byName.chunk.item": "{ARTIST} ({CONTRIBUTIONS})", "listingPage.listArtists.byContribs.title": "Artists - by Contributions", "listingPage.listArtists.byContribs.title.short": "...by Contributions", "listingPage.listArtists.byContribs.item": "{ARTIST} ({CONTRIBUTIONS})", -- cgit 1.3.0-6-gf8a5 From e035dab576875bca12485f60a1aeb257c394c723 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 20 Aug 2023 22:07:05 -0300 Subject: content: generateListingPage: "skip to a section" --- src/content/dependencies/generateListingPage.js | 38 +++++++++++++++++++++++-- src/content/dependencies/listArtistsByName.js | 19 ++++++++++++- src/strings-default.json | 1 + 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 08eb40c6..4de2d006 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -7,6 +7,7 @@ export default { 'generatePageLayout', 'linkListing', 'linkListingIndex', + 'linkTemplate', ], extraDependencies: ['html', 'language', 'wikiData'], @@ -26,6 +27,9 @@ export default { relations.chunkHeading = relation('generateContentHeading'); + relations.showSkipToSectionLinkTemplate = + relation('linkTemplate'); + if (listing.target.listings.length > 1) { relations.sameTargetListingLinks = listing.target.listings @@ -65,6 +69,9 @@ export default { chunkTitles: {validate: v => v.strictArrayOf(v.isObject)}, chunkRows: {validate: v => v.strictArrayOf(v.isObject)}, + showSkipToSection: {type: 'boolean', default: false}, + chunkIDs: {validate: v => v.strictArrayOf(v.isString)}, + listStyle: { validate: v => v.is('ordered', 'unordered'), default: 'unordered', @@ -128,16 +135,40 @@ export default { formatListingString('item', row)))), slots.type === 'chunks' && - html.tag('dl', + html.tag('dl', [ + slots.showSkipToSection && [ + html.tag('dt', + language.$('listingPage.skipToSection')), + + html.tag('dd', + html.tag('ul', + stitchArrays({ + title: slots.chunkTitles, + id: slots.chunkIDs, + }).filter(({id}) => id) + .map(({title, id}) => + html.tag('li', + relations.showSkipToSectionLinkTemplate + .clone() + .slots({ + hash: id, + content: + formatListingString('chunk.title', title) + .replace(/:$/, ''), + }))))), + ], + stitchArrays({ title: slots.chunkTitles, rows: slots.chunkRows, - }).map(({title, rows}) => [ + id: slots.chunkIDs, + }).map(({title, rows, id}) => [ relations.chunkHeading .clone() .slots({ tag: 'dt', title: formatListingString('chunk.title', title), + id, }), html.tag('dd', @@ -146,7 +177,8 @@ export default { html.tag('li', {class: row.stringsKey === 'rerelease' && 'rerelease'}, formatListingString('chunk.item', row))))), - ])), + ]), + ]), slots.type === 'custom' && slots.content, diff --git a/src/content/dependencies/listArtistsByName.js b/src/content/dependencies/listArtistsByName.js index d83150e8..3778b9e3 100644 --- a/src/content/dependencies/listArtistsByName.js +++ b/src/content/dependencies/listArtistsByName.js @@ -1,5 +1,10 @@ import {empty, stitchArrays, unique} from '#sugar'; -import {getArtistNumContributions, sortAlphabetically} from '#wiki-data'; + +import { + filterMultipleArrays, + getArtistNumContributions, + sortAlphabetically, +} from '#wiki-data'; export default { contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], @@ -29,6 +34,9 @@ export default { groups.map(group => artists.filter((artist, index) => artistGroups[index].includes(group))); + filterMultipleArrays(groups, artistsByGroup, + (group, artists) => !empty(artists)); + return {spec, groups, artistsByGroup}; }, @@ -68,6 +76,10 @@ export default { } if (query.artistsByGroup) { + data.groupDirectories = + query.groups + .map(group => group.directory); + data.countsByGroup = query.artistsByGroup .map(artists => artists @@ -83,6 +95,11 @@ export default { ? relations.page.slots({ type: 'chunks', + showSkipToSection: true, + chunkIDs: + data.groupDirectories + .map(directory => `contributed-to-${directory}`), + chunkTitles: relations.groupLinks.map(groupLink => ({ group: groupLink, diff --git a/src/strings-default.json b/src/strings-default.json index 2b4b0981..2cab3a19 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -348,6 +348,7 @@ "listingPage.target.other": "Other", "listingPage.listingsFor": "Listings for {TARGET}: {LISTINGS}", "listingPage.seeAlso": "Also check out: {LISTINGS}", + "listingPage.skipToSection": "Skip to a section:", "listingPage.listAlbums.byName.title": "Albums - by Name", "listingPage.listAlbums.byName.title.short": "...by Name", "listingPage.listAlbums.byName.item": "{ALBUM} ({TRACKS})", -- cgit 1.3.0-6-gf8a5 From f12605331f60796da178f3d4b7bbc096f02b0d48 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 28 Aug 2023 09:34:29 -0300 Subject: content: move new "Artists - by Name" implementation to "by Group" --- src/content/dependencies/listArtistsByGroup.js | 133 ++++++++++++++++++++++ src/content/dependencies/listArtistsByName.js | 151 ++++++------------------- src/listing-spec.js | 10 ++ src/strings-default.json | 7 +- 4 files changed, 180 insertions(+), 121 deletions(-) create mode 100644 src/content/dependencies/listArtistsByGroup.js diff --git a/src/content/dependencies/listArtistsByGroup.js b/src/content/dependencies/listArtistsByGroup.js new file mode 100644 index 00000000..3778b9e3 --- /dev/null +++ b/src/content/dependencies/listArtistsByGroup.js @@ -0,0 +1,133 @@ +import {empty, stitchArrays, unique} from '#sugar'; + +import { + filterMultipleArrays, + getArtistNumContributions, + sortAlphabetically, +} from '#wiki-data'; + +export default { + contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], + extraDependencies: ['language', 'wikiData'], + + sprawl({artistData, wikiInfo}) { + return {artistData, wikiInfo}; + }, + + query(sprawl, spec) { + const artists = sortAlphabetically(sprawl.artistData.slice()); + const groups = sprawl.wikiInfo.divideTrackListsByGroups; + + if (empty(groups)) { + return {spec, artists}; + } + + const artistGroups = + artists.map(artist => + unique( + unique([ + ...artist.albumsAsAny, + ...artist.tracksAsAny.map(track => track.album), + ]).flatMap(album => album.groups))) + + const artistsByGroup = + groups.map(group => + artists.filter((artist, index) => artistGroups[index].includes(group))); + + filterMultipleArrays(groups, artistsByGroup, + (group, artists) => !empty(artists)); + + return {spec, groups, artistsByGroup}; + }, + + relations(relation, query) { + const relations = {}; + + relations.page = + relation('generateListingPage', query.spec); + + if (query.artists) { + relations.artistLinks = + query.artists + .map(artist => relation('linkArtist', artist)); + } + + if (query.artistsByGroup) { + relations.groupLinks = + query.groups + .map(group => relation('linkGroup', group)); + + relations.artistLinksByGroup = + query.artistsByGroup + .map(artists => artists + .map(artist => relation('linkArtist', artist))); + } + + return relations; + }, + + data(query) { + const data = {}; + + if (query.artists) { + data.counts = + query.artists + .map(artist => getArtistNumContributions(artist)); + } + + if (query.artistsByGroup) { + data.groupDirectories = + query.groups + .map(group => group.directory); + + data.countsByGroup = + query.artistsByGroup + .map(artists => artists + .map(artist => getArtistNumContributions(artist))); + } + + return data; + }, + + generate(data, relations, {language}) { + return ( + (relations.artistLinksByGroup + ? relations.page.slots({ + type: 'chunks', + + showSkipToSection: true, + chunkIDs: + data.groupDirectories + .map(directory => `contributed-to-${directory}`), + + chunkTitles: + relations.groupLinks.map(groupLink => ({ + group: groupLink, + })), + + chunkRows: + stitchArrays({ + artistLinks: relations.artistLinksByGroup, + counts: data.countsByGroup, + }).map(({artistLinks, counts}) => + stitchArrays({ + link: artistLinks, + count: counts, + }).map(({link, count}) => ({ + artist: link, + contributions: language.countContributions(count, {unit: true}), + }))), + }) + : relations.page.slots({ + type: 'rows', + rows: + stitchArrays({ + link: relations.artistLinks, + count: data.counts, + }).map(({link, count}) => ({ + artist: link, + contributions: language.countContributions(count, {unit: true}), + })), + }))); + }, +}; diff --git a/src/content/dependencies/listArtistsByName.js b/src/content/dependencies/listArtistsByName.js index 3778b9e3..554b4587 100644 --- a/src/content/dependencies/listArtistsByName.js +++ b/src/content/dependencies/listArtistsByName.js @@ -1,133 +1,46 @@ -import {empty, stitchArrays, unique} from '#sugar'; - -import { - filterMultipleArrays, - getArtistNumContributions, - sortAlphabetically, -} from '#wiki-data'; +import {stitchArrays} from '#sugar'; +import {getArtistNumContributions, sortAlphabetically} from '#wiki-data'; export default { contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], extraDependencies: ['language', 'wikiData'], - sprawl({artistData, wikiInfo}) { - return {artistData, wikiInfo}; - }, - - query(sprawl, spec) { - const artists = sortAlphabetically(sprawl.artistData.slice()); - const groups = sprawl.wikiInfo.divideTrackListsByGroups; - - if (empty(groups)) { - return {spec, artists}; - } - - const artistGroups = - artists.map(artist => - unique( - unique([ - ...artist.albumsAsAny, - ...artist.tracksAsAny.map(track => track.album), - ]).flatMap(album => album.groups))) - - const artistsByGroup = - groups.map(group => - artists.filter((artist, index) => artistGroups[index].includes(group))); - - filterMultipleArrays(groups, artistsByGroup, - (group, artists) => !empty(artists)); - - return {spec, groups, artistsByGroup}; - }, - - relations(relation, query) { - const relations = {}; + sprawl: ({artistData, wikiInfo}) => + ({artistData, wikiInfo}), - relations.page = - relation('generateListingPage', query.spec); + query: (sprawl, spec) => ({ + spec, - if (query.artists) { - relations.artistLinks = - query.artists - .map(artist => relation('linkArtist', artist)); - } + artists: + sortAlphabetically(sprawl.artistData.slice()), + }), - if (query.artistsByGroup) { - relations.groupLinks = - query.groups - .map(group => relation('linkGroup', group)); + relations: (relation, query) => ({ + page: + relation('generateListingPage', query.spec), - relations.artistLinksByGroup = - query.artistsByGroup - .map(artists => artists - .map(artist => relation('linkArtist', artist))); - } + artistLinks: + query.artists + .map(artist => relation('linkArtist', artist)), + }), - return relations; - }, - - data(query) { - const data = {}; - - if (query.artists) { - data.counts = - query.artists - .map(artist => getArtistNumContributions(artist)); - } - - if (query.artistsByGroup) { - data.groupDirectories = - query.groups - .map(group => group.directory); - - data.countsByGroup = - query.artistsByGroup - .map(artists => artists - .map(artist => getArtistNumContributions(artist))); - } - - return data; - }, + data: (query) => ({ + counts: + query.artists + .map(artist => getArtistNumContributions(artist)), + }), generate(data, relations, {language}) { - return ( - (relations.artistLinksByGroup - ? relations.page.slots({ - type: 'chunks', - - showSkipToSection: true, - chunkIDs: - data.groupDirectories - .map(directory => `contributed-to-${directory}`), - - chunkTitles: - relations.groupLinks.map(groupLink => ({ - group: groupLink, - })), - - chunkRows: - stitchArrays({ - artistLinks: relations.artistLinksByGroup, - counts: data.countsByGroup, - }).map(({artistLinks, counts}) => - stitchArrays({ - link: artistLinks, - count: counts, - }).map(({link, count}) => ({ - artist: link, - contributions: language.countContributions(count, {unit: true}), - }))), - }) - : relations.page.slots({ - type: 'rows', - rows: - stitchArrays({ - link: relations.artistLinks, - count: data.counts, - }).map(({link, count}) => ({ - artist: link, - contributions: language.countContributions(count, {unit: true}), - })), - }))); + return relations.page.slots({ + type: 'rows', + rows: + stitchArrays({ + link: relations.artistLinks, + count: data.counts, + }).map(({link, count}) => ({ + artist: link, + contributions: language.countContributions(count, {unit: true}), + })), + }); }, }; diff --git a/src/listing-spec.js b/src/listing-spec.js index fe36fc01..7918dd1e 100644 --- a/src/listing-spec.js +++ b/src/listing-spec.js @@ -54,12 +54,14 @@ listingSpec.push({ directory: 'artists/by-name', stringsKey: 'listArtists.byName', contentFunction: 'listArtistsByName', + seeAlso: ['artists/by-contribs', 'artists/by-group'], }); listingSpec.push({ directory: 'artists/by-contribs', stringsKey: 'listArtists.byContribs', contentFunction: 'listArtistsByContributions', + seeAlso: ['artists/by-name', 'artists/by-group'], }); listingSpec.push({ @@ -74,6 +76,14 @@ listingSpec.push({ contentFunction: 'listArtistsByDuration', }); +listingSpec.push({ + directory: 'artists/by-group', + stringsKey: 'listArtists.byGroup', + contentFunction: 'listArtistsByGroup', + featureFlag: 'enableGroupUI', + seeAlso: ['artists/by-name', 'artists/by-contribs'], +}); + listingSpec.push({ directory: 'artists/by-latest', stringsKey: 'listArtists.byLatest', diff --git a/src/strings-default.json b/src/strings-default.json index 2cab3a19..457b4bed 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -368,8 +368,6 @@ "listingPage.listArtists.byName.title": "Artists - by Name", "listingPage.listArtists.byName.title.short": "...by Name", "listingPage.listArtists.byName.item": "{ARTIST} ({CONTRIBUTIONS})", - "listingPage.listArtists.byName.chunk.title": "Contributed to {GROUP}:", - "listingPage.listArtists.byName.chunk.item": "{ARTIST} ({CONTRIBUTIONS})", "listingPage.listArtists.byContribs.title": "Artists - by Contributions", "listingPage.listArtists.byContribs.title.short": "...by Contributions", "listingPage.listArtists.byContribs.item": "{ARTIST} ({CONTRIBUTIONS})", @@ -379,6 +377,11 @@ "listingPage.listArtists.byDuration.title": "Artists - by Duration", "listingPage.listArtists.byDuration.title.short": "...by Duration", "listingPage.listArtists.byDuration.item": "{ARTIST} ({DURATION})", + "listingPage.listArtists.byGroup.title": "Artists - by Group", + "listingPage.listArtists.byGroup.title.short": "...by Group", + "listingPage.listArtists.byGroup.item": "{ARTIST} ({CONTRIBUTIONS})", + "listingPage.listArtists.byGroup.chunk.title": "Contributed to {GROUP}:", + "listingPage.listArtists.byGroup.chunk.item": "{ARTIST} ({CONTRIBUTIONS})", "listingPage.listArtists.byLatest.title": "Artists - by Latest Contribution", "listingPage.listArtists.byLatest.title.short": "...by Latest Contribution", "listingPage.listArtists.byLatest.chunk.title.album": "{ALBUM} ({DATE})", -- cgit 1.3.0-6-gf8a5 From 168425e7a3dd3a268cfdbd2a395e11ac72eaa3b2 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 28 Aug 2023 09:57:50 -0300 Subject: content: generateListingPage: don't mutate options in slots --- src/content/dependencies/generateListingPage.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 4de2d006..3c50bd59 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -91,12 +91,13 @@ export default { const parts = [baseStringsKey, contextStringsKey]; - if (options.stringsKey) { + const {stringsKey, ...passOptions} = options; + + if (stringsKey) { parts.push(options.stringsKey); - delete options.stringsKey; } - return language.formatString(parts.join('.'), options); + return language.formatString(parts.join('.'), passOptions); }; return relations.layout.slots({ -- cgit 1.3.0-6-gf8a5 From 6b76c7a0c852b87b3ff2e4f3a11fe15424618511 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 28 Aug 2023 09:58:22 -0300 Subject: content: listArtistsByContributions: use updated chunk layout --- .../dependencies/listArtistsByContributions.js | 116 ++++++++++----------- src/strings-default.json | 5 +- 2 files changed, 58 insertions(+), 63 deletions(-) diff --git a/src/content/dependencies/listArtistsByContributions.js b/src/content/dependencies/listArtistsByContributions.js index 86c8cfa2..58c51a40 100644 --- a/src/content/dependencies/listArtistsByContributions.js +++ b/src/content/dependencies/listArtistsByContributions.js @@ -1,5 +1,11 @@ -import {stitchArrays, unique} from '#sugar'; -import {filterByCount, sortAlphabetically, sortByCount} from '#wiki-data'; +import {empty, stitchArrays, unique} from '#sugar'; + +import { + filterByCount, + filterMultipleArrays, + sortAlphabetically, + sortByCount, +} from '#wiki-data'; export default { contentDependencies: ['generateListingPage', 'linkArtist'], @@ -96,68 +102,54 @@ export default { return data; }, - generate(data, relations, {html, language}) { - const lists = Object.fromEntries( - ([ - ['tracks', [ - relations.artistLinksByTrackContributions, - data.countsByTrackContributions, - 'countTracks', - ]], - - ['artworks', [ - relations.artistLinksByArtworkContributions, - data.countsByArtworkContributions, - 'countArtworks', - ]], - - data.enableFlashesAndGames && - ['flashes', [ - relations.artistLinksByFlashContributions, - data.countsByFlashContributions, - 'countFlashes', - ]], - ]).filter(Boolean) - .map(([key, [artistLinks, counts, countFunction]]) => [ - key, - html.tag('ul', - stitchArrays({ - artistLink: artistLinks, - count: counts, - }).map(({artistLink, count}) => - html.tag('li', - language.$('listingPage.listArtists.byContribs.item', { - artist: artistLink, - contributions: language[countFunction](count, {unit: true}), - })))), - ])); + generate(data, relations, {language}) { + const listChunkIDs = ['tracks', 'artworks', 'flashes']; + const listTitleStringsKeys = ['trackContributors', 'artContributors', 'flashContributors']; + const listCountFunctions = ['countTracks', 'countArtworks', 'countFlashes']; + + const listArtistLinks = [ + relations.artistLinksByTrackContributions, + relations.artistLinksByArtworkContributions, + relations.artistLinksByFlashContributions, + ]; + + const listArtistCounts = [ + data.countsByTrackContributions, + data.countsByArtworkContributions, + data.countsByFlashContributions, + ]; + + filterMultipleArrays( + listChunkIDs, + listTitleStringsKeys, + listCountFunctions, + listArtistLinks, + listArtistCounts, + (_chunkID, _titleStringsKey, _countFunction, artistLinks, _artistCounts) => + !empty(artistLinks)); return relations.page.slots({ - type: 'custom', - content: - html.tag('div', {class: 'content-columns'}, [ - html.tag('div', {class: 'column'}, [ - html.tag('h2', - language.$('listingPage.misc.trackContributors')), - - lists.tracks, - ]), - - html.tag('div', {class: 'column'}, [ - html.tag('h2', - language.$( - 'listingPage.misc.artContributors')), - - lists.artworks, - - lists.flashes && [ - html.tag('h2', - language.$('listingPage.misc.flashContributors')), - - lists.flashes, - ], - ]), - ]), + type: 'chunks', + + showSkipToSection: true, + chunkIDs: listChunkIDs, + + chunkTitles: + listTitleStringsKeys.map(stringsKey => ({stringsKey})), + + chunkRows: + stitchArrays({ + artistLinks: listArtistLinks, + artistCounts: listArtistCounts, + countFunction: listCountFunctions, + }).map(({artistLinks, artistCounts, countFunction}) => + stitchArrays({ + artistLink: artistLinks, + artistCount: artistCounts, + }).map(({artistLink, artistCount}) => ({ + artist: artistLink, + contributions: language[countFunction](artistCount, {unit: true}), + }))), }); }, }; diff --git a/src/strings-default.json b/src/strings-default.json index 457b4bed..d170b86b 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -370,7 +370,10 @@ "listingPage.listArtists.byName.item": "{ARTIST} ({CONTRIBUTIONS})", "listingPage.listArtists.byContribs.title": "Artists - by Contributions", "listingPage.listArtists.byContribs.title.short": "...by Contributions", - "listingPage.listArtists.byContribs.item": "{ARTIST} ({CONTRIBUTIONS})", + "listingPage.listArtists.byContribs.chunk.title.trackContributors": "Contributed tracks:", + "listingPage.listArtists.byContribs.chunk.title.artContributors": "Contributed artworks:", + "listingPage.listArtists.byContribs.chunk.title.flashContributors": "Contributed to flashes & games:", + "listingPage.listArtists.byContribs.chunk.item": "{ARTIST} ({CONTRIBUTIONS})", "listingPage.listArtists.byCommentary.title": "Artists - by Commentary Entries", "listingPage.listArtists.byCommentary.title.short": "...by Commentary Entries", "listingPage.listArtists.byCommentary.item": "{ARTIST} ({ENTRIES})", -- cgit 1.3.0-6-gf8a5 From 715bd8eff86c20b3773a66e96e9c9d6ae8602644 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 28 Aug 2023 19:19:39 -0300 Subject: content: listArtistsByLatestContribution: chunk-based rework --- .../listArtistsByLatestContribution.js | 594 ++++++++++----------- src/strings-default.json | 6 +- 2 files changed, 279 insertions(+), 321 deletions(-) diff --git a/src/content/dependencies/listArtistsByLatestContribution.js b/src/content/dependencies/listArtistsByLatestContribution.js index b6ea8e96..edb02e0d 100644 --- a/src/content/dependencies/listArtistsByLatestContribution.js +++ b/src/content/dependencies/listArtistsByLatestContribution.js @@ -1,15 +1,16 @@ -import {transposeArrays, empty, stitchArrays} from '#sugar'; +import {empty, stitchArrays} from '#sugar'; +import T from '#things'; import { chunkMultipleArrays, - compareCaseLessSensitive, - compareDates, - filterMultipleArrays, - reduceMultipleArrays, sortAlphabetically, + sortAlbumsTracksChronologically, + sortFlashesChronologically, sortMultipleArrays, } from '#wiki-data'; +const {Album, Flash} = T; + export default { contentDependencies: [ 'generateListingPage', @@ -20,348 +21,303 @@ export default { extraDependencies: ['html', 'language', 'wikiData'], - sprawl({artistData, wikiInfo}) { - return { - artistData, - enableFlashesAndGames: wikiInfo.enableFlashesAndGames, - }; - }, + sprawl: ({albumData, artistData, flashData, trackData, wikiInfo}) => + ({albumData, artistData, flashData, trackData, + enableFlashesAndGames: wikiInfo.enableFlashesAndGames}), query(sprawl, spec) { - const query = { - spec, - enableFlashesAndGames: sprawl.enableFlashesAndGames, - }; - - const queryContributionInfo = ( - artistsKey, - chunkThingsKey, - datesKey, - datelessArtistsKey, - fn, - ) => { - const artists = sortAlphabetically(sprawl.artistData.slice()); - - // Each value stored in dateLists, corresponding to each artist, - // is going to be a list of dates and nulls. Any nulls represent - // a contribution which isn't associated with a particular date. - const [chunkThingLists, dateLists] = - transposeArrays(artists.map(artist => fn(artist))); - - // Scrap artists who don't even have any relevant contributions. - // These artists may still have other contributions across the wiki, but - // they weren't returned by the callback and so aren't relevant to this - // list. - filterMultipleArrays( - artists, - chunkThingLists, - dateLists, - (artists, chunkThings, dates) => !empty(dates)); - - // Also exclude artists whose remaining contributions are all dateless. - // But keep track of the artists removed here, since they'll be displayed - // in an additional list in the final listing page. - const {removed: [datelessArtists]} = - filterMultipleArrays( - artists, - chunkThingLists, - dateLists, - (artist, chunkThings, dates) => !empty(dates.filter(Boolean))); - - // Cut out dateless contributions. They're not relevant to finding the - // latest date. - for (const [chunkThings, dates] of transposeArrays([chunkThingLists, dateLists])) { - filterMultipleArrays(chunkThings, dates, (chunkThing, date) => date); + // + // First main step is to get the latest thing each artist has contributed + // to, and the date associated with that contribution! Some notes: + // + // * Album and track contributions are considered before flashes, so + // they'll take priority if an artist happens to have multiple contribs + // landing on the same date to both an album and a flash. + // + // * The final (album) contribution list is chunked by album, but also by + // date, because an individual album can cover a variety of dates. + // + // * If an artist has contributed both artworks and tracks to the album + // containing their latest contribution, then that will be indicated + // in an annotation, but *only if* those contributions were also on + // the same date. + // + // * If an artist made contributions to multiple albums on the same date, + // then the first of the *albums* sorted chronologically (latest first) + // is the one that will count. + // + // * Same for artists who've contributed to multiple flashes which were + // released on the same date. + // + // * The map may exclude artists none of whose contributions were dated. + // + + const artistLatestContribMap = new Map(); + + const considerDate = (artist, date, thing, contribution) => { + if (!date) { + return; } - const [chunkThings, dates] = - transposeArrays( - transposeArrays([chunkThingLists, dateLists]) - .map(([chunkThings, dates]) => - reduceMultipleArrays( - chunkThings, dates, - (accChunkThing, accDate, chunkThing, date) => - (date && date > accDate - ? [chunkThing, date] - : [accChunkThing, accDate])))); - - sortMultipleArrays(artists, dates, chunkThings, - (artistA, artistB, dateA, dateB, chunkThingA, chunkThingB) => { - const dateComparison = compareDates(dateA, dateB, {latestFirst: true}); - if (dateComparison !== 0) { - return dateComparison; - } - - // TODO: Compare alphabetically, not just by directory. - return compareCaseLessSensitive(chunkThingA.directory, chunkThingB.directory); - }); - - const chunks = - chunkMultipleArrays(artists, dates, chunkThings, - (artist, lastArtist, date, lastDate, chunkThing, lastChunkThing) => - +date !== +lastDate || chunkThing !== lastChunkThing); + if (artistLatestContribMap.has(artist)) { + const latest = artistLatestContribMap.get(artist); + if (latest.date > date) { + return; + } - query[chunkThingsKey] = - chunks.map(([artists, dates, chunkThings]) => chunkThings[0]); - - query[datesKey] = - chunks.map(([artists, dates, chunkThings]) => dates[0]); + if (latest.date === date) { + if (latest.thing === thing) { + // May combine differnt contributions to the same thing and date. + latest.contribution.add(contribution); + } - query[artistsKey] = - chunks.map(([artists, dates, chunkThings]) => artists); + // Earlier-processed things of same date take priority. + return; + } + } - query[datelessArtistsKey] = datelessArtists; + // First entry for artist or more recent contribution than latest date. + artistLatestContribMap.set(artist, { + date, + thing, + contribution: new Set([contribution]), + }); }; - queryContributionInfo( - 'artistsByTrackContributions', - 'albumsByTrackContributions', - 'datesByTrackContributions', - 'datelessArtistsByTrackContributions', - artist => { - const tracks = - [...artist.tracksAsArtist, ...artist.tracksAsContributor] - .filter(track => !track.originalReleaseTrack); - - const albums = tracks.map(track => track.album); - const dates = tracks.map(track => track.date); + const getArtists = (thing, key) => thing[key].map(({who}) => who); - return [albums, dates]; - }); + const albumsLatestFirst = sortAlbumsTracksChronologically(sprawl.albumData.slice()); + const tracksLatestFirst = sortAlbumsTracksChronologically(sprawl.trackData.slice()); + const flashesLatestFirst = sortFlashesChronologically(sprawl.flashData.slice()); - queryContributionInfo( - 'artistsByArtworkContributions', - 'albumsByArtworkContributions', - 'datesByArtworkContributions', - 'datelessArtistsByArtworkContributions', - artist => [ - [ - ...artist.tracksAsCoverArtist.map(track => track.album), - ...artist.albumsAsCoverArtist, - ...artist.albumsAsWallpaperArtist, - ...artist.albumsAsBannerArtist, - ], - [ - // TODO: Per-artwork dates, see #90. - ...artist.tracksAsCoverArtist.map(track => track.coverArtDate), - ...artist.albumsAsCoverArtist.map(album => album.coverArtDate), - ...artist.albumsAsWallpaperArtist.map(album => album.coverArtDate), - ...artist.albumsAsBannerArtist.map(album => album.coverArtDate), - ], - ]); - - if (sprawl.enableFlashesAndGames) { - queryContributionInfo( - 'artistsByFlashContributions', - 'flashesByFlashContributions', - 'datesByFlashContributions', - 'datelessArtistsByFlashContributions', - artist => [ - [ - ...artist.flashesAsContributor, - ], - [ - ...artist.flashesAsContributor.map(flash => flash.date), - ], - ]); + for (const album of albumsLatestFirst) { + for (const artist of new Set([ + ...getArtists(album, 'coverArtistContribs'), + ...getArtists(album, 'wallpaperArtistContribs'), + ...getArtists(album, 'bannerArtistContribs'), + ])) { + // Might combine later with 'track' of the same album and date. + considerDate(artist, album.coverArtDate, album, 'artwork'); + } } - return query; - }, - - relations(relation, query) { - const relations = {}; - - relations.page = - relation('generateListingPage', query.spec); - - // Track contributors - - relations.albumLinksByTrackContributions = - query.albumsByTrackContributions - .map(album => relation('linkAlbum', album)); - - relations.artistLinksByTrackContributions = - query.artistsByTrackContributions - .map(artists => - artists.map(artist => relation('linkArtist', artist))); - - relations.datelessArtistLinksByTrackContributions = - query.datelessArtistsByTrackContributions - .map(artist => relation('linkArtist', artist)); - - // Artwork contributors - - relations.albumLinksByArtworkContributions = - query.albumsByArtworkContributions - .map(album => relation('linkAlbum', album)); + for (const track of tracksLatestFirst) { + for (const artist of getArtists(track, 'coverArtistContribs')) { + // No special effect if artist already has 'artwork' for the same album and date. + considerDate(artist, track.coverArtDate, track.album, 'artwork'); + } - relations.artistLinksByArtworkContributions = - query.artistsByArtworkContributions - .map(artists => - artists.map(artist => relation('linkArtist', artist))); + for (const artist of new Set([ + ...getArtists(track, 'artistContribs'), + ...getArtists(track, 'contributorContribs'), + ])) { + // Might be combining with 'artwork' of the same album and date. + considerDate(artist, track.date, track.album, 'track'); + } + } - relations.datelessArtistLinksByArtworkContributions = - query.datelessArtistsByArtworkContributions - .map(artist => relation('linkArtist', artist)); + for (const flash of flashesLatestFirst) { + for (const artist of getArtists(flash, 'contributorContribs')) { + // Won't take priority above album contributions of the same date. + considerDate(artist, flash.date, flash, 'flash'); + } + } - // Flash contributors + // + // Next up is to sort all the processed artist information! + // + // Entries with the same album/flash and the same date go together first, + // with the following rules for sorting artists therein: + // + // * If the contributions are different, which can only happen for albums, + // then it's tracks-only first, tracks + artworks next, and artworks-only + // last. + // + // * If the contributions are the same, then sort alphabetically. + // + // Entries with different albums/flashes follow another set of rules: + // + // * Later dates come before earlier dates. + // + // * On the same date, albums come before flashes. + // + // * Things of the same type *and* date are sorted alphabetically. + // + + const artistsAlphabetically = + sortAlphabetically(sprawl.artistData.slice()); + + const artists = + Array.from(artistLatestContribMap.keys()); + + const artistContribEntries = + Array.from(artistLatestContribMap.values()); + + const artistThings = + artistContribEntries.map(({thing}) => thing); + + const artistDates = + artistContribEntries.map(({date}) => date); + + const artistContributions = + artistContribEntries.map(({contribution}) => contribution); + + sortMultipleArrays(artistThings, artistDates, artistContributions, artists, + (thing1, thing2, date1, date2, contrib1, contrib2, artist1, artist2) => { + if (date1 === date2 && thing1 === thing2) { + // Move artwork-only contribs after contribs with tracks. + if (!contrib1.has('track') && contrib2.has('track')) return 1; + if (!contrib2.has('track') && contrib1.has('track')) return -1; + + // Move track-only contribs before tracks with tracks and artwork. + if (!contrib1.has('artwork') && contrib2.has('artwork')) return -1; + if (!contrib2.has('artwork') && contrib1.has('artwork')) return 1; + + // Sort artists of the same type of contribution alphabetically, + // referring to a previous sort. + const index1 = artistsAlphabetically.indexOf(artist1); + const index2 = artistsAlphabetically.indexOf(artist2); + return index1 - index2; + } else { + // Move later dates before earlier ones. + if (date1 !== date2) return date2 - date1; + + // Move albums before flashes. + if (thing1 instanceof Album && thing2 instanceof Flash) return -1; + if (thing1 instanceof Flash && thing2 instanceof Album) return 1; + + // Sort two albums or two flashes alphabetically, referring to a + // previous sort (which was chronological but includes the correct + // ordering for things released on the same date). + const thingsLatestFirst = + (thing1 instanceof Album + ? albumsLatestFirst + : flashesLatestFirst); + const index1 = thingsLatestFirst.indexOf(thing1); + const index2 = thingsLatestFirst.indexOf(thing2); + return index2 - index1; + } + }); - if (query.enableFlashesAndGames) { - relations.flashLinksByFlashContributions = - query.flashesByFlashContributions - .map(flash => relation('linkFlash', flash)); + // Last off, turn the flat sorted list into a proper chunked list, now that + // entries going in the same chunk are sorted correctly next to each other. + // Then extract the parts that are useful for displaying on the listing! - relations.artistLinksByFlashContributions = - query.artistsByFlashContributions - .map(artists => - artists.map(artist => relation('linkArtist', artist))); + const chunks = + chunkMultipleArrays(artistThings, artistDates, artistContributions, artists, + (thing, lastThing, date, lastDate) => + thing !== lastThing || + +date !== +lastDate); - relations.datelessArtistLinksByFlashContributions = - query.datelessArtistsByFlashContributions - .map(artist => relation('linkArtist', artist)); - } + const chunkThings = + chunks.map(([artistThings, , , ]) => artistThings[0]); - return relations; - }, + const chunkDates = + chunks.map(([, artistDates, , ]) => artistDates[0]); - data(query) { - const data = {}; + const chunkArtistContributions = + chunks.map(([, , artistContributions, ]) => artistContributions); - data.enableFlashesAndGames = query.enableFlashesAndGames; + const chunkArtists = + chunks.map(([, , , artists]) => artists); - data.datesByTrackContributions = query.datesByTrackContributions; - data.datesByArtworkContributions = query.datesByArtworkContributions; + // And one bonus step - keep track of all the artists whose contributions + // were all without date. - if (query.enableFlashesAndGames) { - data.datesByFlashContributions = query.datesByFlashContributions; - } + const datelessArtists = + artistsAlphabetically + .filter(artist => !artists.includes(artist)); - return data; + return { + spec, + chunkThings, + chunkDates, + chunkArtistContributions, + chunkArtists, + datelessArtists, + }; }, - generate(data, relations, {html, language}) { - const chunkTitles = Object.fromEntries( - ([ - ['tracks', [ - 'album', - relations.albumLinksByTrackContributions, - data.datesByTrackContributions, - ]], - - ['artworks', [ - 'album', - relations.albumLinksByArtworkContributions, - data.datesByArtworkContributions, - ]], - - data.enableFlashesAndGames && - ['flashes', [ - 'flash', - relations.flashLinksByFlashContributions, - data.datesByFlashContributions, - ]], - ]).filter(Boolean) - .map(([key, [stringsKey, links, dates]]) => [ - key, - stitchArrays({link: links, date: dates}) - .map(({link, date}) => - html.tag('dt', - language.$(`listingPage.listArtists.byLatest.chunk.title.${stringsKey}`, { - [stringsKey]: link, - date: language.formatDate(date), - }))), - ])); - - const chunkItems = Object.fromEntries( - ([ - ['tracks', relations.artistLinksByTrackContributions], - ['artworks', relations.artistLinksByArtworkContributions], - data.enableFlashesAndGames && - ['flashes', relations.artistLinksByFlashContributions], - ]).filter(Boolean) - .map(([key, artistLinkLists]) => [ - key, - artistLinkLists.map(artistLinks => - html.tag('dd', - html.tag('ul', - artistLinks.map(artistLink => - html.tag('li', - language.$('listingPage.listArtists.byLatest.chunk.item', { - artist: artistLink, - })))))), - ])); - - const lists = Object.fromEntries( - ([ - ['tracks', [ - chunkTitles.tracks, - chunkItems.tracks, - relations.datelessArtistLinksByTrackContributions, - ]], - - ['artworks', [ - chunkTitles.artworks, - chunkItems.artworks, - relations.datelessArtistLinksByArtworkContributions, - ]], - - data.enableFlashesAndGames && - ['flashes', [ - chunkTitles.flashes, - chunkItems.flashes, - relations.datelessArtistLinksByFlashContributions, - ]], - ]).filter(Boolean) - .map(([key, [titles, items, datelessArtistLinks]]) => [ - key, - html.tags([ - html.tag('dl', - stitchArrays({ - title: titles, - items: items, - }).map(({title, items}) => [title, items])), - - !empty(datelessArtistLinks) && [ - html.tag('p', - language.$('listingPage.listArtists.byLatest.dateless.title')), - - html.tag('ul', - datelessArtistLinks.map(artistLink => - html.tag('li', - language.$('listingPage.listArtists.byLatest.dateless.item', { - artist: artistLink, - })))), - ], - ]), - ])); - + relations: (relation, query) => ({ + page: + relation('generateListingPage', query.spec), + + chunkAlbumLinks: + query.chunkThings + .map(thing => + (thing instanceof Album + ? relation('linkAlbum', thing) + : null)), + + chunkFlashLinks: + query.chunkThings + .map(thing => + (thing instanceof Flash + ? relation('linkFlash', thing) + : null)), + + chunkArtistLinks: + query.chunkArtists + .map(artists => artists + .map(artist => relation('linkArtist', artist))), + + datelessArtistLinks: + query.datelessArtists + .map(artist => relation('linkArtist', artist)), + }), + + data: (query) => ({ + chunkDates: query.chunkDates, + chunkArtistContributions: query.chunkArtistContributions, + }), + + generate(data, relations, {language}) { return relations.page.slots({ - type: 'custom', - content: - html.tag('div', {class: 'content-columns'}, [ - html.tag('div', {class: 'column'}, [ - html.tag('h2', - language.$('listingPage.misc.trackContributors')), - - lists.tracks, - ]), - - html.tag('div', {class: 'column'}, [ - html.tag('h2', - language.$( - 'listingPage.misc.artContributors')), - - lists.artworks, - - lists.flashes && [ - html.tag('h2', - language.$('listingPage.misc.flashContributors')), - - lists.flashes, - ], - ]), - ]), + type: 'chunks', + + chunkTitles: + stitchArrays({ + albumLink: relations.chunkAlbumLinks, + flashLink: relations.chunkFlashLinks, + date: data.chunkDates, + }).map(({albumLink, flashLink, date}) => ({ + date: language.formatDate(date), + ...(albumLink + ? {stringsKey: 'album', album: albumLink} + : {stringsKey: 'flash', flash: flashLink}), + })) + .concat( + (empty(relations.datelessArtistLinks) + ? [] + : [{stringsKey: 'dateless'}])), + + chunkRows: + stitchArrays({ + artistLinks: relations.chunkArtistLinks, + contributions: data.chunkArtistContributions, + }).map(({artistLinks, contributions}) => + stitchArrays({ + artistLink: artistLinks, + contribution: contributions, + }).map(({artistLink, contribution}) => ({ + artist: artistLink, + stringsKey: + (contribution.has('track') && contribution.has('artwork') + ? 'tracksAndArt' + : contribution.has('track') + ? 'tracks' + : contribution.has('artwork') + ? 'art' + : null), + }))) + .concat( + (empty(relations.datelessArtistLinks) + ? [] + : [ + relations.datelessArtistLinks.map(artistLink => ({ + artist: artistLink, + })), + ])), }); }, }; diff --git a/src/strings-default.json b/src/strings-default.json index d170b86b..e893a5ce 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -389,9 +389,11 @@ "listingPage.listArtists.byLatest.title.short": "...by Latest Contribution", "listingPage.listArtists.byLatest.chunk.title.album": "{ALBUM} ({DATE})", "listingPage.listArtists.byLatest.chunk.title.flash": "{FLASH} ({DATE})", + "listingPage.listArtists.byLatest.chunk.title.dateless": "These artists' contributions aren't dated:", "listingPage.listArtists.byLatest.chunk.item": "{ARTIST}", - "listingPage.listArtists.byLatest.dateless.title": "These artists' contributions aren't dated:", - "listingPage.listArtists.byLatest.dateless.item": "{ARTIST}", + "listingPage.listArtists.byLatest.chunk.item.tracks": "{ARTIST} (tracks)", + "listingPage.listArtists.byLatest.chunk.item.tracksAndArt": "{ARTIST} (tracks, art)", + "listingPage.listArtists.byLatest.chunk.item.art": "{ARTIST} (art)", "listingPage.listGroups.byName.title": "Groups - by Name", "listingPage.listGroups.byName.title.short": "...by Name", "listingPage.listGroups.byName.item": "{GROUP} ({GALLERY})", -- cgit 1.3.0-6-gf8a5 From c4ef4ced62d659d217873c6c48dd8038dbf765af Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 31 Aug 2023 10:49:03 -0300 Subject: content: generateListingPage: show custom content above auto content This is towards enabling custom controls and/or accents on listings which are otherwise represented by rows or chunks. --- src/content/dependencies/generateListingPage.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 3c50bd59..f527f16f 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -129,6 +129,8 @@ export default { listings: language.formatUnitList(relations.seeAlsoLinks), })), + slots.content, + slots.type === 'rows' && html.tag(listTag, slots.rows.map(row => @@ -180,9 +182,6 @@ export default { formatListingString('chunk.item', row))))), ]), ]), - - slots.type === 'custom' && - slots.content, ], navLinkStyle: 'hierarchical', -- cgit 1.3.0-6-gf8a5 From c59545f5faafc826ff24ff779c20318ef14ae123 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 29 Oct 2023 09:27:23 -0300 Subject: content, client: generalize "random pages" listing to wiki dividing groups --- .../generateListRandomPageLinksGroupSection.js | 9 ++--- src/content/dependencies/listRandomPageLinks.js | 28 +++++--------- src/static/client2.js | 45 ++++++++++++++++++++-- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/content/dependencies/generateListRandomPageLinksGroupSection.js b/src/content/dependencies/generateListRandomPageLinksGroupSection.js index 2a684b19..74872724 100644 --- a/src/content/dependencies/generateListRandomPageLinksGroupSection.js +++ b/src/content/dependencies/generateListRandomPageLinksGroupSection.js @@ -23,10 +23,7 @@ export default { .map(() => relation('generateColorStyleVariables')), }), - data: (query, sprawl, group) => ({ - groupDirectory: - group.directory, - + data: (query) => ({ albumColors: query.albums .map(album => album.color), @@ -48,12 +45,12 @@ export default { randomAlbum: html.tag('a', - {href: '#', 'data-random': 'album-in-' + data.groupDirectory}, + {href: '#', 'data-random': 'album-in-group-dl'}, language.$('listingPage.other.randomPages.group.randomAlbum')), randomTrack: html.tag('a', - {href: '#', 'data-random': 'track-in-' + data.groupDirectory}, + {href: '#', 'data-random': 'track-in-group-dl'}, language.$('listingPage.other.randomPages.group.randomTrack')), })), diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index 43bf7dd5..57ecb044 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -6,19 +6,16 @@ export default { extraDependencies: ['html', 'language', 'wikiData'], - sprawl({groupData}) { - return {groupData}; + sprawl({wikiInfo}) { + return {wikiInfo}; }, query(sprawl, spec) { - const group = directory => - sprawl.groupData.find(group => group.directory === directory); - return { spec, - officialGroup: group('official'), - fandomGroup: group('fandom'), - beyondGroup: group('beyond'), + + groups: + sprawl.wikiInfo.divideTrackListsByGroups, }; }, @@ -26,14 +23,9 @@ export default { return { page: relation('generateListingPage', query.spec), - officialSection: - relation('generateListRandomPageLinksGroupSection', query.officialGroup), - - fandomSection: - relation('generateListRandomPageLinksGroupSection', query.fandomGroup), - - beyondSection: - relation('generateListRandomPageLinksGroupSection', query.beyondGroup), + groupSections: + query.groups + .map(group => relation('generateListRandomPageLinksGroupSection', group)), }; }, @@ -81,9 +73,7 @@ export default { language.$('listingPage.other.randomPages.misc.randomTrackWholeSite'))), ])), - relations.officialSection, - relations.fandomSection, - relations.beyondSection, + relations.groupSections, ]), ], }); diff --git a/src/static/client2.js b/src/static/client2.js index 758d91a6..3a5f9c37 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -149,6 +149,47 @@ function addRandomLinkListeners() { a.href = openAlbum(pick(albumData).directory); break; + case 'track': + a.href = openTrack(getRefDirectory(pick(tracks(albumData)))); + break; + + case 'album-in-group-dl': { + const albumLinks = + Array.from(a + .closest('dt') + .nextElementSibling + .querySelectorAll('li a')) + + const albumDirectories = + albumLinks.map(a => + getComputedStyle(a).getPropertyValue('--album-directory')); + + a.href = openAlbum(pick(albumDirectories)); + break; + } + + case 'track-in-group-dl': { + const albumLinks = + Array.from(a + .closest('dt') + .nextElementSibling + .querySelectorAll('li a')) + + const albumDirectories = + albumLinks.map(a => + getComputedStyle(a).getPropertyValue('--album-directory')); + + const filteredAlbumData = + albumData.filter(album => + albumDirectories.includes(album.directory)); + + a.href = openTrack(getRefDirectory(pick(tracks(filteredAlbumData)))); + break; + } + + /* Legacy links, for old versions * + * of generateListRandomPageLinksGroupSection */ + case 'album-in-official': a.href = openAlbum(pick(officialAlbumData).directory); break; @@ -161,9 +202,7 @@ function addRandomLinkListeners() { a.href = openAlbum(pick(beyondAlbumData).directory); break; - case 'track': - a.href = openTrack(getRefDirectory(pick(tracks(albumData)))); - break; + /* End legacy links */ case 'track-in-album': a.href = openTrack(getRefDirectory(pick(getAlbum(a).tracks))); -- cgit 1.3.0-6-gf8a5 From 777fbd3535f80fb8fb28d80a5dc53efe44c6cb82 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 29 Oct 2023 09:49:56 -0300 Subject: content: linkTemplate: append provided style to own style --- src/content/dependencies/linkTemplate.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/content/dependencies/linkTemplate.js b/src/content/dependencies/linkTemplate.js index d9af726c..a361a4e7 100644 --- a/src/content/dependencies/linkTemplate.js +++ b/src/content/dependencies/linkTemplate.js @@ -64,6 +64,14 @@ export default { style = `--primary-color: ${primary}; --dim-color: ${dim}`; } + if (slots.attributes?.style) { + if (style) { + style += '; ' + slots.attributes.style; + } else { + style = slots.attributes.style; + } + } + if (slots.tooltip) { title = slots.tooltip; } -- cgit 1.3.0-6-gf8a5 From d2903c800b6a005a447cc26f9431fe5fe4fb08b6 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 29 Oct 2023 09:51:01 -0300 Subject: content: adapt "random pages" to wikis without dividing groups --- .../generateListRandomPageLinksAlbumLink.js | 18 +++++++++ .../generateListRandomPageLinksAllAlbumsSection.js | 35 ++++++++++++++++ .../generateListRandomPageLinksGroupSection.js | 47 +++++----------------- src/content/dependencies/listRandomPageLinks.js | 40 +++++++++--------- src/strings-default.json | 7 ++-- 5 files changed, 89 insertions(+), 58 deletions(-) create mode 100644 src/content/dependencies/generateListRandomPageLinksAlbumLink.js create mode 100644 src/content/dependencies/generateListRandomPageLinksAllAlbumsSection.js diff --git a/src/content/dependencies/generateListRandomPageLinksAlbumLink.js b/src/content/dependencies/generateListRandomPageLinksAlbumLink.js new file mode 100644 index 00000000..b3560aca --- /dev/null +++ b/src/content/dependencies/generateListRandomPageLinksAlbumLink.js @@ -0,0 +1,18 @@ +export default { + contentDependencies: ['linkAlbum'], + + data: (album) => + ({directory: album.directory}), + + relations: (relation, album) => + ({albumLink: relation('linkAlbum', album)}), + + generate: (data, relations) => + relations.albumLink.slots({ + anchor: true, + attributes: { + 'data-random': 'track-in-album', + 'style': `--album-directory: ${data.directory}`, + }, + }), +}; diff --git a/src/content/dependencies/generateListRandomPageLinksAllAlbumsSection.js b/src/content/dependencies/generateListRandomPageLinksAllAlbumsSection.js new file mode 100644 index 00000000..e03252c9 --- /dev/null +++ b/src/content/dependencies/generateListRandomPageLinksAllAlbumsSection.js @@ -0,0 +1,35 @@ +import {sortChronologically} from '#wiki-data'; + +export default { + contentDependencies: ['generateListRandomPageLinksAlbumLink', 'linkGroup'], + extraDependencies: ['html', 'language', 'wikiData'], + + sprawl: ({albumData}) => ({albumData}), + + query: (sprawl) => ({ + albums: + sortChronologically(sprawl.albumData.slice()) + .filter(album => album.tracks.length > 1), + }), + + relations: (relation, query) => ({ + albumLinks: + query.albums + .map(album => relation('generateListRandomPageLinksAlbumLink', album)), + }), + + generate: (relations, {html, language}) => + html.tags([ + html.tag('dt', + language.$('listingPage.other.randomPages.fromAlbum')), + + html.tag('dd', + html.tag('ul', + relations.albumLinks + .map(albumLink => + html.tag('li', + language.$('listingPage.other.randomPages.album', { + album: albumLink, + }))))), + ]), +}; diff --git a/src/content/dependencies/generateListRandomPageLinksGroupSection.js b/src/content/dependencies/generateListRandomPageLinksGroupSection.js index 74872724..d05be8f7 100644 --- a/src/content/dependencies/generateListRandomPageLinksGroupSection.js +++ b/src/content/dependencies/generateListRandomPageLinksGroupSection.js @@ -1,8 +1,7 @@ -import {stitchArrays} from '#sugar'; import {sortChronologically} from '#wiki-data'; export default { - contentDependencies: ['generateColorStyleVariables', 'linkGroup'], + contentDependencies: ['generateListRandomPageLinksAlbumLink', 'linkGroup'], extraDependencies: ['html', 'language', 'wikiData'], sprawl: ({albumData}) => ({albumData}), @@ -18,61 +17,35 @@ export default { groupLink: relation('linkGroup', group), - albumColorVariables: + albumLinks: query.albums - .map(() => relation('generateColorStyleVariables')), + .map(album => relation('generateListRandomPageLinksAlbumLink', album)), }), - data: (query) => ({ - albumColors: - query.albums - .map(album => album.color), - - albumDirectories: - query.albums - .map(album => album.directory), - - albumNames: - query.albums - .map(album => album.name), - }), - - generate: (data, relations, {html, language}) => + generate: (relations, {html, language}) => html.tags([ html.tag('dt', - language.$('listingPage.other.randomPages.group', { + language.$('listingPage.other.randomPages.fromGroup', { group: relations.groupLink, randomAlbum: html.tag('a', {href: '#', 'data-random': 'album-in-group-dl'}, - language.$('listingPage.other.randomPages.group.randomAlbum')), + language.$('listingPage.other.randomPages.fromGroup.randomAlbum')), randomTrack: html.tag('a', {href: '#', 'data-random': 'track-in-group-dl'}, - language.$('listingPage.other.randomPages.group.randomTrack')), + language.$('listingPage.other.randomPages.fromGroup.randomTrack')), })), html.tag('dd', html.tag('ul', - stitchArrays({ - colorVariables: relations.albumColorVariables, - color: data.albumColors, - directory: data.albumDirectories, - name: data.albumNames, - }).map(({colorVariables, color, directory, name}) => + relations.albumLinks + .map(albumLink => html.tag('li', language.$('listingPage.other.randomPages.album', { - album: - html.tag('a', { - href: '#', - 'data-random': 'track-in-album', - style: - colorVariables.slot('color', color).content + - '; ' + - `--album-directory: ${directory}`, - }, name), + album: albumLink, }))))), ]), }; diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index 57ecb044..ce90a153 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -1,33 +1,36 @@ +import {empty} from '#sugar'; + export default { contentDependencies: [ 'generateListingPage', + 'generateListRandomPageLinksAllAlbumsSection', 'generateListRandomPageLinksGroupSection', ], extraDependencies: ['html', 'language', 'wikiData'], - sprawl({wikiInfo}) { - return {wikiInfo}; - }, + sprawl: ({wikiInfo}) => ({wikiInfo}), - query(sprawl, spec) { - return { - spec, + query: ({wikiInfo: {divideTrackListsByGroups: groups}}, spec) => ({ + spec, + groups, + divideByGroups: !empty(groups), + }), - groups: - sprawl.wikiInfo.divideTrackListsByGroups, - }; - }, + relations: (relation, query) => ({ + page: relation('generateListingPage', query.spec), - relations(relation, query) { - return { - page: relation('generateListingPage', query.spec), + allAlbumsSection: + (query.divideByGroups + ? null + : relation('generateListRandomPageLinksAllAlbumsSection')), - groupSections: - query.groups - .map(group => relation('generateListRandomPageLinksGroupSection', group)), - }; - }, + groupSections: + (query.divideByGroups + ? query.groups + .map(group => relation('generateListRandomPageLinksGroupSection', group)) + : null), + }), generate(relations, {html, language}) { return relations.page.slots({ @@ -73,6 +76,7 @@ export default { language.$('listingPage.other.randomPages.misc.randomTrackWholeSite'))), ])), + relations.allAlbumsSection, relations.groupSections, ]), ], diff --git a/src/strings-default.json b/src/strings-default.json index 6c841e72..81bf5d48 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -493,9 +493,10 @@ "listingPage.other.randomPages.misc.atLeastTwoContributions": "at least 2 contributions", "listingPage.other.randomPages.misc.randomAlbumWholeSite": "Random Album (whole site)", "listingPage.other.randomPages.misc.randomTrackWholeSite": "Random Track (whole site)", - "listingPage.other.randomPages.group": "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})", - "listingPage.other.randomPages.group.randomAlbum": "Random Album", - "listingPage.other.randomPages.group.randomTrack": "Random Track", + "listingPage.other.randomPages.fromAlbum": "From an album:", + "listingPage.other.randomPages.fromGroup": "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})", + "listingPage.other.randomPages.fromGroup.randomAlbum": "Random Album", + "listingPage.other.randomPages.fromGroup.randomTrack": "Random Track", "listingPage.other.randomPages.album": "{ALBUM}", "listingPage.misc.trackContributors": "Track Contributors", "listingPage.misc.artContributors": "Art Contributors", -- cgit 1.3.0-6-gf8a5 From e71230340181a3b7b38ff05ba23504b264f5b26c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 29 Oct 2023 10:00:52 -0300 Subject: content: listRandomPageLinks: update chooseLinkLine wording --- src/content/dependencies/listRandomPageLinks.js | 10 +++++++++- src/strings-default.json | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index ce90a153..599a82d3 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -37,7 +37,15 @@ export default { type: 'custom', content: [ html.tag('p', - language.$('listingPage.other.randomPages.chooseLinkLine')), + language.$('listingPage.other.randomPages.chooseLinkLine', { + fromPart: + (empty(relations.groupSections) + ? language.$('listingPage.other.randomPages.chooseLinkLine.fromPart.notDividedByGroups') + : language.$('listingPage.other.randomPages.chooseLinkLine.fromPart.dividedByGroups')), + + browserSupportPart: + language.$('listingPage.other.randomPages.chooseLinkLine.browserSupportPart'), + })), html.tag('p', {class: 'js-hide-once-data'}, diff --git a/src/strings-default.json b/src/strings-default.json index 81bf5d48..b0b68a57 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -485,7 +485,10 @@ "listingPage.other.allAdditionalFiles.file.withMultipleFiles": "{TITLE} ({FILES})", "listingPage.other.randomPages.title": "Random Pages", "listingPage.other.randomPages.title.short": "Random Pages", - "listingPage.other.randomPages.chooseLinkLine": "Choose a link to go to a random page in that category or album! If your browser doesn't support relatively modern JavaScript or you've disabled it, these links won't work - sorry.", + "listingPage.other.randomPages.chooseLinkLine": "{FROM_PART} {BROWSER_SUPPORT_PART}", + "listingPage.other.randomPages.chooseLinkLine.fromPart.dividedByGroups": "Choose a link to go to a random page in that group or album!", + "listingPage.other.randomPages.chooseLinkLine.fromPart.notDividedByGroups": "Choose a link to go to a random page in that album!", + "listingPage.other.randomPages.chooseLinkLine.browserSupportPart": "If your browser doesn't support relatively modern JavaScript or you've disabled it, these links won't work - sorry.", "listingPage.other.randomPages.dataLoadingLine": "(Data files are downloading in the background! Please wait for data to load.)", "listingPage.other.randomPages.dataLoadedLine": "(Data files have finished being downloaded. The links should work!)", "listingPage.other.randomPages.misc": "Miscellaneous:", -- cgit 1.3.0-6-gf8a5 From 0768953f9538f0bbd65835b0a4293e2ba438ce52 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 1 Nov 2023 09:00:55 -0300 Subject: data: tidy language loading code, add processLanguageSpec --- src/data/language.js | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index 09466907..34de8779 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -5,35 +5,43 @@ import he from 'he'; import T from '#things'; -export async function processLanguageFile(file) { - const contents = await readFile(file, 'utf-8'); - const json = JSON.parse(contents); +export function processLanguageSpec(spec) { + const { + 'meta.languageCode': code, + 'meta.languageName': name, + + 'meta.languageIntlCode': intlCode = null, + 'meta.hidden': hidden = false, + + ...strings + } = spec; - const code = json['meta.languageCode']; if (!code) { throw new Error(`Missing language code (file: ${file})`); } - delete json['meta.languageCode']; - const intlCode = json['meta.languageIntlCode'] ?? null; - delete json['meta.languageIntlCode']; - - const name = json['meta.languageName']; if (!name) { throw new Error(`Missing language name (${code})`); } - delete json['meta.languageName']; - - const hidden = json['meta.hidden'] ?? false; - delete json['meta.hidden']; const language = new T.Language(); - language.code = code; - language.intlCode = intlCode; - language.name = name; - language.hidden = hidden; - language.escapeHTML = (string) => + + Object.assign(language, { + code, + intlCode, + name, + hidden, + strings, + }); + + language.escapeHTML = string => he.encode(string, {useNamedReferences: true}); - language.strings = json; + return language; } + +export async function processLanguageFile(file) { + const contents = await readFile(file, 'utf-8'); + const spec = JSON.parse(contents); + return processLanguageSpec(spec); +} -- cgit 1.3.0-6-gf8a5 From cc3a6e32b957c60aa29027fa575e4b3ca0c05c64 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 1 Nov 2023 09:12:23 -0300 Subject: data: more language loading refactoring --- src/data/language.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index 34de8779..b71e55a2 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -1,10 +1,13 @@ import {readFile} from 'node:fs/promises'; -// It stands for "HTML Entities", apparently. Cursed. -import he from 'he'; +import chokidar from 'chokidar'; +import he from 'he'; // It stands for "HTML Entities", apparently. Cursed. +import {withAggregate} from '#sugar'; import T from '#things'; +const {Language} = T; + export function processLanguageSpec(spec) { const { 'meta.languageCode': code, @@ -16,23 +19,21 @@ export function processLanguageSpec(spec) { ...strings } = spec; - if (!code) { - throw new Error(`Missing language code (file: ${file})`); - } + withAggregate({message: `Errors validating language spec`}, ({push}) => { + if (!code) { + push(new Error(`Missing language code (file: ${file})`)); + } - if (!name) { - throw new Error(`Missing language name (${code})`); - } + if (!name) { + push(new Error(`Missing language name (${code})`)); + } + }); - const language = new T.Language(); + return {code, intlCode, name, hidden, strings}; +} - Object.assign(language, { - code, - intlCode, - name, - hidden, - strings, - }); +export function initializeLanguageObject() { + const language = new Language(); language.escapeHTML = string => he.encode(string, {useNamedReferences: true}); @@ -43,5 +44,10 @@ export function processLanguageSpec(spec) { export async function processLanguageFile(file) { const contents = await readFile(file, 'utf-8'); const spec = JSON.parse(contents); - return processLanguageSpec(spec); + + const language = initializeLanguageObject(); + const properties = processLanguageSpec(spec); + Object.assign(language, properties); + + return language; } -- cgit 1.3.0-6-gf8a5 From d497be7b5e1e4d9f9a8ca71de0a82def384467f8 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 17:42:35 -0400 Subject: data: language: basic watchLanguageFile implementation --- src/data/language.js | 108 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 8 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index b71e55a2..ec38cbde 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -1,10 +1,19 @@ +import EventEmitter from 'node:events'; import {readFile} from 'node:fs/promises'; +import path from 'node:path'; import chokidar from 'chokidar'; import he from 'he'; // It stands for "HTML Entities", apparently. Cursed. -import {withAggregate} from '#sugar'; import T from '#things'; +import {colors, logWarn} from '#cli'; + +import { + annotateError, + annotateErrorWithFile, + showAggregate, + withAggregate, +} from '#sugar'; const {Language} = T; @@ -21,17 +30,43 @@ export function processLanguageSpec(spec) { withAggregate({message: `Errors validating language spec`}, ({push}) => { if (!code) { - push(new Error(`Missing language code (file: ${file})`)); + push(new Error(`Missing language code`)); } if (!name) { - push(new Error(`Missing language name (${code})`)); + push(new Error(`Missing language name`)); } }); return {code, intlCode, name, hidden, strings}; } +async function processLanguageSpecFromFile(file) { + let contents, spec; + + try { + contents = await readFile(file, 'utf-8'); + } catch (caughtError) { + throw annotateError( + new Error(`Failed to read language file`, {cause: caughtError}), + error => annotateErrorWithFile(error, file)); + } + + try { + spec = JSON.parse(contents); + } catch (caughtError) { + throw annotateError( + new Error(`Failed to parse language file as valid JSON`, {cause: caughtError}), + error => annotateErrorWithFile(error, file)); + } + + try { + return processLanguageSpec(spec); + } catch (caughtError) { + throw annotateErrorWithFile(caughtError, file); + } +} + export function initializeLanguageObject() { const language = new Language(); @@ -42,12 +77,69 @@ export function initializeLanguageObject() { } export async function processLanguageFile(file) { - const contents = await readFile(file, 'utf-8'); - const spec = JSON.parse(contents); + const language = initializeLanguageObject(); + const properties = await processLanguageSpecFromFile(file); + return Object.assign(language, properties); +} + +export function watchLanguageFile(file, { + logging = true, +} = {}) { + const basename = path.basename(file); + const events = new EventEmitter(); const language = initializeLanguageObject(); - const properties = processLanguageSpec(spec); - Object.assign(language, properties); - return language; + let emittedReady = false; + let successfullyAppliedLanguage = false; + + Object.assign(events, {language, close}); + + const watcher = chokidar.watch(file); + watcher.on('change', () => handleFileUpdated()); + + setImmediate(handleFileUpdated); + + return events; + + async function close() { + return watcher.close(); + } + + function checkReadyConditions() { + if (emittedReady) return; + if (!successfullyAppliedLanguage) return; + + events.emit('ready'); + emittedReady = true; + } + + async function handleFileUpdated() { + let properties; + + try { + properties = await processLanguageSpecFromFile(file); + } catch (error) { + if (logging) { + if (successfullyAppliedLanguage) { + logWarn`Failed to load language ${basename} - using existing version`; + } else { + logWarn`Failed to load language ${basename} - no prior version loaded`; + } + showAggregate(error, {showTraces: false}); + } + return; + } + + Object.assign(language, properties); + successfullyAppliedLanguage = true; + + if (logging && emittedReady) { + const timestamp = new Date().toLocaleString('en-US', {timeStyle: 'medium'}); + console.log(colors.green(`[${timestamp}] Updated language ${language.name} (${language.code})`)); + } + + events.emit('update'); + checkReadyConditions(); + } } -- cgit 1.3.0-6-gf8a5 From 39a2f042cd6275f06977be6fe9335bd76fb94222 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 17:53:58 -0400 Subject: upd8: fix not defaulting to internal language properly --- src/upd8.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/upd8.js b/src/upd8.js index 408ad884..b1340989 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -1162,7 +1162,10 @@ async function main() { }); const customDefaultLanguage = - languages[wikiData.wikiInfo.defaultLanguage ?? internalDefaultLanguage.code]; + (wikiData.wikiInfo.defaultLanguage + ? languages[wikiData.wikiInfo.defaultLanguage] + : null); + let finalDefaultLanguage; if (customDefaultLanguage) { -- cgit 1.3.0-6-gf8a5 From 06949e1d20d38d38eb05999ca236f2c7d150691e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 17:55:25 -0400 Subject: upd8: basic watchLanguageFile integration for internal language --- src/data/language.js | 3 +++ src/upd8.js | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index ec38cbde..5ab3936e 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -120,6 +120,8 @@ export function watchLanguageFile(file, { try { properties = await processLanguageSpecFromFile(file); } catch (error) { + events.emit('error', error); + if (logging) { if (successfullyAppliedLanguage) { logWarn`Failed to load language ${basename} - using existing version`; @@ -128,6 +130,7 @@ export function watchLanguageFile(file, { } showAggregate(error, {showTraces: false}); } + return; } diff --git a/src/upd8.js b/src/upd8.js index b1340989..9714d166 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -39,7 +39,7 @@ import {fileURLToPath} from 'node:url'; import wrap from 'word-wrap'; import {displayCompositeCacheAnalysis} from '#composite'; -import {processLanguageFile} from '#language'; +import {processLanguageFile, watchLanguageFile} from '#language'; import {isMain, traverse} from '#node-utils'; import bootRepl from '#repl'; import {empty, showAggregate, withEntries} from '#sugar'; @@ -1086,17 +1086,34 @@ async function main() { let internalDefaultLanguage; - try { - internalDefaultLanguage = - await processLanguageFile(path.join(__dirname, DEFAULT_STRINGS_FILE)); + const internalDefaultLanguageWatcher = + watchLanguageFile(path.join(__dirname, DEFAULT_STRINGS_FILE)); - Object.assign(stepStatusSummary.loadInternalDefaultLanguage, { - status: STATUS_DONE_CLEAN, - timeEnd: Date.now(), + try { + await new Promise((resolve, reject) => { + const watcher = internalDefaultLanguageWatcher; + + const onReady = () => { + watcher.removeListener('ready', onReady); + watcher.removeListener('error', onError); + resolve(); + }; + + const onError = error => { + watcher.removeListener('ready', onReady); + watcher.removeListener('error', onError); + watcher.close(); + reject(error); + }; + + watcher.on('ready', onReady); + watcher.on('error', onError); }); - } catch (error) { - console.error(error); + internalDefaultLanguage = internalDefaultLanguageWatcher.language; + } catch (_error) { + // No need to display the error here - it's already printed by + // watchLanguageFile. logError`There was an error reading the internal language file.`; fileIssue(); @@ -1109,6 +1126,14 @@ async function main() { return false; } + // Bypass node.js special-case handling for uncaught error events + internalDefaultLanguageWatcher.on('error', () => {}); + + Object.assign(stepStatusSummary.loadInternalDefaultLanguage, { + status: STATUS_DONE_CLEAN, + timeEnd: Date.now(), + }); + let languages; if (langPath) { -- cgit 1.3.0-6-gf8a5 From 9f3a1f476752059681fbe21f8a1f7bf11dd73c9b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 18:43:49 -0400 Subject: data: language: nicer language labelling for successive errors --- src/data/language.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index 5ab3936e..99eaa58f 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -17,7 +17,7 @@ import { const {Language} = T; -export function processLanguageSpec(spec) { +export function processLanguageSpec(spec, {existingCode = null}) { const { 'meta.languageCode': code, 'meta.languageName': name, @@ -36,12 +36,16 @@ export function processLanguageSpec(spec) { if (!name) { push(new Error(`Missing language name`)); } + + if (code && existingCode && code !== existingCode) { + push(new Error(`Language code (${code}) doesn't match previous value\n(You'll have to reload hsmusic to load this)`)); + } }); return {code, intlCode, name, hidden, strings}; } -async function processLanguageSpecFromFile(file) { +async function processLanguageSpecFromFile(file, processLanguageSpecOpts) { let contents, spec; try { @@ -61,7 +65,7 @@ async function processLanguageSpecFromFile(file) { } try { - return processLanguageSpec(spec); + return processLanguageSpec(spec, processLanguageSpecOpts); } catch (caughtError) { throw annotateErrorWithFile(caughtError, file); } @@ -118,15 +122,25 @@ export function watchLanguageFile(file, { let properties; try { - properties = await processLanguageSpecFromFile(file); + properties = await processLanguageSpecFromFile(file, { + existingCode: + (successfullyAppliedLanguage + ? language.code + : null), + }); } catch (error) { events.emit('error', error); if (logging) { + const label = + (successfullyAppliedLanguage + ? `${language.name} (${language.code})` + : basename); + if (successfullyAppliedLanguage) { - logWarn`Failed to load language ${basename} - using existing version`; + logWarn`Failed to load language ${label} - using existing version`; } else { - logWarn`Failed to load language ${basename} - no prior version loaded`; + logWarn`Failed to load language ${label} - no prior version loaded`; } showAggregate(error, {showTraces: false}); } -- cgit 1.3.0-6-gf8a5 From 8197804433694865567c826ec0031e8ce73cbcd4 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 18:45:16 -0400 Subject: upd8: complete integration for reloading custom languages --- src/upd8.js | 160 +++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 110 insertions(+), 50 deletions(-) diff --git a/src/upd8.js b/src/upd8.js index 9714d166..c011b660 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -1134,6 +1134,7 @@ async function main() { timeEnd: Date.now(), }); + let customLanguageWatchers; let languages; if (langPath) { @@ -1147,31 +1148,77 @@ async function main() { pathStyle: 'device', }); - let results; + customLanguageWatchers = + languageDataFiles.map(file => { + const watcher = watchLanguageFile(file); - // TODO: Aggregate errors (with Promise.allSettled). - try { - results = - await progressPromiseAll(`Reading & processing language files.`, - languageDataFiles.map((file) => processLanguageFile(file))); - } catch (error) { - console.error(error); + // Bypass node.js special-case handling for uncaught error events + watcher.on('error', () => {}); - logError`Failed to load language files. Please investigate these, or don't provide`; - logError`--lang-path (or HSMUSIC_LANG) and build again.`; - - Object.assign(stepStatusSummary.loadLanguageFiles, { - status: STATUS_FATAL_ERROR, - annotation: `see log for details`, - timeEnd: Date.now(), + return watcher; }); - return false; + const waitingOnWatchers = new Set(customLanguageWatchers); + + const initialResults = + await Promise.allSettled( + customLanguageWatchers.map(watcher => + new Promise((resolve, reject) => { + const onReady = () => { + watcher.removeListener('ready', onReady); + watcher.removeListener('error', onError); + waitingOnWatchers.delete(watcher); + resolve(); + }; + + const onError = error => { + watcher.removeListener('ready', onReady); + watcher.removeListener('error', onError); + reject(error); + }; + + watcher.on('ready', onReady); + watcher.on('error', onError); + }))); + + if (initialResults.some(({status}) => status === 'rejected')) { + logWarn`There were errors loading custom languages from the language path`; + logWarn`provided: ${langPath}`; + + if (noInput) { + logError`Failed to load language files. Please investigate these, or don't provide`; + logError`--lang-path (or HSMUSIC_LANG) and build again.`; + + Object.assign(stepStatusSummary.loadLanguageFiles, { + status: STATUS_FATAL_ERROR, + annotation: `see log for details`, + timeEnd: Date.now(), + }); + + return false; + } + + logWarn`The build should start automatically if you investigate these.`; + logWarn`Or, exit by pressing ^C here (control+C) and run again without`; + logWarn`providing ${'--lang-path'} (or ${'HSMUSIC_LANG'}) to build without custom`; + logWarn`languages.`; + + await new Promise(resolve => { + for (const watcher of waitingOnWatchers) { + watcher.once('ready', () => { + waitingOnWatchers.remove(watcher); + if (empty(waitingOnWatchers)) { + resolve(); + } + }); + } + }); } languages = Object.fromEntries( - results.map((language) => [language.code, language])); + customLanguageWatchers + .map(watcher => [watcher.language.code, watcher.language])); Object.assign(stepStatusSummary.loadLanguageFiles, { status: STATUS_DONE_CLEAN, @@ -1192,54 +1239,67 @@ async function main() { : null); let finalDefaultLanguage; + let finalDefaultLanguageWatcher; + let finalDefaultLanguageAnnotation; - if (customDefaultLanguage) { - logInfo`Applying new default strings from custom ${customDefaultLanguage.code} language file.`; - customDefaultLanguage.inheritedStrings = internalDefaultLanguage.strings; - finalDefaultLanguage = customDefaultLanguage; + if (wikiData.wikiInfo.defaultLanguage) { + const customDefaultLanguage = languages[wikiData.wikiInfo.defaultLanguage]; - Object.assign(stepStatusSummary.initializeDefaultLanguage, { - status: STATUS_DONE_CLEAN, - annotation: `using wiki-specified custom default language`, - timeEnd: Date.now(), - }); - } else if (wikiData.wikiInfo.defaultLanguage) { - logError`Wiki info file specified default language is ${wikiData.wikiInfo.defaultLanguage}, but no such language file exists!`; - if (langPath) { - logError`Check if an appropriate file exists in ${langPath}?`; - } else { - logError`Be sure to specify ${'--lang-path'} or ${'HSMUSIC_LANG'} with the path to language files.`; + if (!customDefaultLanguage) { + logError`Wiki info file specified default language is ${wikiData.wikiInfo.defaultLanguage}, but no such language file exists!`; + if (langPath) { + logError`Check if an appropriate file exists in ${langPath}?`; + } else { + logError`Be sure to specify ${'--lang-path'} or ${'HSMUSIC_LANG'} with the path to language files.`; + } + + Object.assign(stepStatusSummary.initializeDefaultLanguage, { + status: STATUS_FATAL_ERROR, + annotation: `wiki specifies default language whose file is not available`, + timeEnd: Date.now(), + }); + + return false; } - Object.assign(stepStatusSummary.initializeDefaultLanguage, { - status: STATUS_FATAL_ERROR, - annotation: `wiki specifies default language whose file is not available`, - timeEnd: Date.now(), - }); + customDefaultLanguage.inheritedStrings = internalDefaultLanguage.strings; - return false; + logInfo`Applying new default strings from custom ${customDefaultLanguage.code} language file.`; + + finalDefaultLanguage = customDefaultLanguage; + finalDefaultLanguageWatcher = + customLanguageWatchers.find(({language}) => language === customDefaultLanguage); + finalDefaultLanguageAnnotation = `using wiki-specified custom default language`; } else { languages[internalDefaultLanguage.code] = internalDefaultLanguage; - finalDefaultLanguage = internalDefaultLanguage; - stepStatusSummary.initializeDefaultLanguage.status = STATUS_DONE_CLEAN; - Object.assign(stepStatusSummary.initializeDefaultLanguage, { - status: STATUS_DONE_CLEAN, - annotation: `no custom default language specified`, - timeEnd: Date.now(), - }); + finalDefaultLanguage = internalDefaultLanguage; + finalDefaultLanguageWatcher = internalDefaultLanguageWatcher; + finalDefaultLanguageAnnotation = `no custom default language specified`; } - for (const language of Object.values(languages)) { - if (language === finalDefaultLanguage) { - continue; + const inheritStringsFromDefaultLanguage = () => { + const {strings: inheritedStrings} = finalDefaultLanguage; + for (const language of Object.values(languages)) { + if (language === finalDefaultLanguage) continue; + Object.assign(language, {inheritedStrings}); } + }; - language.inheritedStrings = finalDefaultLanguage.strings; - } + inheritStringsFromDefaultLanguage(); + + finalDefaultLanguageWatcher.on('update', () => { + inheritStringsFromDefaultLanguage(); + }); logInfo`Loaded language strings: ${Object.keys(languages).join(', ')}`; + Object.assign(stepStatusSummary.initializeDefaultLanguage, { + status: STATUS_DONE_CLEAN, + annotation: finalDefaultLanguageAnnotation, + timeEnd: Date.now(), + }); + const urls = generateURLs(urlSpec); Object.assign(stepStatusSummary.verifyImagePaths, { -- cgit 1.3.0-6-gf8a5 From bd3affb31b6b2e5cb0667c550bcdbde8af51a392 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 19:06:27 -0400 Subject: data: language: basic support for loading language from YAML --- src/data/language.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index 99eaa58f..aed16057 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -4,6 +4,7 @@ import path from 'node:path'; import chokidar from 'chokidar'; import he from 'he'; // It stands for "HTML Entities", apparently. Cursed. +import yaml from 'js-yaml'; import T from '#things'; import {colors, logWarn} from '#cli'; @@ -56,11 +57,18 @@ async function processLanguageSpecFromFile(file, processLanguageSpecOpts) { error => annotateErrorWithFile(error, file)); } + let parseLanguage; try { - spec = JSON.parse(contents); + if (path.extname(file) === '.yaml') { + parseLanguage = 'YAML'; + spec = yaml.load(contents); + } else { + parseLanguage = 'JSON'; + spec = JSON.parse(contents); + } } catch (caughtError) { throw annotateError( - new Error(`Failed to parse language file as valid JSON`, {cause: caughtError}), + new Error(`Failed to parse language file as valid ${parseLanguage}`, {cause: caughtError}), error => annotateErrorWithFile(error, file)); } -- cgit 1.3.0-6-gf8a5 From fade91f5f20a68ea8ffad6f151ff9c9bd8b19736 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 19:06:50 -0400 Subject: upd8: basic port strings-default.json -> strings-default.yaml --- src/strings-default.json | 513 ----------------------------------------- src/strings-default.yaml | 585 +++++++++++++++++++++++++++++++++++++++++++++++ src/upd8.js | 2 +- 3 files changed, 586 insertions(+), 514 deletions(-) delete mode 100644 src/strings-default.json create mode 100644 src/strings-default.yaml diff --git a/src/strings-default.json b/src/strings-default.json deleted file mode 100644 index b6471bdf..00000000 --- a/src/strings-default.json +++ /dev/null @@ -1,513 +0,0 @@ -{ - "meta.languageCode": "en", - "meta.languageName": "English", - "count.tracks": "{TRACKS}", - "count.tracks.withUnit.zero": "", - "count.tracks.withUnit.one": "{TRACKS} track", - "count.tracks.withUnit.two": "", - "count.tracks.withUnit.few": "", - "count.tracks.withUnit.many": "", - "count.tracks.withUnit.other": "{TRACKS} tracks", - "count.additionalFiles": "{FILES}", - "count.additionalFiles.withUnit.zero": "", - "count.additionalFiles.withUnit.one": "{FILES} file", - "count.additionalFiles.withUnit.two": "", - "count.additionalFiles.withUnit.few": "", - "count.additionalFiles.withUnit.many": "", - "count.additionalFiles.withUnit.other": "{FILES} files", - "count.albums": "{ALBUMS}", - "count.albums.withUnit.zero": "", - "count.albums.withUnit.one": "{ALBUMS} album", - "count.albums.withUnit.two": "", - "count.albums.withUnit.few": "", - "count.albums.withUnit.many": "", - "count.albums.withUnit.other": "{ALBUMS} albums", - "count.artworks": "{ARTWORKS}", - "count.artworks.withUnit.zero": "", - "count.artworks.withUnit.one": "{ARTWORKS} artwork", - "count.artworks.withUnit.two": "", - "count.artworks.withUnit.few": "", - "count.artworks.withUnit.many": "", - "count.artworks.withUnit.other": "{ARTWORKS} artworks", - "count.commentaryEntries": "{ENTRIES}", - "count.commentaryEntries.withUnit.zero": "", - "count.commentaryEntries.withUnit.one": "{ENTRIES} entry", - "count.commentaryEntries.withUnit.two": "", - "count.commentaryEntries.withUnit.few": "", - "count.commentaryEntries.withUnit.many": "", - "count.commentaryEntries.withUnit.other": "{ENTRIES} entries", - "count.contributions": "{CONTRIBUTIONS}", - "count.contributions.withUnit.zero": "", - "count.contributions.withUnit.one": "{CONTRIBUTIONS} contribution", - "count.contributions.withUnit.two": "", - "count.contributions.withUnit.few": "", - "count.contributions.withUnit.many": "", - "count.contributions.withUnit.other": "{CONTRIBUTIONS} contributions", - "count.coverArts": "{COVER_ARTS}", - "count.coverArts.withUnit.zero": "", - "count.coverArts.withUnit.one": "{COVER_ARTS} cover art", - "count.coverArts.withUnit.two": "", - "count.coverArts.withUnit.few": "", - "count.coverArts.withUnit.many": "", - "count.coverArts.withUnit.other": "{COVER_ARTS} cover arts", - "count.flashes": "{FLASHES}", - "count.flashes.withUnit.zero": "", - "count.flashes.withUnit.one": "{FLASHES} flashes & games", - "count.flashes.withUnit.two": "", - "count.flashes.withUnit.few": "", - "count.flashes.withUnit.many": "", - "count.flashes.withUnit.other": "{FLASHES} flashes & games", - "count.timesReferenced": "{TIMES_REFERENCED}", - "count.timesReferenced.withUnit.zero": "", - "count.timesReferenced.withUnit.one": "{TIMES_REFERENCED} time referenced", - "count.timesReferenced.withUnit.two": "", - "count.timesReferenced.withUnit.few": "", - "count.timesReferenced.withUnit.many": "", - "count.timesReferenced.withUnit.other": "{TIMES_REFERENCED} times referenced", - "count.words": "{WORDS}", - "count.words.thousand": "{WORDS}k", - "count.words.withUnit.zero": "", - "count.words.withUnit.one": "{WORDS} word", - "count.words.withUnit.two": "", - "count.words.withUnit.few": "", - "count.words.withUnit.many": "", - "count.words.withUnit.other": "{WORDS} words", - "count.timesUsed": "{TIMES_USED}", - "count.timesUsed.withUnit.zero": "", - "count.timesUsed.withUnit.one": "used {TIMES_USED} time", - "count.timesUsed.withUnit.two": "", - "count.timesUsed.withUnit.few": "", - "count.timesUsed.withUnit.many": "", - "count.timesUsed.withUnit.other": "used {TIMES_USED} times", - "count.index.zero": "", - "count.index.one": "{INDEX}st", - "count.index.two": "{INDEX}nd", - "count.index.few": "{INDEX}rd", - "count.index.many": "", - "count.index.other": "{INDEX}th", - "count.duration.hours": "{HOURS}:{MINUTES}:{SECONDS}", - "count.duration.hours.withUnit": "{HOURS}:{MINUTES}:{SECONDS} hours", - "count.duration.minutes": "{MINUTES}:{SECONDS}", - "count.duration.minutes.withUnit": "{MINUTES}:{SECONDS} minutes", - "count.duration.approximate": "~{DURATION}", - "count.duration.missing": "_:__", - "count.fileSize.terabytes": "{TERABYTES} TB", - "count.fileSize.gigabytes": "{GIGABYTES} GB", - "count.fileSize.megabytes": "{MEGABYTES} MB", - "count.fileSize.kilobytes": "{KILOBYTES} kB", - "count.fileSize.bytes": "{BYTES} bytes", - "releaseInfo.by": "By {ARTISTS}.", - "releaseInfo.from": "From {ALBUM}.", - "releaseInfo.coverArtBy": "Cover art by {ARTISTS}.", - "releaseInfo.wallpaperArtBy": "Wallpaper art by {ARTISTS}.", - "releaseInfo.bannerArtBy": "Banner art by {ARTISTS}.", - "releaseInfo.released": "Released {DATE}.", - "releaseInfo.artReleased": "Art released {DATE}.", - "releaseInfo.addedToWiki": "Added to wiki {DATE}.", - "releaseInfo.duration": "Duration: {DURATION}.", - "releaseInfo.viewCommentary": "View {LINK}!", - "releaseInfo.viewCommentary.link": "commentary page", - "releaseInfo.viewGallery": "View {LINK}!", - "releaseInfo.viewGallery.link": "gallery page", - "releaseInfo.viewGalleryOrCommentary": "View {GALLERY} or {COMMENTARY}!", - "releaseInfo.viewGalleryOrCommentary.gallery": "gallery page", - "releaseInfo.viewGalleryOrCommentary.commentary": "commentary page", - "releaseInfo.viewOriginalFile": "View {LINK}.", - "releaseInfo.viewOriginalFile.withSize": "View {LINK} ({SIZE}).", - "releaseInfo.viewOriginalFile.link": "original file", - "releaseInfo.viewOriginalFile.sizeWarning": "(Heads up! If you're on a mobile plan, this is a large download.)", - "releaseInfo.listenOn": "Listen on {LINKS}.", - "releaseInfo.listenOn.noLinks": "This wiki doesn't have any listening links for {NAME}.", - "releaseInfo.visitOn": "Visit on {LINKS}.", - "releaseInfo.playOn": "Play on {LINKS}.", - "releaseInfo.readCommentary": "Read {LINK}.", - "releaseInfo.readCommentary.link": "artist commentary", - "releaseInfo.alsoReleasedAs": "Also released as:", - "releaseInfo.alsoReleasedAs.item": "{TRACK} (on {ALBUM})", - "releaseInfo.contributors": "Contributors:", - "releaseInfo.tracksReferenced": "Tracks that {TRACK} references:", - "releaseInfo.tracksThatReference": "Tracks that reference {TRACK}:", - "releaseInfo.tracksSampled": "Tracks that {TRACK} samples:", - "releaseInfo.tracksThatSample": "Tracks that sample {TRACK}:", - "releaseInfo.flashesThatFeature": "Flashes & games that feature {TRACK}:", - "releaseInfo.flashesThatFeature.item": "{FLASH}", - "releaseInfo.flashesThatFeature.item.asDifferentRelease": "{FLASH} (as {TRACK})", - "releaseInfo.tracksFeatured": "Tracks that {FLASH} features:", - "releaseInfo.lyrics": "Lyrics:", - "releaseInfo.artistCommentary": "Artist commentary:", - "releaseInfo.artistCommentary.seeOriginalRelease": "See {ORIGINAL}!", - "releaseInfo.artTags": "Tags:", - "releaseInfo.artTags.inline": "Tags: {TAGS}", - "releaseInfo.additionalFiles.shortcut": "View {ANCHOR_LINK}: {TITLES}", - "releaseInfo.additionalFiles.shortcut.anchorLink": "additional files", - "releaseInfo.additionalFiles.heading": "View or download {ADDITIONAL_FILES}:", - "releaseInfo.additionalFiles.entry": "{TITLE}", - "releaseInfo.additionalFiles.entry.withDescription": "{TITLE}: {DESCRIPTION}", - "releaseInfo.additionalFiles.file": "{FILE}", - "releaseInfo.additionalFiles.file.withSize": "{FILE} ({SIZE})", - "releaseInfo.sheetMusicFiles.shortcut": "Download {LINK}.", - "releaseInfo.sheetMusicFiles.shortcut.link": "sheet music files", - "releaseInfo.sheetMusicFiles.heading": "Print or download sheet music files:", - "releaseInfo.midiProjectFiles.shortcut": "Download {LINK}.", - "releaseInfo.midiProjectFiles.shortcut.link": "MIDI/project files", - "releaseInfo.midiProjectFiles.heading": "Download MIDI/project files:", - "releaseInfo.note": "Context notes:", - "trackList.section.withDuration": "{SECTION} ({DURATION}):", - "trackList.group": "From {GROUP}:", - "trackList.group.fromOther": "From somewhere else:", - "trackList.item.withDuration": "({DURATION}) {TRACK}", - "trackList.item.withDuration.withArtists": "({DURATION}) {TRACK} {BY}", - "trackList.item.withArtists": "{TRACK} {BY}", - "trackList.item.withArtists.by": "by {ARTISTS}", - "trackList.item.rerelease": "{TRACK} (re-release)", - "misc.alt.albumCover": "album cover", - "misc.alt.albumBanner": "album banner", - "misc.alt.trackCover": "track cover", - "misc.alt.artistAvatar": "artist avatar", - "misc.alt.flashArt": "flash art", - "misc.artistLink": "{ARTIST}", - "misc.artistLink.withContribution": "{ARTIST} ({CONTRIB})", - "misc.artistLink.withExternalLinks": "{ARTIST} ({LINKS})", - "misc.artistLink.withContribution.withExternalLinks": "{ARTIST} ({CONTRIB}) ({LINKS})", - "misc.chronology.seeArtistPages": "(See artist pages for chronology info!)", - "misc.chronology.heading.coverArt": "{INDEX} cover art by {ARTIST}", - "misc.chronology.heading.flash": "{INDEX} flash/game by {ARTIST}", - "misc.chronology.heading.track": "{INDEX} track by {ARTIST}", - "misc.chronology.withNavigation": "{HEADING} ({NAVIGATION})", - "misc.external.domain": "External ({DOMAIN})", - "misc.external.local": "Wiki Archive (local upload)", - "misc.external.bandcamp": "Bandcamp", - "misc.external.bandcamp.domain": "Bandcamp ({DOMAIN})", - "misc.external.deviantart": "DeviantArt", - "misc.external.instagram": "Instagram", - "misc.external.mastodon": "Mastodon", - "misc.external.mastodon.domain": "Mastodon ({DOMAIN})", - "misc.external.newgrounds": "Newgrounds", - "misc.external.patreon": "Patreon", - "misc.external.poetryFoundation": "Poetry Foundation", - "misc.external.soundcloud": "SoundCloud", - "misc.external.spotify": "Spotify", - "misc.external.tumblr": "Tumblr", - "misc.external.twitter": "Twitter", - "misc.external.wikipedia": "Wikipedia", - "misc.external.youtube": "YouTube", - "misc.external.youtube.playlist": "YouTube (playlist)", - "misc.external.youtube.fullAlbum": "YouTube (full album)", - "misc.external.flash.bgreco": "{LINK} (HQ Audio)", - "misc.external.flash.homestuck.page": "{LINK} (page {PAGE})", - "misc.external.flash.homestuck.secret": "{LINK} (secret page)", - "misc.external.flash.youtube": "{LINK} (on any device)", - "misc.missingImage": "(This image file is missing)", - "misc.missingLinkContent": "(Missing link content)", - "misc.nav.previous": "Previous", - "misc.nav.next": "Next", - "misc.nav.info": "Info", - "misc.nav.gallery": "Gallery", - "misc.pageTitle": "{TITLE}", - "misc.pageTitle.withWikiName": "{TITLE} | {WIKI_NAME}", - "misc.skippers.skipTo": "Skip to:", - "misc.skippers.content": "Content", - "misc.skippers.sidebar": "Sidebar", - "misc.skippers.sidebar.left": "Sidebar (left)", - "misc.skippers.sidebar.right": "Sidebar (right)", - "misc.skippers.header": "Header", - "misc.skippers.footer": "Footer", - "misc.skippers.tracks": "Tracks", - "misc.skippers.art": "Artworks", - "misc.skippers.flashes": "Flashes & Games", - "misc.skippers.contributors": "Contributors", - "misc.skippers.references": "References...", - "misc.skippers.referencedBy": "Referenced by...", - "misc.skippers.samples": "Samples...", - "misc.skippers.sampledBy": "Sampled by...", - "misc.skippers.features": "Features...", - "misc.skippers.featuredIn": "Featured in...", - "misc.skippers.lyrics": "Lyrics", - "misc.skippers.sheetMusicFiles": "Sheet music files", - "misc.skippers.midiProjectFiles": "MIDI/project files", - "misc.skippers.additionalFiles": "Additional files", - "misc.skippers.commentary": "Commentary", - "misc.skippers.artistCommentary": "Commentary", - "misc.socialEmbed.heading": "{WIKI_NAME} | {HEADING}", - "misc.jumpTo": "Jump to:", - "misc.jumpTo.withLinks": "Jump to: {LINKS}.", - "misc.contentWarnings": "cw: {WARNINGS}", - "misc.contentWarnings.reveal": "click to show", - "misc.albumGrid.details": "({TRACKS}, {TIME})", - "misc.albumGrid.details.coverArtists": "(Illust. {ARTISTS})", - "misc.albumGrid.details.otherCoverArtists": "(With {ARTISTS})", - "misc.albumGrid.noCoverArt": "{ALBUM}", - "misc.albumGalleryGrid.noCoverArt": "{NAME}", - "misc.uiLanguage": "UI Language: {LANGUAGES}", - "homepage.title": "{TITLE}", - "homepage.news.title": "News", - "homepage.news.entry.viewRest": "(View rest of entry!)", - "albumSidebar.trackList.fallbackSectionName": "Track list", - "albumSidebar.trackList.group": "{GROUP}", - "albumSidebar.trackList.group.withRange": "{GROUP} ({RANGE})", - "albumSidebar.trackList.item": "{TRACK}", - "albumSidebar.groupBox.title": "{GROUP}", - "albumSidebar.groupBox.next": "Next: {ALBUM}", - "albumSidebar.groupBox.previous": "Previous: {ALBUM}", - "albumPage.title": "{ALBUM}", - "albumPage.nav.album": "{ALBUM}", - "albumPage.nav.randomTrack": "Random Track", - "albumPage.nav.gallery": "Gallery", - "albumPage.nav.commentary": "Commentary", - "albumPage.socialEmbed.heading": "{GROUP}", - "albumPage.socialEmbed.title": "{ALBUM}", - "albumPage.socialEmbed.body.withDuration": "{DURATION}.", - "albumPage.socialEmbed.body.withTracks": "{TRACKS}.", - "albumPage.socialEmbed.body.withReleaseDate": "Released {DATE}.", - "albumPage.socialEmbed.body.withDuration.withTracks": "{DURATION}, {TRACKS}.", - "albumPage.socialEmbed.body.withDuration.withReleaseDate": "{DURATION}. Released {DATE}.", - "albumPage.socialEmbed.body.withTracks.withReleaseDate": "{TRACKS}. Released {DATE}.", - "albumPage.socialEmbed.body.withDuration.withTracks.withReleaseDate": "{DURATION}, {TRACKS}. Released {DATE}.", - "albumGalleryPage.title": "{ALBUM} - Gallery", - "albumGalleryPage.statsLine": "{TRACKS} totaling {DURATION}.", - "albumGalleryPage.statsLine.withDate": "{TRACKS} totaling {DURATION}. Released {DATE}.", - "albumGalleryPage.coverArtistsLine": "All track artwork by {ARTISTS}.", - "albumGalleryPage.noTrackArtworksLine": "This album doesn't have any track artwork.", - "albumCommentaryPage.title": "{ALBUM} - Commentary", - "albumCommentaryPage.infoLine": "{WORDS} across {ENTRIES}.", - "albumCommentaryPage.nav.album": "Album: {ALBUM}", - "albumCommentaryPage.entry.title.albumCommentary": "Album commentary", - "albumCommentaryPage.entry.title.trackCommentary": "{TRACK}", - "artistPage.title": "{ARTIST}", - "artistPage.creditList.album": "{ALBUM}", - "artistPage.creditList.album.withDate": "{ALBUM} ({DATE})", - "artistPage.creditList.album.withDuration": "{ALBUM} ({DURATION})", - "artistPage.creditList.album.withDate.withDuration": "{ALBUM} ({DATE}; {DURATION})", - "artistPage.creditList.flashAct": "{ACT}", - "artistPage.creditList.flashAct.withDate": "{ACT} ({DATE})", - "artistPage.creditList.flashAct.withDateRange": "{ACT} ({DATE_RANGE})", - "artistPage.creditList.entry.track": "{TRACK}", - "artistPage.creditList.entry.track.withDuration": "({DURATION}) {TRACK}", - "artistPage.creditList.entry.album.coverArt": "(cover art)", - "artistPage.creditList.entry.album.wallpaperArt": "(wallpaper art)", - "artistPage.creditList.entry.album.bannerArt": "(banner art)", - "artistPage.creditList.entry.album.commentary": "(album commentary)", - "artistPage.creditList.entry.flash": "{FLASH}", - "artistPage.creditList.entry.rerelease": "{ENTRY} (re-release)", - "artistPage.creditList.entry.withContribution": "{ENTRY} ({CONTRIBUTION})", - "artistPage.creditList.entry.withArtists": "{ENTRY} (with {ARTISTS})", - "artistPage.creditList.entry.withArtists.withContribution": "{ENTRY} ({CONTRIBUTION}; with {ARTISTS})", - "artistPage.contributedDurationLine": "{ARTIST} has contributed {DURATION} of music shared on this wiki.", - "artistPage.musicGroupsLine": "Contributed music to groups: {GROUPS}", - "artistPage.artGroupsLine": "Contributed art to groups: {GROUPS}", - "artistPage.groupsLine.item.withCount": "{GROUP} ({COUNT})", - "artistPage.groupsLine.item.withDuration": "{GROUP} ({DURATION})", - "artistPage.groupContributions.title.music": "Contributed music to groups:", - "artistPage.groupContributions.title.artworks": "Contributed artworks to groups:", - "artistPage.groupContributions.title.withSortButton": "{TITLE} ({SORT})", - "artistPage.groupContributions.title.sorting.count": "Sorting by count.", - "artistPage.groupContributions.title.sorting.duration": "Sorting by duration.", - "artistPage.groupContributions.item.countAccent": "({COUNT})", - "artistPage.groupContributions.item.durationAccent": "({DURATION})", - "artistPage.groupContributions.item.countDurationAccent": "({COUNT} — {DURATION})", - "artistPage.groupContributions.item.durationCountAccent": "({DURATION} — {COUNT})", - "artistPage.trackList.title": "Tracks", - "artistPage.artList.title": "Artworks", - "artistPage.flashList.title": "Flashes & Games", - "artistPage.commentaryList.title": "Commentary", - "artistPage.viewArtGallery": "View {LINK}!", - "artistPage.viewArtGallery.orBrowseList": "View {LINK}! Or browse the list:", - "artistPage.viewArtGallery.link": "art gallery", - "artistPage.nav.artist": "Artist: {ARTIST}", - "artistGalleryPage.title": "{ARTIST} - Gallery", - "artistGalleryPage.infoLine": "Contributed to {COVER_ARTS}.", - "commentaryIndex.title": "Commentary", - "commentaryIndex.infoLine": "{WORDS} across {ENTRIES}, in all.", - "commentaryIndex.albumList.title": "Choose an album:", - "commentaryIndex.albumList.item": "{ALBUM} ({WORDS} across {ENTRIES})", - "flashIndex.title": "Flashes & Games", - "flashPage.title": "{FLASH}", - "flashPage.nav.flash": "{FLASH}", - "flashSidebar.flashList.flashesInThisAct": "Flashes in this act", - "flashSidebar.flashList.entriesInThisSection": "Entries in this section", - "groupSidebar.title": "Groups", - "groupSidebar.groupList.category": "{CATEGORY}", - "groupSidebar.groupList.item": "{GROUP}", - "groupPage.nav.group": "Group: {GROUP}", - "groupInfoPage.title": "{GROUP}", - "groupInfoPage.viewAlbumGallery": "View {LINK}! Or browse the list:", - "groupInfoPage.viewAlbumGallery.link": "album gallery", - "groupInfoPage.albumList.title": "Albums", - "groupInfoPage.albumList.item": "({YEAR}) {ALBUM}", - "groupInfoPage.albumList.item.withoutYear": "{ALBUM}", - "groupInfoPage.albumList.item.withAccent": "{ITEM} {ACCENT}", - "groupInfoPage.albumList.item.otherGroupAccent": "(from {GROUP})", - "groupGalleryPage.title": "{GROUP} - Gallery", - "groupGalleryPage.infoLine": "{TRACKS} across {ALBUMS}, totaling {TIME}.", - "listingIndex.title": "Listings", - "listingIndex.infoLine": "{WIKI}: {TRACKS} across {ALBUMS}, totaling {DURATION}.", - "listingIndex.exploreList": "Feel free to explore any of the listings linked below and in the sidebar!", - "listingPage.target.album": "Albums", - "listingPage.target.artist": "Artists", - "listingPage.target.group": "Groups", - "listingPage.target.track": "Tracks", - "listingPage.target.tag": "Tags", - "listingPage.target.other": "Other", - "listingPage.listingsFor": "Listings for {TARGET}: {LISTINGS}", - "listingPage.seeAlso": "Also check out: {LISTINGS}", - "listingPage.listAlbums.byName.title": "Albums - by Name", - "listingPage.listAlbums.byName.title.short": "...by Name", - "listingPage.listAlbums.byName.item": "{ALBUM} ({TRACKS})", - "listingPage.listAlbums.byTracks.title": "Albums - by Tracks", - "listingPage.listAlbums.byTracks.title.short": "...by Tracks", - "listingPage.listAlbums.byTracks.item": "{ALBUM} ({TRACKS})", - "listingPage.listAlbums.byDuration.title": "Albums - by Duration", - "listingPage.listAlbums.byDuration.title.short": "...by Duration", - "listingPage.listAlbums.byDuration.item": "{ALBUM} ({DURATION})", - "listingPage.listAlbums.byDate.title": "Albums - by Date", - "listingPage.listAlbums.byDate.title.short": "...by Date", - "listingPage.listAlbums.byDate.item": "{ALBUM} ({DATE})", - "listingPage.listAlbums.byDateAdded.title.short": "...by Date Added to Wiki", - "listingPage.listAlbums.byDateAdded.title": "Albums - by Date Added to Wiki", - "listingPage.listAlbums.byDateAdded.chunk.title": "{DATE}", - "listingPage.listAlbums.byDateAdded.chunk.item": "{ALBUM}", - "listingPage.listArtists.byName.title": "Artists - by Name", - "listingPage.listArtists.byName.title.short": "...by Name", - "listingPage.listArtists.byName.item": "{ARTIST} ({CONTRIBUTIONS})", - "listingPage.listArtists.byContribs.title": "Artists - by Contributions", - "listingPage.listArtists.byContribs.title.short": "...by Contributions", - "listingPage.listArtists.byContribs.item": "{ARTIST} ({CONTRIBUTIONS})", - "listingPage.listArtists.byCommentary.title": "Artists - by Commentary Entries", - "listingPage.listArtists.byCommentary.title.short": "...by Commentary Entries", - "listingPage.listArtists.byCommentary.item": "{ARTIST} ({ENTRIES})", - "listingPage.listArtists.byDuration.title": "Artists - by Duration", - "listingPage.listArtists.byDuration.title.short": "...by Duration", - "listingPage.listArtists.byDuration.item": "{ARTIST} ({DURATION})", - "listingPage.listArtists.byLatest.title": "Artists - by Latest Contribution", - "listingPage.listArtists.byLatest.title.short": "...by Latest Contribution", - "listingPage.listArtists.byLatest.chunk.title.album": "{ALBUM} ({DATE})", - "listingPage.listArtists.byLatest.chunk.title.flash": "{FLASH} ({DATE})", - "listingPage.listArtists.byLatest.chunk.item": "{ARTIST}", - "listingPage.listArtists.byLatest.dateless.title": "These artists' contributions aren't dated:", - "listingPage.listArtists.byLatest.dateless.item": "{ARTIST}", - "listingPage.listGroups.byName.title": "Groups - by Name", - "listingPage.listGroups.byName.title.short": "...by Name", - "listingPage.listGroups.byName.item": "{GROUP} ({GALLERY})", - "listingPage.listGroups.byName.item.gallery": "Gallery", - "listingPage.listGroups.byCategory.title": "Groups - by Category", - "listingPage.listGroups.byCategory.title.short": "...by Category", - "listingPage.listGroups.byCategory.chunk.title": "{CATEGORY}", - "listingPage.listGroups.byCategory.chunk.item": "{GROUP} ({GALLERY})", - "listingPage.listGroups.byCategory.chunk.item.gallery": "Gallery", - "listingPage.listGroups.byAlbums.title": "Groups - by Albums", - "listingPage.listGroups.byAlbums.title.short": "...by Albums", - "listingPage.listGroups.byAlbums.item": "{GROUP} ({ALBUMS})", - "listingPage.listGroups.byTracks.title": "Groups - by Tracks", - "listingPage.listGroups.byTracks.title.short": "...by Tracks", - "listingPage.listGroups.byTracks.item": "{GROUP} ({TRACKS})", - "listingPage.listGroups.byDuration.title": "Groups - by Duration", - "listingPage.listGroups.byDuration.title.short": "...by Duration", - "listingPage.listGroups.byDuration.item": "{GROUP} ({DURATION})", - "listingPage.listGroups.byLatest.title": "Groups - by Latest Album", - "listingPage.listGroups.byLatest.title.short": "...by Latest Album", - "listingPage.listGroups.byLatest.item": "{GROUP} ({DATE})", - "listingPage.listTracks.byName.title": "Tracks - by Name", - "listingPage.listTracks.byName.title.short": "...by Name", - "listingPage.listTracks.byName.item": "{TRACK}", - "listingPage.listTracks.byAlbum.title": "Tracks - by Album", - "listingPage.listTracks.byAlbum.title.short": "...by Album", - "listingPage.listTracks.byAlbum.chunk.title": "{ALBUM}", - "listingPage.listTracks.byAlbum.chunk.item": "{TRACK}", - "listingPage.listTracks.byDate.title": "Tracks - by Date", - "listingPage.listTracks.byDate.title.short": "...by Date", - "listingPage.listTracks.byDate.chunk.title": "{ALBUM} ({DATE})", - "listingPage.listTracks.byDate.chunk.item": "{TRACK}", - "listingPage.listTracks.byDate.chunk.item.rerelease": "{TRACK} (re-release)", - "listingPage.listTracks.byDuration.title": "Tracks - by Duration", - "listingPage.listTracks.byDuration.title.short": "...by Duration", - "listingPage.listTracks.byDuration.item": "{TRACK} ({DURATION})", - "listingPage.listTracks.byDurationInAlbum.title": "Tracks - by Duration (in Album)", - "listingPage.listTracks.byDurationInAlbum.title.short": "...by Duration (in Album)", - "listingPage.listTracks.byDurationInAlbum.chunk.title": "{ALBUM}", - "listingPage.listTracks.byDurationInAlbum.chunk.item": "{TRACK} ({DURATION})", - "listingPage.listTracks.byTimesReferenced.title": "Tracks - by Times Referenced", - "listingPage.listTracks.byTimesReferenced.title.short": "...by Times Referenced", - "listingPage.listTracks.byTimesReferenced.item": "{TRACK} ({TIMES_REFERENCED})", - "listingPage.listTracks.inFlashes.byAlbum.title": "Tracks - in Flashes & Games (by Album)", - "listingPage.listTracks.inFlashes.byAlbum.title.short": "...in Flashes & Games (by Album)", - "listingPage.listTracks.inFlashes.byAlbum.chunk.title": "{ALBUM}", - "listingPage.listTracks.inFlashes.byAlbum.chunk.item": "{TRACK} (in {FLASHES})", - "listingPage.listTracks.inFlashes.byFlash.title": "Tracks - in Flashes & Games (by Flash)", - "listingPage.listTracks.inFlashes.byFlash.title.short": "...in Flashes & Games (by Flash)", - "listingPage.listTracks.inFlashes.byFlash.chunk.title": "{FLASH}", - "listingPage.listTracks.inFlashes.byFlash.chunk.item": "{TRACK} (from {ALBUM})", - "listingPage.listTracks.withLyrics.title": "Tracks - with Lyrics", - "listingPage.listTracks.withLyrics.title.short": "...with Lyrics", - "listingPage.listTracks.withLyrics.chunk.title": "{ALBUM}", - "listingPage.listTracks.withLyrics.chunk.title.withDate": "{ALBUM} ({DATE})", - "listingPage.listTracks.withLyrics.chunk.item": "{TRACK}", - "listingPage.listTracks.withSheetMusicFiles.title": "Tracks - with Sheet Music Files", - "listingPage.listTracks.withSheetMusicFiles.title.short": "...with Sheet Music Files", - "listingPage.listTracks.withSheetMusicFiles.chunk.title": "{ALBUM}", - "listingPage.listTracks.withSheetMusicFiles.chunk.title.withDate": "{ALBUM} ({DATE})", - "listingPage.listTracks.withSheetMusicFiles.chunk.item": "{TRACK}", - "listingPage.listTracks.withMidiProjectFiles.title": "Tracks - with MIDI & Project Files", - "listingPage.listTracks.withMidiProjectFiles.title.short": "...with MIDI & Project Files", - "listingPage.listTracks.withMidiProjectFiles.chunk.title": "{ALBUM}", - "listingPage.listTracks.withMidiProjectFiles.chunk.title.withDate": "{ALBUM} ({DATE})", - "listingPage.listTracks.withMidiProjectFiles.chunk.item": "{TRACK}", - "listingPage.listTags.byName.title": "Tags - by Name", - "listingPage.listTags.byName.title.short": "...by Name", - "listingPage.listTags.byName.item": "{TAG} ({TIMES_USED})", - "listingPage.listTags.byUses.title": "Tags - by Uses", - "listingPage.listTags.byUses.title.short": "...by Uses", - "listingPage.listTags.byUses.item": "{TAG} ({TIMES_USED})", - "listingPage.other.allSheetMusic.title": "All Sheet Music", - "listingPage.other.allSheetMusic.title.short": "All Sheet Music", - "listingPage.other.allSheetMusic.albumFiles": "Album sheet music:", - "listingPage.other.allSheetMusic.file": "{TITLE}", - "listingPage.other.allSheetMusic.file.withMultipleFiles": "{TITLE} ({FILES})", - "listingPage.other.allMidiProjectFiles.title": "All MIDI/Project Files", - "listingPage.other.allMidiProjectFiles.title.short": "All MIDI/Project Files", - "listingPage.other.allMidiProjectFiles.albumFiles": "Album MIDI/project files:", - "listingPage.other.allMidiProjectFiles.file": "{TITLE}", - "listingPage.other.allMidiProjectFiles.file.withMultipleFiles": "{TITLE} ({FILES})", - "listingPage.other.allAdditionalFiles.title": "All Additional Files", - "listingPage.other.allAdditionalFiles.title.short": "All Additional Files", - "listingPage.other.allAdditionalFiles.albumFiles": "Album additional files:", - "listingPage.other.allAdditionalFiles.file": "{TITLE}", - "listingPage.other.allAdditionalFiles.file.withMultipleFiles": "{TITLE} ({FILES})", - "listingPage.other.randomPages.title": "Random Pages", - "listingPage.other.randomPages.title.short": "Random Pages", - "listingPage.other.randomPages.chooseLinkLine": "Choose a link to go to a random page in that category or album! If your browser doesn't support relatively modern JavaScript or you've disabled it, these links won't work - sorry.", - "listingPage.other.randomPages.dataLoadingLine": "(Data files are downloading in the background! Please wait for data to load.)", - "listingPage.other.randomPages.dataLoadedLine": "(Data files have finished being downloaded. The links should work!)", - "listingPage.other.randomPages.misc": "Miscellaneous:", - "listingPage.other.randomPages.misc.randomArtist": "Random Artist", - "listingPage.other.randomPages.misc.atLeastTwoContributions": "at least 2 contributions", - "listingPage.other.randomPages.misc.randomAlbumWholeSite": "Random Album (whole site)", - "listingPage.other.randomPages.misc.randomTrackWholeSite": "Random Track (whole site)", - "listingPage.other.randomPages.group": "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})", - "listingPage.other.randomPages.group.randomAlbum": "Random Album", - "listingPage.other.randomPages.group.randomTrack": "Random Track", - "listingPage.other.randomPages.album": "{ALBUM}", - "listingPage.misc.trackContributors": "Track Contributors", - "listingPage.misc.artContributors": "Art Contributors", - "listingPage.misc.flashContributors": "Flash & Game Contributors", - "listingPage.misc.artAndFlashContributors": "Art & Flash Contributors", - "newsIndex.title": "News", - "newsIndex.entry.viewRest": "(View rest of entry!)", - "newsEntryPage.title": "{ENTRY}", - "newsEntryPage.published": "(Published {DATE}.)", - "redirectPage.title": "Moved to {TITLE}", - "redirectPage.infoLine": "This page has been moved to {TARGET}.", - "tagPage.title": "{TAG}", - "tagPage.infoLine": "Appears in {COVER_ARTS}.", - "tagPage.nav.tag": "Tag: {TAG}", - "trackPage.title": "{TRACK}", - "trackPage.referenceList.fandom": "Fandom:", - "trackPage.referenceList.official": "Official:", - "trackPage.nav.track": "{TRACK}", - "trackPage.nav.track.withNumber": "{NUMBER}. {TRACK}", - "trackPage.nav.random": "Random", - "trackPage.socialEmbed.heading": "{ALBUM}", - "trackPage.socialEmbed.title": "{TRACK}", - "trackPage.socialEmbed.body.withArtists.withCoverArtists": "By {ARTISTS}; art by {COVER_ARTISTS}.", - "trackPage.socialEmbed.body.withArtists": "By {ARTISTS}.", - "trackPage.socialEmbed.body.withCoverArtists": "Art by {COVER_ARTISTS}." -} diff --git a/src/strings-default.yaml b/src/strings-default.yaml new file mode 100644 index 00000000..1f16de46 --- /dev/null +++ b/src/strings-default.yaml @@ -0,0 +1,585 @@ +meta.languageCode: en +meta.languageName: English + +count.tracks: "{TRACKS}" +count.tracks.withUnit.zero: "" +count.tracks.withUnit.one: "{TRACKS} track" +count.tracks.withUnit.two: "" +count.tracks.withUnit.few: "" +count.tracks.withUnit.many: "" +count.tracks.withUnit.other: "{TRACKS} tracks" + +count.additionalFiles: "{FILES}" +count.additionalFiles.withUnit.zero: "" +count.additionalFiles.withUnit.one: "{FILES} file" +count.additionalFiles.withUnit.two: "" +count.additionalFiles.withUnit.few: "" +count.additionalFiles.withUnit.many: "" +count.additionalFiles.withUnit.other: "{FILES} files" + +count.albums: "{ALBUMS}" +count.albums.withUnit.zero: "" +count.albums.withUnit.one: "{ALBUMS} album" +count.albums.withUnit.two: "" +count.albums.withUnit.few: "" +count.albums.withUnit.many: "" +count.albums.withUnit.other: "{ALBUMS} albums" + +count.artworks: "{ARTWORKS}" +count.artworks.withUnit.zero: "" +count.artworks.withUnit.one: "{ARTWORKS} artwork" +count.artworks.withUnit.two: "" +count.artworks.withUnit.few: "" +count.artworks.withUnit.many: "" +count.artworks.withUnit.other: "{ARTWORKS} artworks" + +count.commentaryEntries: "{ENTRIES}" +count.commentaryEntries.withUnit.zero: "" +count.commentaryEntries.withUnit.one: "{ENTRIES} entry" +count.commentaryEntries.withUnit.two: "" +count.commentaryEntries.withUnit.few: "" +count.commentaryEntries.withUnit.many: "" +count.commentaryEntries.withUnit.other: "{ENTRIES} entries" + +count.contributions: "{CONTRIBUTIONS}" +count.contributions.withUnit.zero: "" +count.contributions.withUnit.one: "{CONTRIBUTIONS} contribution" +count.contributions.withUnit.two: "" +count.contributions.withUnit.few: "" +count.contributions.withUnit.many: "" +count.contributions.withUnit.other: "{CONTRIBUTIONS} contributions" + +count.coverArts: "{COVER_ARTS}" +count.coverArts.withUnit.zero: "" +count.coverArts.withUnit.one: "{COVER_ARTS} cover art" +count.coverArts.withUnit.two: "" +count.coverArts.withUnit.few: "" +count.coverArts.withUnit.many: "" +count.coverArts.withUnit.other: "{COVER_ARTS} cover arts" + +count.flashes: "{FLASHES}" +count.flashes.withUnit.zero: "" +count.flashes.withUnit.one: "{FLASHES} flashes & games" +count.flashes.withUnit.two: "" +count.flashes.withUnit.few: "" +count.flashes.withUnit.many: "" +count.flashes.withUnit.other: "{FLASHES} flashes & games" + +count.timesReferenced: "{TIMES_REFERENCED}" +count.timesReferenced.withUnit.zero: "" +count.timesReferenced.withUnit.one: "{TIMES_REFERENCED} time referenced" +count.timesReferenced.withUnit.two: "" +count.timesReferenced.withUnit.few: "" +count.timesReferenced.withUnit.many: "" +count.timesReferenced.withUnit.other: "{TIMES_REFERENCED} times referenced" + +count.words: "{WORDS}" +count.words.thousand: "{WORDS}k" +count.words.withUnit.zero: "" +count.words.withUnit.one: "{WORDS} word" +count.words.withUnit.two: "" +count.words.withUnit.few: "" +count.words.withUnit.many: "" +count.words.withUnit.other: "{WORDS} words" + +count.timesUsed: "{TIMES_USED}" +count.timesUsed.withUnit.zero: "" +count.timesUsed.withUnit.one: "used {TIMES_USED} time" +count.timesUsed.withUnit.two: "" +count.timesUsed.withUnit.few: "" +count.timesUsed.withUnit.many: "" +count.timesUsed.withUnit.other: "used {TIMES_USED} times" + +count.index.zero: "" +count.index.one: "{INDEX}st" +count.index.two: "{INDEX}nd" +count.index.few: "{INDEX}rd" +count.index.many: "" +count.index.other: "{INDEX}th" + +count.duration.hours: "{HOURS}:{MINUTES}:{SECONDS}" +count.duration.hours.withUnit: "{HOURS}:{MINUTES}:{SECONDS} hours" +count.duration.minutes: "{MINUTES}:{SECONDS}" +count.duration.minutes.withUnit: "{MINUTES}:{SECONDS} minutes" +count.duration.approximate: "~{DURATION}" +count.duration.missing: "_:__" + +count.fileSize.terabytes: "{TERABYTES} TB" +count.fileSize.gigabytes: "{GIGABYTES} GB" +count.fileSize.megabytes: "{MEGABYTES} MB" +count.fileSize.kilobytes: "{KILOBYTES} kB" +count.fileSize.bytes: "{BYTES} bytes" + +releaseInfo.by: "By {ARTISTS}." +releaseInfo.from: "From {ALBUM}." +releaseInfo.coverArtBy: "Cover art by {ARTISTS}." +releaseInfo.wallpaperArtBy: "Wallpaper art by {ARTISTS}." +releaseInfo.bannerArtBy: "Banner art by {ARTISTS}." +releaseInfo.released: "Released {DATE}." +releaseInfo.artReleased: "Art released {DATE}." +releaseInfo.addedToWiki: "Added to wiki {DATE}." +releaseInfo.duration: "Duration: {DURATION}." +releaseInfo.viewCommentary: "View {LINK}!" +releaseInfo.viewCommentary.link: "commentary page" +releaseInfo.viewGallery: "View {LINK}!" +releaseInfo.viewGallery.link: "gallery page" +releaseInfo.viewGalleryOrCommentary: "View {GALLERY} or {COMMENTARY}!" +releaseInfo.viewGalleryOrCommentary.gallery: "gallery page" +releaseInfo.viewGalleryOrCommentary.commentary: "commentary page" +releaseInfo.viewOriginalFile: "View {LINK}." +releaseInfo.viewOriginalFile.withSize: "View {LINK} ({SIZE})." +releaseInfo.viewOriginalFile.link: "original file" +releaseInfo.viewOriginalFile.sizeWarning: >- + (Heads up! If you're on a mobile plan, this is a large download.) +releaseInfo.listenOn: "Listen on {LINKS}." +releaseInfo.listenOn.noLinks: >- + This wiki doesn't have any listening links for {NAME}. +releaseInfo.visitOn: "Visit on {LINKS}." +releaseInfo.playOn: "Play on {LINKS}." +releaseInfo.readCommentary: "Read {LINK}." +releaseInfo.readCommentary.link: "artist commentary" +releaseInfo.alsoReleasedAs: "Also released as:" +releaseInfo.alsoReleasedAs.item: "{TRACK} (on {ALBUM})" +releaseInfo.contributors: "Contributors:" +releaseInfo.tracksReferenced: "Tracks that {TRACK} references:" +releaseInfo.tracksThatReference: "Tracks that reference {TRACK}:" +releaseInfo.tracksSampled: "Tracks that {TRACK} samples:" +releaseInfo.tracksThatSample: "Tracks that sample {TRACK}:" +releaseInfo.flashesThatFeature: "Flashes & games that feature {TRACK}:" +releaseInfo.flashesThatFeature.item: "{FLASH}" +releaseInfo.flashesThatFeature.item.asDifferentRelease: "{FLASH} (as {TRACK})" +releaseInfo.tracksFeatured: "Tracks that {FLASH} features:" +releaseInfo.lyrics: "Lyrics:" +releaseInfo.artistCommentary: "Artist commentary:" +releaseInfo.artistCommentary.seeOriginalRelease: "See {ORIGINAL}!" +releaseInfo.artTags: "Tags:" +releaseInfo.artTags.inline: "Tags: {TAGS}" +releaseInfo.additionalFiles.shortcut: "View {ANCHOR_LINK}: {TITLES}" +releaseInfo.additionalFiles.shortcut.anchorLink: "additional files" +releaseInfo.additionalFiles.heading: "View or download {ADDITIONAL_FILES}:" +releaseInfo.additionalFiles.entry: "{TITLE}" +releaseInfo.additionalFiles.entry.withDescription: "{TITLE}: {DESCRIPTION}" +releaseInfo.additionalFiles.file: "{FILE}" +releaseInfo.additionalFiles.file.withSize: "{FILE} ({SIZE})" +releaseInfo.sheetMusicFiles.shortcut: "Download {LINK}." +releaseInfo.sheetMusicFiles.shortcut.link: "sheet music files" +releaseInfo.sheetMusicFiles.heading: "Print or download sheet music files:" +releaseInfo.midiProjectFiles.shortcut: "Download {LINK}." +releaseInfo.midiProjectFiles.shortcut.link: "MIDI/project files" +releaseInfo.midiProjectFiles.heading: "Download MIDI/project files:" +releaseInfo.note: "Context notes:" + +trackList.section.withDuration: "{SECTION} ({DURATION}):" +trackList.group: "From {GROUP}:" +trackList.group.fromOther: "From somewhere else:" +trackList.item.withDuration: "({DURATION}) {TRACK}" +trackList.item.withDuration.withArtists: "({DURATION}) {TRACK} {BY}" +trackList.item.withArtists: "{TRACK} {BY}" +trackList.item.withArtists.by: "by {ARTISTS}" +trackList.item.rerelease: "{TRACK} (re-release)" + +misc.alt.albumCover: "album cover" +misc.alt.albumBanner: "album banner" +misc.alt.trackCover: "track cover" +misc.alt.artistAvatar: "artist avatar" +misc.alt.flashArt: "flash art" + +misc.artistLink: "{ARTIST}" +misc.artistLink.withContribution: "{ARTIST} ({CONTRIB})" +misc.artistLink.withExternalLinks: "{ARTIST} ({LINKS})" +misc.artistLink.withContribution.withExternalLinks: "{ARTIST} ({CONTRIB}) ({LINKS})" + +misc.chronology.seeArtistPages: "(See artist pages for chronology info!)" +misc.chronology.heading.coverArt: "{INDEX} cover art by {ARTIST}" +misc.chronology.heading.flash: "{INDEX} flash/game by {ARTIST}" +misc.chronology.heading.track: "{INDEX} track by {ARTIST}" +misc.chronology.withNavigation: "{HEADING} ({NAVIGATION})" + +misc.external.domain: "External ({DOMAIN})" +misc.external.local: "Wiki Archive (local upload)" +misc.external.bandcamp: "Bandcamp" +misc.external.bandcamp.domain: "Bandcamp ({DOMAIN})" +misc.external.deviantart: "DeviantArt" +misc.external.instagram: "Instagram" +misc.external.mastodon: "Mastodon" +misc.external.mastodon.domain: "Mastodon ({DOMAIN})" +misc.external.newgrounds: "Newgrounds" +misc.external.patreon: "Patreon" +misc.external.poetryFoundation: "Poetry Foundation" +misc.external.soundcloud: "SoundCloud" +misc.external.spotify: "Spotify" +misc.external.tumblr: "Tumblr" +misc.external.twitter: "Twitter" +misc.external.wikipedia: "Wikipedia" +misc.external.youtube: "YouTube" +misc.external.youtube.playlist: "YouTube (playlist)" +misc.external.youtube.fullAlbum: "YouTube (full album)" +misc.external.flash.bgreco: "{LINK} (HQ Audio)" +misc.external.flash.homestuck.page: "{LINK} (page {PAGE})" +misc.external.flash.homestuck.secret: "{LINK} (secret page)" +misc.external.flash.youtube: "{LINK} (on any device)" + +misc.missingImage: "(This image file is missing)" +misc.missingLinkContent: "(Missing link content)" + +misc.nav.previous: "Previous" +misc.nav.next: "Next" +misc.nav.info: "Info" +misc.nav.gallery: "Gallery" + +misc.pageTitle: "{TITLE}" +misc.pageTitle.withWikiName: "{TITLE} | {WIKI_NAME}" + +misc.skippers.skipTo: "Skip to:" +misc.skippers.content: "Content" +misc.skippers.sidebar: "Sidebar" +misc.skippers.sidebar.left: "Sidebar (left)" +misc.skippers.sidebar.right: "Sidebar (right)" +misc.skippers.header: "Header" +misc.skippers.footer: "Footer" +misc.skippers.tracks: "Tracks" +misc.skippers.art: "Artworks" +misc.skippers.flashes: "Flashes & Games" +misc.skippers.contributors: "Contributors" +misc.skippers.references: "References..." +misc.skippers.referencedBy: "Referenced by..." +misc.skippers.samples: "Samples..." +misc.skippers.sampledBy: "Sampled by..." +misc.skippers.features: "Features..." +misc.skippers.featuredIn: "Featured in..." +misc.skippers.lyrics: "Lyrics" +misc.skippers.sheetMusicFiles: "Sheet music files" +misc.skippers.midiProjectFiles: "MIDI/project files" +misc.skippers.additionalFiles: "Additional files" +misc.skippers.commentary: "Commentary" +misc.skippers.artistCommentary: "Commentary" + +misc.socialEmbed.heading: "{WIKI_NAME} | {HEADING}" + +misc.jumpTo: "Jump to:" +misc.jumpTo.withLinks: "Jump to: {LINKS}." + +misc.contentWarnings: "cw: {WARNINGS}" +misc.contentWarnings.reveal: "click to show" + +misc.albumGrid.details: "({TRACKS}, {TIME})" +misc.albumGrid.details.coverArtists: "(Illust. {ARTISTS})" +misc.albumGrid.details.otherCoverArtists: "(With {ARTISTS})" +misc.albumGrid.noCoverArt: "{ALBUM}" +misc.albumGalleryGrid.noCoverArt: "{NAME}" + +misc.uiLanguage: "UI Language: {LANGUAGES" + +homepage.title: "{TITLE}" +homepage.news.title: News +homepage.news.entry.viewRest: "(View rest of entry!)" + +albumSidebar.trackList.fallbackSectionName: "Track list" +albumSidebar.trackList.group: "{GROUP}" +albumSidebar.trackList.group.withRange: "{GROUP} ({RANGE})" +albumSidebar.trackList.item: "{TRACK}" +albumSidebar.groupBox.title: "{GROUP}" +albumSidebar.groupBox.next: "Next: {ALBUM}" +albumSidebar.groupBox.previous: "Previous: {ALBUM}" + +albumPage.title: "{ALBUM}" +albumPage.nav.album: "{ALBUM}" +albumPage.nav.randomTrack: "Random Track" +albumPage.nav.gallery: "Gallery" +albumPage.nav.commentary: "Commentary" +albumPage.socialEmbed.heading: "{GROUP}" +albumPage.socialEmbed.title: "{ALBUM}" +albumPage.socialEmbed.body.withDuration: "{DURATION}." +albumPage.socialEmbed.body.withTracks: "{TRACKS}." +albumPage.socialEmbed.body.withReleaseDate: Released {DATE}. +albumPage.socialEmbed.body.withDuration.withTracks: "{DURATION}, {TRACKS}." +albumPage.socialEmbed.body.withDuration.withReleaseDate: "{DURATION}. Released {DATE}." +albumPage.socialEmbed.body.withTracks.withReleaseDate: "{TRACKS}. Released {DATE}." +albumPage.socialEmbed.body.withDuration.withTracks.withReleaseDate: "{DURATION}, {TRACKS}. + Released {DATE}." + +albumGalleryPage.title: "{ALBUM} - Gallery" +albumGalleryPage.statsLine: >- + {TRACKS} totaling {DURATION}. +albumGalleryPage.statsLine.withDate: >- + {TRACKS} totaling {DURATION}. Released {DATE}. +albumGalleryPage.coverArtistsLine: >- + All track artwork by {ARTISTS}. +albumGalleryPage.noTrackArtworksLine: >- + This album doesn't have any track artwork. + +albumCommentaryPage.title: "{ALBUM} - Commentary" +albumCommentaryPage.infoLine: "{WORDS} across {ENTRIES}." +albumCommentaryPage.nav.album: "Album: {ALBUM}" +albumCommentaryPage.entry.title.albumCommentary: "Album commentary" +albumCommentaryPage.entry.title.trackCommentary: "{TRACK}" + +artistPage.title: "{ARTIST}" +artistPage.creditList.album: "{ALBUM}" +artistPage.creditList.album.withDate: "{ALBUM} ({DATE})" +artistPage.creditList.album.withDuration: "{ALBUM} ({DURATION})" +artistPage.creditList.album.withDate.withDuration: "{ALBUM} ({DATE}; {DURATION})" +artistPage.creditList.flashAct: "{ACT}" +artistPage.creditList.flashAct.withDate: "{ACT} ({DATE})" +artistPage.creditList.flashAct.withDateRange: "{ACT} ({DATE_RANGE})" +artistPage.creditList.entry.track: "{TRACK}" +artistPage.creditList.entry.track.withDuration: "({DURATION}) {TRACK}" +artistPage.creditList.entry.album.coverArt: "(cover art)" +artistPage.creditList.entry.album.wallpaperArt: "(wallpaper art)" +artistPage.creditList.entry.album.bannerArt: "(banner art)" +artistPage.creditList.entry.album.commentary: "(album commentary)" +artistPage.creditList.entry.flash: "{FLASH}" +artistPage.creditList.entry.rerelease: "{ENTRY} (re-release)" +artistPage.creditList.entry.withContribution: "{ENTRY} ({CONTRIBUTION})" +artistPage.creditList.entry.withArtists: "{ENTRY} (with {ARTISTS})" +artistPage.creditList.entry.withArtists.withContribution: "{ENTRY} ({CONTRIBUTION}; with {ARTISTS})" +artistPage.contributedDurationLine: >- + {ARTIST} has contributed {DURATION} of music shared on this wiki. +artistPage.musicGroupsLine: "Contributed music to groups: {GROUPS}" +artistPage.artGroupsLine: "Contributed art to groups: {GROUPS}" +artistPage.groupsLine.item.withCount: "{GROUP} ({COUNT})" +artistPage.groupsLine.item.withDuration: "{GROUP} ({DURATION})" +artistPage.groupContributions.title.music: "Contributed music to groups:" +artistPage.groupContributions.title.artworks: "Contributed artworks to groups:" +artistPage.groupContributions.title.withSortButton: "{TITLE} ({SORT})" +artistPage.groupContributions.title.sorting.count: "Sorting by count." +artistPage.groupContributions.title.sorting.duration: "Sorting by duration." +artistPage.groupContributions.item.countAccent: "({COUNT})" +artistPage.groupContributions.item.durationAccent: "({DURATION})" +artistPage.groupContributions.item.countDurationAccent: "({COUNT} — {DURATION})" +artistPage.groupContributions.item.durationCountAccent: "({DURATION} — {COUNT})" +artistPage.trackList.title: "Tracks" +artistPage.artList.title: "Artworks" +artistPage.flashList.title: "Flashes & Games" +artistPage.commentaryList.title: "Commentary" +artistPage.viewArtGallery: "View {LINK}!" +artistPage.viewArtGallery.orBrowseList: "View {LINK}! Or browse the list:" +artistPage.viewArtGallery.link: "art gallery" +artistPage.nav.artist: "Artist: {ARTIST}" +artistGalleryPage.title: "{ARTIST} - Gallery" +artistGalleryPage.infoLine: "Contributed to {COVER_ARTS}." + +commentaryIndex.title: "Commentary" +commentaryIndex.infoLine: "{WORDS} across {ENTRIES}, in all." +commentaryIndex.albumList.title: "Choose an album:" +commentaryIndex.albumList.item: "{ALBUM} ({WORDS} across {ENTRIES})" + +flashIndex.title: "Flashes & Games" + +flashPage.title: "{FLASH}" +flashPage.nav.flash: "{FLASH}" + +flashSidebar.flashList.flashesInThisAct: "Flashes in this act" +flashSidebar.flashList.entriesInThisSection: "Entries in this section" + +groupSidebar.title: "Groups" +groupSidebar.groupList.category: "{CATEGORY}" +groupSidebar.groupList.item: "{GROUP}" + +groupPage.nav.group: "Group: {GROUP}" + +groupInfoPage.title: "{GROUP}" +groupInfoPage.viewAlbumGallery: "View {LINK}! Or browse the list:" +groupInfoPage.viewAlbumGallery.link: "album gallery" +groupInfoPage.albumList.title: "Albums" +groupInfoPage.albumList.item: "({YEAR}) {ALBUM}" +groupInfoPage.albumList.item.withoutYear: "{ALBUM}" +groupInfoPage.albumList.item.withAccent: "{ITEM} {ACCENT}" +groupInfoPage.albumList.item.otherGroupAccent: "(from {GROUP})" + +groupGalleryPage.title: "{GROUP} - Gallery" +groupGalleryPage.infoLine: "{TRACKS} across {ALBUMS}, totaling {TIME}." + +listingIndex.title: "Listings" +listingIndex.infoLine: >- + {WIKI}: {TRACKS} across {ALBUMS}, totaling {DURATION}. +listingIndex.exploreList: >- + Feel free to explore any of the listings linked below and in the sidebar! + +listingPage.target.album: "Albums" +listingPage.target.artist: "Artists" +listingPage.target.group: "Groups" +listingPage.target.track: "Tracks" +listingPage.target.tag: "Tags" +listingPage.target.other: "Other" + +listingPage.listingsFor: "Listings for {TARGET}: {LISTINGS}" +listingPage.seeAlso: "Also check out: {LISTINGS}" + +listingPage.listAlbums.byName.title: "Albums - by Name" +listingPage.listAlbums.byName.title.short: "...by Name" +listingPage.listAlbums.byName.item: "{ALBUM} ({TRACKS})" +listingPage.listAlbums.byTracks.title: "Albums - by Tracks" +listingPage.listAlbums.byTracks.title.short: "...by Tracks" +listingPage.listAlbums.byTracks.item: "{ALBUM} ({TRACKS})" +listingPage.listAlbums.byDuration.title: "Albums - by Duration" +listingPage.listAlbums.byDuration.title.short: "...by Duration" +listingPage.listAlbums.byDuration.item: "{ALBUM} ({DURATION})" +listingPage.listAlbums.byDate.title: "Albums - by Date" +listingPage.listAlbums.byDate.title.short: "...by Date" +listingPage.listAlbums.byDate.item: "{ALBUM} ({DATE})" +listingPage.listAlbums.byDateAdded.title.short: "...by Date Added to Wiki" +listingPage.listAlbums.byDateAdded.title: "Albums - by Date Added to Wiki" +listingPage.listAlbums.byDateAdded.chunk.title: "{DATE}" +listingPage.listAlbums.byDateAdded.chunk.item: "{ALBUM}" + +listingPage.listArtists.byName.title: "Artists - by Name" +listingPage.listArtists.byName.title.short: "...by Name" +listingPage.listArtists.byName.item: "{ARTIST} ({CONTRIBUTIONS})" +listingPage.listArtists.byContribs.title: "Artists - by Contributions" +listingPage.listArtists.byContribs.title.short: "...by Contributions" +listingPage.listArtists.byContribs.item: "{ARTIST} ({CONTRIBUTIONS})" +listingPage.listArtists.byCommentary.title: "Artists - by Commentary Entries" +listingPage.listArtists.byCommentary.title.short: "...by Commentary Entries" +listingPage.listArtists.byCommentary.item: "{ARTIST} ({ENTRIES})" +listingPage.listArtists.byDuration.title: "Artists - by Duration" +listingPage.listArtists.byDuration.title.short: "...by Duration" +listingPage.listArtists.byDuration.item: "{ARTIST} ({DURATION})" +listingPage.listArtists.byLatest.title: "Artists - by Latest Contribution" +listingPage.listArtists.byLatest.title.short: "...by Latest Contribution" +listingPage.listArtists.byLatest.chunk.title.album: "{ALBUM} ({DATE})" +listingPage.listArtists.byLatest.chunk.title.flash: "{FLASH} ({DATE})" +listingPage.listArtists.byLatest.chunk.item: "{ARTIST}" +listingPage.listArtists.byLatest.dateless.title: "These artists' contributions aren't dated:" +listingPage.listArtists.byLatest.dateless.item: "{ARTIST}" + +listingPage.listGroups.byName.title: "Groups - by Name" +listingPage.listGroups.byName.title.short: "...by Name" +listingPage.listGroups.byName.item: "{GROUP} ({GALLERY})" +listingPage.listGroups.byName.item.gallery: "Gallery" +listingPage.listGroups.byCategory.title: "Groups - by Category" +listingPage.listGroups.byCategory.title.short: "...by Category" +listingPage.listGroups.byCategory.chunk.title: "{CATEGORY}" +listingPage.listGroups.byCategory.chunk.item: "{GROUP} ({GALLERY})" +listingPage.listGroups.byCategory.chunk.item.gallery: "Gallery" +listingPage.listGroups.byAlbums.title: "Groups - by Albums" +listingPage.listGroups.byAlbums.title.short: "...by Albums" +listingPage.listGroups.byAlbums.item: "{GROUP} ({ALBUMS})" +listingPage.listGroups.byTracks.title: "Groups - by Tracks" +listingPage.listGroups.byTracks.title.short: "...by Tracks" +listingPage.listGroups.byTracks.item: "{GROUP} ({TRACKS})" +listingPage.listGroups.byDuration.title: "Groups - by Duration" +listingPage.listGroups.byDuration.title.short: "...by Duration" +listingPage.listGroups.byDuration.item: "{GROUP} ({DURATION})" +listingPage.listGroups.byLatest.title: "Groups - by Latest Album" +listingPage.listGroups.byLatest.title.short: "...by Latest Album" +listingPage.listGroups.byLatest.item: "{GROUP} ({DATE})" + +listingPage.listTracks.byName.title: "Tracks - by Name" +listingPage.listTracks.byName.title.short: "...by Name" +listingPage.listTracks.byName.item: "{TRACK}" +listingPage.listTracks.byAlbum.title: "Tracks - by Album" +listingPage.listTracks.byAlbum.title.short: "...by Album" +listingPage.listTracks.byAlbum.chunk.title: "{ALBUM}" +listingPage.listTracks.byAlbum.chunk.item: "{TRACK}" +listingPage.listTracks.byDate.title: "Tracks - by Date" +listingPage.listTracks.byDate.title.short: "...by Date" +listingPage.listTracks.byDate.chunk.title: "{ALBUM} ({DATE})" +listingPage.listTracks.byDate.chunk.item: "{TRACK}" +listingPage.listTracks.byDate.chunk.item.rerelease: "{TRACK} (re-release)" +listingPage.listTracks.byDuration.title: "Tracks - by Duration" +listingPage.listTracks.byDuration.title.short: "...by Duration" +listingPage.listTracks.byDuration.item: "{TRACK} ({DURATION})" +listingPage.listTracks.byDurationInAlbum.title: "Tracks - by Duration (in Album)" +listingPage.listTracks.byDurationInAlbum.title.short: "...by Duration (in Album)" +listingPage.listTracks.byDurationInAlbum.chunk.title: "{ALBUM}" +listingPage.listTracks.byDurationInAlbum.chunk.item: "{TRACK} ({DURATION})" +listingPage.listTracks.byTimesReferenced.title: "Tracks - by Times Referenced" +listingPage.listTracks.byTimesReferenced.title.short: "...by Times Referenced" +listingPage.listTracks.byTimesReferenced.item: "{TRACK} ({TIMES_REFERENCED})" +listingPage.listTracks.inFlashes.byAlbum.title: "Tracks - in Flashes & Games (by Album)" +listingPage.listTracks.inFlashes.byAlbum.title.short: "...in Flashes & Games (by Album)" +listingPage.listTracks.inFlashes.byAlbum.chunk.title: "{ALBUM}" +listingPage.listTracks.inFlashes.byAlbum.chunk.item: "{TRACK} (in {FLASHES})" +listingPage.listTracks.inFlashes.byFlash.title: "Tracks - in Flashes & Games (by Flash)" +listingPage.listTracks.inFlashes.byFlash.title.short: "...in Flashes & Games (by Flash)" +listingPage.listTracks.inFlashes.byFlash.chunk.title: "{FLASH}" +listingPage.listTracks.inFlashes.byFlash.chunk.item: "{TRACK} (from {ALBUM})" +listingPage.listTracks.withLyrics.title: "Tracks - with Lyrics" +listingPage.listTracks.withLyrics.title.short: "...with Lyrics" +listingPage.listTracks.withLyrics.chunk.title: "{ALBUM}" +listingPage.listTracks.withLyrics.chunk.title.withDate: "{ALBUM} ({DATE})" +listingPage.listTracks.withLyrics.chunk.item: "{TRACK}" +listingPage.listTracks.withSheetMusicFiles.title: "Tracks - with Sheet Music Files" +listingPage.listTracks.withSheetMusicFiles.title.short: "...with Sheet Music Files" +listingPage.listTracks.withSheetMusicFiles.chunk.title: "{ALBUM}" +listingPage.listTracks.withSheetMusicFiles.chunk.title.withDate: "{ALBUM} ({DATE})" +listingPage.listTracks.withSheetMusicFiles.chunk.item: "{TRACK}" +listingPage.listTracks.withMidiProjectFiles.title: "Tracks - with MIDI & Project Files" +listingPage.listTracks.withMidiProjectFiles.title.short: "...with MIDI & Project Files" +listingPage.listTracks.withMidiProjectFiles.chunk.title: "{ALBUM}" +listingPage.listTracks.withMidiProjectFiles.chunk.title.withDate: "{ALBUM} ({DATE})" +listingPage.listTracks.withMidiProjectFiles.chunk.item: "{TRACK}" + +listingPage.listTags.byName.title: "Tags - by Name" +listingPage.listTags.byName.title.short: "...by Name" +listingPage.listTags.byName.item: "{TAG} ({TIMES_USED})" +listingPage.listTags.byUses.title: "Tags - by Uses" +listingPage.listTags.byUses.title.short: "...by Uses" +listingPage.listTags.byUses.item: "{TAG} ({TIMES_USED})" + +listingPage.other.allSheetMusic.title: "All Sheet Music" +listingPage.other.allSheetMusic.title.short: "All Sheet Music" +listingPage.other.allSheetMusic.albumFiles: "Album sheet music:" +listingPage.other.allSheetMusic.file: "{TITLE}" +listingPage.other.allSheetMusic.file.withMultipleFiles: "{TITLE} ({FILES})" +listingPage.other.allMidiProjectFiles.title: "All MIDI/Project Files" +listingPage.other.allMidiProjectFiles.title.short: "All MIDI/Project Files" +listingPage.other.allMidiProjectFiles.albumFiles: "Album MIDI/project files:" +listingPage.other.allMidiProjectFiles.file: "{TITLE}" +listingPage.other.allMidiProjectFiles.file.withMultipleFiles: "{TITLE} ({FILES})" +listingPage.other.allAdditionalFiles.title: "All Additional Files" +listingPage.other.allAdditionalFiles.title.short: "All Additional Files" +listingPage.other.allAdditionalFiles.albumFiles: "Album additional files:" +listingPage.other.allAdditionalFiles.file: "{TITLE}" +listingPage.other.allAdditionalFiles.file.withMultipleFiles: "{TITLE} ({FILES})" + +listingPage.other.randomPages.title: "Random Pages" +listingPage.other.randomPages.title.short: "Random Pages" +listingPage.other.randomPages.chooseLinkLine: >- + Choose a link to go to a random page in that category or album! + If your browser doesn't support relatively modern JavaScript + or you've disabled it, these links won't work - sorry. +listingPage.other.randomPages.dataLoadingLine: >- + (Data files are downloading in the background! Please wait for data to load.) +listingPage.other.randomPages.dataLoadedLine: >- + (Data files have finished being downloaded. The links should work!) +listingPage.other.randomPages.misc: "Miscellaneous:" +listingPage.other.randomPages.misc.randomArtist: "Random Artist" +listingPage.other.randomPages.misc.atLeastTwoContributions: "at least 2 contributions" +listingPage.other.randomPages.misc.randomAlbumWholeSite: "Random Album (whole site)" +listingPage.other.randomPages.misc.randomTrackWholeSite: "Random Track (whole site)" +listingPage.other.randomPages.group: "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})" +listingPage.other.randomPages.group.randomAlbum: "Random Album" +listingPage.other.randomPages.group.randomTrack: "Random Track" +listingPage.other.randomPages.album: "{ALBUM}" + +listingPage.misc.trackContributors: "Track Contributors" +listingPage.misc.artContributors: "Art Contributors" +listingPage.misc.flashContributors: "Flash & Game Contributors" +listingPage.misc.artAndFlashContributors: "Art & Flash Contributors" + +newsIndex.title: "News" +newsIndex.entry.viewRest: "(View rest of entry!)" + +newsEntryPage.title: "{ENTRY}" +newsEntryPage.published: "(Published {DATE}.)" + +redirectPage.title: "Moved to {TITLE}" +redirectPage.infoLine: "This page has been moved to {TARGET}." + +tagPage.title: "{TAG}" +tagPage.infoLine: "Appears in {COVER_ARTS}." +tagPage.nav.tag: "Tag: {TAG}" + +trackPage.title: "{TRACK}" +trackPage.referenceList.fandom: "Fandom:" +trackPage.referenceList.official: "Official:" +trackPage.nav.track: "{TRACK}" +trackPage.nav.track.withNumber: "{NUMBER}. {TRACK}" +trackPage.nav.random: "Random" +trackPage.socialEmbed.heading: "{ALBUM}" +trackPage.socialEmbed.title: "{TRACK}" +trackPage.socialEmbed.body.withArtists.withCoverArtists: "By {ARTISTS}; art by {COVER_ARTISTS}." +trackPage.socialEmbed.body.withArtists: "By {ARTISTS}." +trackPage.socialEmbed.body.withCoverArtists: "Art by {COVER_ARTISTS}." diff --git a/src/upd8.js b/src/upd8.js index c011b660..eae9d217 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -94,7 +94,7 @@ try { const BUILD_TIME = new Date(); -const DEFAULT_STRINGS_FILE = 'strings-default.json'; +const DEFAULT_STRINGS_FILE = 'strings-default.yaml'; const STATUS_NOT_STARTED = `not started`; const STATUS_NOT_APPLICABLE = `not applicable`; -- cgit 1.3.0-6-gf8a5 From 8d24f17f729c7da550824ab4134b89757754fb9c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 20:08:15 -0400 Subject: data: language: flatten language spec, allow for structuring --- src/data/language.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index aed16057..99efc03d 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -46,8 +46,24 @@ export function processLanguageSpec(spec, {existingCode = null}) { return {code, intlCode, name, hidden, strings}; } +function flattenLanguageSpec(spec) { + const recursive = (keyPath, value) => + (typeof value === 'object' + ? Object.assign({}, ... + Object.entries(value) + .map(([key, value]) => + (key === '_' + ? {[keyPath]: value} + : recursive( + (keyPath ? `${keyPath}.${key}` : key), + value)))) + : {[keyPath]: value}); + + return recursive('', spec); +} + async function processLanguageSpecFromFile(file, processLanguageSpecOpts) { - let contents, spec; + let contents; try { contents = await readFile(file, 'utf-8'); @@ -57,14 +73,16 @@ async function processLanguageSpecFromFile(file, processLanguageSpecOpts) { error => annotateErrorWithFile(error, file)); } + let rawSpec; let parseLanguage; + try { if (path.extname(file) === '.yaml') { parseLanguage = 'YAML'; - spec = yaml.load(contents); + rawSpec = yaml.load(contents); } else { parseLanguage = 'JSON'; - spec = JSON.parse(contents); + rawSpec = JSON.parse(contents); } } catch (caughtError) { throw annotateError( @@ -72,8 +90,10 @@ async function processLanguageSpecFromFile(file, processLanguageSpecOpts) { error => annotateErrorWithFile(error, file)); } + const flattenedSpec = flattenLanguageSpec(rawSpec); + try { - return processLanguageSpec(spec, processLanguageSpecOpts); + return processLanguageSpec(flattenedSpec, processLanguageSpecOpts); } catch (caughtError) { throw annotateErrorWithFile(caughtError, file); } -- cgit 1.3.0-6-gf8a5 From 143e4fbaed9e58387147b8fec5e1697bf1079f31 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 6 Nov 2023 20:08:54 -0400 Subject: content: organize strings-default.yaml into tree structure --- src/strings-default.yaml | 1509 ++++++++++++++++++++++++++++------------------ 1 file changed, 927 insertions(+), 582 deletions(-) diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 1f16de46..2fd905d1 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -1,585 +1,930 @@ meta.languageCode: en meta.languageName: English -count.tracks: "{TRACKS}" -count.tracks.withUnit.zero: "" -count.tracks.withUnit.one: "{TRACKS} track" -count.tracks.withUnit.two: "" -count.tracks.withUnit.few: "" -count.tracks.withUnit.many: "" -count.tracks.withUnit.other: "{TRACKS} tracks" - -count.additionalFiles: "{FILES}" -count.additionalFiles.withUnit.zero: "" -count.additionalFiles.withUnit.one: "{FILES} file" -count.additionalFiles.withUnit.two: "" -count.additionalFiles.withUnit.few: "" -count.additionalFiles.withUnit.many: "" -count.additionalFiles.withUnit.other: "{FILES} files" - -count.albums: "{ALBUMS}" -count.albums.withUnit.zero: "" -count.albums.withUnit.one: "{ALBUMS} album" -count.albums.withUnit.two: "" -count.albums.withUnit.few: "" -count.albums.withUnit.many: "" -count.albums.withUnit.other: "{ALBUMS} albums" - -count.artworks: "{ARTWORKS}" -count.artworks.withUnit.zero: "" -count.artworks.withUnit.one: "{ARTWORKS} artwork" -count.artworks.withUnit.two: "" -count.artworks.withUnit.few: "" -count.artworks.withUnit.many: "" -count.artworks.withUnit.other: "{ARTWORKS} artworks" - -count.commentaryEntries: "{ENTRIES}" -count.commentaryEntries.withUnit.zero: "" -count.commentaryEntries.withUnit.one: "{ENTRIES} entry" -count.commentaryEntries.withUnit.two: "" -count.commentaryEntries.withUnit.few: "" -count.commentaryEntries.withUnit.many: "" -count.commentaryEntries.withUnit.other: "{ENTRIES} entries" - -count.contributions: "{CONTRIBUTIONS}" -count.contributions.withUnit.zero: "" -count.contributions.withUnit.one: "{CONTRIBUTIONS} contribution" -count.contributions.withUnit.two: "" -count.contributions.withUnit.few: "" -count.contributions.withUnit.many: "" -count.contributions.withUnit.other: "{CONTRIBUTIONS} contributions" - -count.coverArts: "{COVER_ARTS}" -count.coverArts.withUnit.zero: "" -count.coverArts.withUnit.one: "{COVER_ARTS} cover art" -count.coverArts.withUnit.two: "" -count.coverArts.withUnit.few: "" -count.coverArts.withUnit.many: "" -count.coverArts.withUnit.other: "{COVER_ARTS} cover arts" - -count.flashes: "{FLASHES}" -count.flashes.withUnit.zero: "" -count.flashes.withUnit.one: "{FLASHES} flashes & games" -count.flashes.withUnit.two: "" -count.flashes.withUnit.few: "" -count.flashes.withUnit.many: "" -count.flashes.withUnit.other: "{FLASHES} flashes & games" - -count.timesReferenced: "{TIMES_REFERENCED}" -count.timesReferenced.withUnit.zero: "" -count.timesReferenced.withUnit.one: "{TIMES_REFERENCED} time referenced" -count.timesReferenced.withUnit.two: "" -count.timesReferenced.withUnit.few: "" -count.timesReferenced.withUnit.many: "" -count.timesReferenced.withUnit.other: "{TIMES_REFERENCED} times referenced" - -count.words: "{WORDS}" -count.words.thousand: "{WORDS}k" -count.words.withUnit.zero: "" -count.words.withUnit.one: "{WORDS} word" -count.words.withUnit.two: "" -count.words.withUnit.few: "" -count.words.withUnit.many: "" -count.words.withUnit.other: "{WORDS} words" - -count.timesUsed: "{TIMES_USED}" -count.timesUsed.withUnit.zero: "" -count.timesUsed.withUnit.one: "used {TIMES_USED} time" -count.timesUsed.withUnit.two: "" -count.timesUsed.withUnit.few: "" -count.timesUsed.withUnit.many: "" -count.timesUsed.withUnit.other: "used {TIMES_USED} times" - -count.index.zero: "" -count.index.one: "{INDEX}st" -count.index.two: "{INDEX}nd" -count.index.few: "{INDEX}rd" -count.index.many: "" -count.index.other: "{INDEX}th" - -count.duration.hours: "{HOURS}:{MINUTES}:{SECONDS}" -count.duration.hours.withUnit: "{HOURS}:{MINUTES}:{SECONDS} hours" -count.duration.minutes: "{MINUTES}:{SECONDS}" -count.duration.minutes.withUnit: "{MINUTES}:{SECONDS} minutes" -count.duration.approximate: "~{DURATION}" -count.duration.missing: "_:__" - -count.fileSize.terabytes: "{TERABYTES} TB" -count.fileSize.gigabytes: "{GIGABYTES} GB" -count.fileSize.megabytes: "{MEGABYTES} MB" -count.fileSize.kilobytes: "{KILOBYTES} kB" -count.fileSize.bytes: "{BYTES} bytes" - -releaseInfo.by: "By {ARTISTS}." -releaseInfo.from: "From {ALBUM}." -releaseInfo.coverArtBy: "Cover art by {ARTISTS}." -releaseInfo.wallpaperArtBy: "Wallpaper art by {ARTISTS}." -releaseInfo.bannerArtBy: "Banner art by {ARTISTS}." -releaseInfo.released: "Released {DATE}." -releaseInfo.artReleased: "Art released {DATE}." -releaseInfo.addedToWiki: "Added to wiki {DATE}." -releaseInfo.duration: "Duration: {DURATION}." -releaseInfo.viewCommentary: "View {LINK}!" -releaseInfo.viewCommentary.link: "commentary page" -releaseInfo.viewGallery: "View {LINK}!" -releaseInfo.viewGallery.link: "gallery page" -releaseInfo.viewGalleryOrCommentary: "View {GALLERY} or {COMMENTARY}!" -releaseInfo.viewGalleryOrCommentary.gallery: "gallery page" -releaseInfo.viewGalleryOrCommentary.commentary: "commentary page" -releaseInfo.viewOriginalFile: "View {LINK}." -releaseInfo.viewOriginalFile.withSize: "View {LINK} ({SIZE})." -releaseInfo.viewOriginalFile.link: "original file" -releaseInfo.viewOriginalFile.sizeWarning: >- - (Heads up! If you're on a mobile plan, this is a large download.) -releaseInfo.listenOn: "Listen on {LINKS}." -releaseInfo.listenOn.noLinks: >- - This wiki doesn't have any listening links for {NAME}. -releaseInfo.visitOn: "Visit on {LINKS}." -releaseInfo.playOn: "Play on {LINKS}." -releaseInfo.readCommentary: "Read {LINK}." -releaseInfo.readCommentary.link: "artist commentary" -releaseInfo.alsoReleasedAs: "Also released as:" -releaseInfo.alsoReleasedAs.item: "{TRACK} (on {ALBUM})" -releaseInfo.contributors: "Contributors:" -releaseInfo.tracksReferenced: "Tracks that {TRACK} references:" -releaseInfo.tracksThatReference: "Tracks that reference {TRACK}:" -releaseInfo.tracksSampled: "Tracks that {TRACK} samples:" -releaseInfo.tracksThatSample: "Tracks that sample {TRACK}:" -releaseInfo.flashesThatFeature: "Flashes & games that feature {TRACK}:" -releaseInfo.flashesThatFeature.item: "{FLASH}" -releaseInfo.flashesThatFeature.item.asDifferentRelease: "{FLASH} (as {TRACK})" -releaseInfo.tracksFeatured: "Tracks that {FLASH} features:" -releaseInfo.lyrics: "Lyrics:" -releaseInfo.artistCommentary: "Artist commentary:" -releaseInfo.artistCommentary.seeOriginalRelease: "See {ORIGINAL}!" -releaseInfo.artTags: "Tags:" -releaseInfo.artTags.inline: "Tags: {TAGS}" -releaseInfo.additionalFiles.shortcut: "View {ANCHOR_LINK}: {TITLES}" -releaseInfo.additionalFiles.shortcut.anchorLink: "additional files" -releaseInfo.additionalFiles.heading: "View or download {ADDITIONAL_FILES}:" -releaseInfo.additionalFiles.entry: "{TITLE}" -releaseInfo.additionalFiles.entry.withDescription: "{TITLE}: {DESCRIPTION}" -releaseInfo.additionalFiles.file: "{FILE}" -releaseInfo.additionalFiles.file.withSize: "{FILE} ({SIZE})" -releaseInfo.sheetMusicFiles.shortcut: "Download {LINK}." -releaseInfo.sheetMusicFiles.shortcut.link: "sheet music files" -releaseInfo.sheetMusicFiles.heading: "Print or download sheet music files:" -releaseInfo.midiProjectFiles.shortcut: "Download {LINK}." -releaseInfo.midiProjectFiles.shortcut.link: "MIDI/project files" -releaseInfo.midiProjectFiles.heading: "Download MIDI/project files:" -releaseInfo.note: "Context notes:" - -trackList.section.withDuration: "{SECTION} ({DURATION}):" -trackList.group: "From {GROUP}:" -trackList.group.fromOther: "From somewhere else:" -trackList.item.withDuration: "({DURATION}) {TRACK}" -trackList.item.withDuration.withArtists: "({DURATION}) {TRACK} {BY}" -trackList.item.withArtists: "{TRACK} {BY}" -trackList.item.withArtists.by: "by {ARTISTS}" -trackList.item.rerelease: "{TRACK} (re-release)" - -misc.alt.albumCover: "album cover" -misc.alt.albumBanner: "album banner" -misc.alt.trackCover: "track cover" -misc.alt.artistAvatar: "artist avatar" -misc.alt.flashArt: "flash art" - -misc.artistLink: "{ARTIST}" -misc.artistLink.withContribution: "{ARTIST} ({CONTRIB})" -misc.artistLink.withExternalLinks: "{ARTIST} ({LINKS})" -misc.artistLink.withContribution.withExternalLinks: "{ARTIST} ({CONTRIB}) ({LINKS})" - -misc.chronology.seeArtistPages: "(See artist pages for chronology info!)" -misc.chronology.heading.coverArt: "{INDEX} cover art by {ARTIST}" -misc.chronology.heading.flash: "{INDEX} flash/game by {ARTIST}" -misc.chronology.heading.track: "{INDEX} track by {ARTIST}" -misc.chronology.withNavigation: "{HEADING} ({NAVIGATION})" - -misc.external.domain: "External ({DOMAIN})" -misc.external.local: "Wiki Archive (local upload)" -misc.external.bandcamp: "Bandcamp" -misc.external.bandcamp.domain: "Bandcamp ({DOMAIN})" -misc.external.deviantart: "DeviantArt" -misc.external.instagram: "Instagram" -misc.external.mastodon: "Mastodon" -misc.external.mastodon.domain: "Mastodon ({DOMAIN})" -misc.external.newgrounds: "Newgrounds" -misc.external.patreon: "Patreon" -misc.external.poetryFoundation: "Poetry Foundation" -misc.external.soundcloud: "SoundCloud" -misc.external.spotify: "Spotify" -misc.external.tumblr: "Tumblr" -misc.external.twitter: "Twitter" -misc.external.wikipedia: "Wikipedia" -misc.external.youtube: "YouTube" -misc.external.youtube.playlist: "YouTube (playlist)" -misc.external.youtube.fullAlbum: "YouTube (full album)" -misc.external.flash.bgreco: "{LINK} (HQ Audio)" -misc.external.flash.homestuck.page: "{LINK} (page {PAGE})" -misc.external.flash.homestuck.secret: "{LINK} (secret page)" -misc.external.flash.youtube: "{LINK} (on any device)" - -misc.missingImage: "(This image file is missing)" -misc.missingLinkContent: "(Missing link content)" - -misc.nav.previous: "Previous" -misc.nav.next: "Next" -misc.nav.info: "Info" -misc.nav.gallery: "Gallery" - -misc.pageTitle: "{TITLE}" -misc.pageTitle.withWikiName: "{TITLE} | {WIKI_NAME}" - -misc.skippers.skipTo: "Skip to:" -misc.skippers.content: "Content" -misc.skippers.sidebar: "Sidebar" -misc.skippers.sidebar.left: "Sidebar (left)" -misc.skippers.sidebar.right: "Sidebar (right)" -misc.skippers.header: "Header" -misc.skippers.footer: "Footer" -misc.skippers.tracks: "Tracks" -misc.skippers.art: "Artworks" -misc.skippers.flashes: "Flashes & Games" -misc.skippers.contributors: "Contributors" -misc.skippers.references: "References..." -misc.skippers.referencedBy: "Referenced by..." -misc.skippers.samples: "Samples..." -misc.skippers.sampledBy: "Sampled by..." -misc.skippers.features: "Features..." -misc.skippers.featuredIn: "Featured in..." -misc.skippers.lyrics: "Lyrics" -misc.skippers.sheetMusicFiles: "Sheet music files" -misc.skippers.midiProjectFiles: "MIDI/project files" -misc.skippers.additionalFiles: "Additional files" -misc.skippers.commentary: "Commentary" -misc.skippers.artistCommentary: "Commentary" - -misc.socialEmbed.heading: "{WIKI_NAME} | {HEADING}" - -misc.jumpTo: "Jump to:" -misc.jumpTo.withLinks: "Jump to: {LINKS}." - -misc.contentWarnings: "cw: {WARNINGS}" -misc.contentWarnings.reveal: "click to show" - -misc.albumGrid.details: "({TRACKS}, {TIME})" -misc.albumGrid.details.coverArtists: "(Illust. {ARTISTS})" -misc.albumGrid.details.otherCoverArtists: "(With {ARTISTS})" -misc.albumGrid.noCoverArt: "{ALBUM}" -misc.albumGalleryGrid.noCoverArt: "{NAME}" - -misc.uiLanguage: "UI Language: {LANGUAGES" - -homepage.title: "{TITLE}" -homepage.news.title: News -homepage.news.entry.viewRest: "(View rest of entry!)" - -albumSidebar.trackList.fallbackSectionName: "Track list" -albumSidebar.trackList.group: "{GROUP}" -albumSidebar.trackList.group.withRange: "{GROUP} ({RANGE})" -albumSidebar.trackList.item: "{TRACK}" -albumSidebar.groupBox.title: "{GROUP}" -albumSidebar.groupBox.next: "Next: {ALBUM}" -albumSidebar.groupBox.previous: "Previous: {ALBUM}" - -albumPage.title: "{ALBUM}" -albumPage.nav.album: "{ALBUM}" -albumPage.nav.randomTrack: "Random Track" -albumPage.nav.gallery: "Gallery" -albumPage.nav.commentary: "Commentary" -albumPage.socialEmbed.heading: "{GROUP}" -albumPage.socialEmbed.title: "{ALBUM}" -albumPage.socialEmbed.body.withDuration: "{DURATION}." -albumPage.socialEmbed.body.withTracks: "{TRACKS}." -albumPage.socialEmbed.body.withReleaseDate: Released {DATE}. -albumPage.socialEmbed.body.withDuration.withTracks: "{DURATION}, {TRACKS}." -albumPage.socialEmbed.body.withDuration.withReleaseDate: "{DURATION}. Released {DATE}." -albumPage.socialEmbed.body.withTracks.withReleaseDate: "{TRACKS}. Released {DATE}." -albumPage.socialEmbed.body.withDuration.withTracks.withReleaseDate: "{DURATION}, {TRACKS}. - Released {DATE}." - -albumGalleryPage.title: "{ALBUM} - Gallery" -albumGalleryPage.statsLine: >- - {TRACKS} totaling {DURATION}. -albumGalleryPage.statsLine.withDate: >- - {TRACKS} totaling {DURATION}. Released {DATE}. -albumGalleryPage.coverArtistsLine: >- - All track artwork by {ARTISTS}. -albumGalleryPage.noTrackArtworksLine: >- - This album doesn't have any track artwork. - -albumCommentaryPage.title: "{ALBUM} - Commentary" -albumCommentaryPage.infoLine: "{WORDS} across {ENTRIES}." -albumCommentaryPage.nav.album: "Album: {ALBUM}" -albumCommentaryPage.entry.title.albumCommentary: "Album commentary" -albumCommentaryPage.entry.title.trackCommentary: "{TRACK}" - -artistPage.title: "{ARTIST}" -artistPage.creditList.album: "{ALBUM}" -artistPage.creditList.album.withDate: "{ALBUM} ({DATE})" -artistPage.creditList.album.withDuration: "{ALBUM} ({DURATION})" -artistPage.creditList.album.withDate.withDuration: "{ALBUM} ({DATE}; {DURATION})" -artistPage.creditList.flashAct: "{ACT}" -artistPage.creditList.flashAct.withDate: "{ACT} ({DATE})" -artistPage.creditList.flashAct.withDateRange: "{ACT} ({DATE_RANGE})" -artistPage.creditList.entry.track: "{TRACK}" -artistPage.creditList.entry.track.withDuration: "({DURATION}) {TRACK}" -artistPage.creditList.entry.album.coverArt: "(cover art)" -artistPage.creditList.entry.album.wallpaperArt: "(wallpaper art)" -artistPage.creditList.entry.album.bannerArt: "(banner art)" -artistPage.creditList.entry.album.commentary: "(album commentary)" -artistPage.creditList.entry.flash: "{FLASH}" -artistPage.creditList.entry.rerelease: "{ENTRY} (re-release)" -artistPage.creditList.entry.withContribution: "{ENTRY} ({CONTRIBUTION})" -artistPage.creditList.entry.withArtists: "{ENTRY} (with {ARTISTS})" -artistPage.creditList.entry.withArtists.withContribution: "{ENTRY} ({CONTRIBUTION}; with {ARTISTS})" -artistPage.contributedDurationLine: >- - {ARTIST} has contributed {DURATION} of music shared on this wiki. -artistPage.musicGroupsLine: "Contributed music to groups: {GROUPS}" -artistPage.artGroupsLine: "Contributed art to groups: {GROUPS}" -artistPage.groupsLine.item.withCount: "{GROUP} ({COUNT})" -artistPage.groupsLine.item.withDuration: "{GROUP} ({DURATION})" -artistPage.groupContributions.title.music: "Contributed music to groups:" -artistPage.groupContributions.title.artworks: "Contributed artworks to groups:" -artistPage.groupContributions.title.withSortButton: "{TITLE} ({SORT})" -artistPage.groupContributions.title.sorting.count: "Sorting by count." -artistPage.groupContributions.title.sorting.duration: "Sorting by duration." -artistPage.groupContributions.item.countAccent: "({COUNT})" -artistPage.groupContributions.item.durationAccent: "({DURATION})" -artistPage.groupContributions.item.countDurationAccent: "({COUNT} — {DURATION})" -artistPage.groupContributions.item.durationCountAccent: "({DURATION} — {COUNT})" -artistPage.trackList.title: "Tracks" -artistPage.artList.title: "Artworks" -artistPage.flashList.title: "Flashes & Games" -artistPage.commentaryList.title: "Commentary" -artistPage.viewArtGallery: "View {LINK}!" -artistPage.viewArtGallery.orBrowseList: "View {LINK}! Or browse the list:" -artistPage.viewArtGallery.link: "art gallery" -artistPage.nav.artist: "Artist: {ARTIST}" -artistGalleryPage.title: "{ARTIST} - Gallery" -artistGalleryPage.infoLine: "Contributed to {COVER_ARTS}." - -commentaryIndex.title: "Commentary" -commentaryIndex.infoLine: "{WORDS} across {ENTRIES}, in all." -commentaryIndex.albumList.title: "Choose an album:" -commentaryIndex.albumList.item: "{ALBUM} ({WORDS} across {ENTRIES})" - -flashIndex.title: "Flashes & Games" - -flashPage.title: "{FLASH}" -flashPage.nav.flash: "{FLASH}" - -flashSidebar.flashList.flashesInThisAct: "Flashes in this act" -flashSidebar.flashList.entriesInThisSection: "Entries in this section" - -groupSidebar.title: "Groups" -groupSidebar.groupList.category: "{CATEGORY}" -groupSidebar.groupList.item: "{GROUP}" - -groupPage.nav.group: "Group: {GROUP}" - -groupInfoPage.title: "{GROUP}" -groupInfoPage.viewAlbumGallery: "View {LINK}! Or browse the list:" -groupInfoPage.viewAlbumGallery.link: "album gallery" -groupInfoPage.albumList.title: "Albums" -groupInfoPage.albumList.item: "({YEAR}) {ALBUM}" -groupInfoPage.albumList.item.withoutYear: "{ALBUM}" -groupInfoPage.albumList.item.withAccent: "{ITEM} {ACCENT}" -groupInfoPage.albumList.item.otherGroupAccent: "(from {GROUP})" - -groupGalleryPage.title: "{GROUP} - Gallery" -groupGalleryPage.infoLine: "{TRACKS} across {ALBUMS}, totaling {TIME}." - -listingIndex.title: "Listings" -listingIndex.infoLine: >- - {WIKI}: {TRACKS} across {ALBUMS}, totaling {DURATION}. -listingIndex.exploreList: >- - Feel free to explore any of the listings linked below and in the sidebar! - -listingPage.target.album: "Albums" -listingPage.target.artist: "Artists" -listingPage.target.group: "Groups" -listingPage.target.track: "Tracks" -listingPage.target.tag: "Tags" -listingPage.target.other: "Other" - -listingPage.listingsFor: "Listings for {TARGET}: {LISTINGS}" -listingPage.seeAlso: "Also check out: {LISTINGS}" - -listingPage.listAlbums.byName.title: "Albums - by Name" -listingPage.listAlbums.byName.title.short: "...by Name" -listingPage.listAlbums.byName.item: "{ALBUM} ({TRACKS})" -listingPage.listAlbums.byTracks.title: "Albums - by Tracks" -listingPage.listAlbums.byTracks.title.short: "...by Tracks" -listingPage.listAlbums.byTracks.item: "{ALBUM} ({TRACKS})" -listingPage.listAlbums.byDuration.title: "Albums - by Duration" -listingPage.listAlbums.byDuration.title.short: "...by Duration" -listingPage.listAlbums.byDuration.item: "{ALBUM} ({DURATION})" -listingPage.listAlbums.byDate.title: "Albums - by Date" -listingPage.listAlbums.byDate.title.short: "...by Date" -listingPage.listAlbums.byDate.item: "{ALBUM} ({DATE})" -listingPage.listAlbums.byDateAdded.title.short: "...by Date Added to Wiki" -listingPage.listAlbums.byDateAdded.title: "Albums - by Date Added to Wiki" -listingPage.listAlbums.byDateAdded.chunk.title: "{DATE}" -listingPage.listAlbums.byDateAdded.chunk.item: "{ALBUM}" - -listingPage.listArtists.byName.title: "Artists - by Name" -listingPage.listArtists.byName.title.short: "...by Name" -listingPage.listArtists.byName.item: "{ARTIST} ({CONTRIBUTIONS})" -listingPage.listArtists.byContribs.title: "Artists - by Contributions" -listingPage.listArtists.byContribs.title.short: "...by Contributions" -listingPage.listArtists.byContribs.item: "{ARTIST} ({CONTRIBUTIONS})" -listingPage.listArtists.byCommentary.title: "Artists - by Commentary Entries" -listingPage.listArtists.byCommentary.title.short: "...by Commentary Entries" -listingPage.listArtists.byCommentary.item: "{ARTIST} ({ENTRIES})" -listingPage.listArtists.byDuration.title: "Artists - by Duration" -listingPage.listArtists.byDuration.title.short: "...by Duration" -listingPage.listArtists.byDuration.item: "{ARTIST} ({DURATION})" -listingPage.listArtists.byLatest.title: "Artists - by Latest Contribution" -listingPage.listArtists.byLatest.title.short: "...by Latest Contribution" -listingPage.listArtists.byLatest.chunk.title.album: "{ALBUM} ({DATE})" -listingPage.listArtists.byLatest.chunk.title.flash: "{FLASH} ({DATE})" -listingPage.listArtists.byLatest.chunk.item: "{ARTIST}" -listingPage.listArtists.byLatest.dateless.title: "These artists' contributions aren't dated:" -listingPage.listArtists.byLatest.dateless.item: "{ARTIST}" - -listingPage.listGroups.byName.title: "Groups - by Name" -listingPage.listGroups.byName.title.short: "...by Name" -listingPage.listGroups.byName.item: "{GROUP} ({GALLERY})" -listingPage.listGroups.byName.item.gallery: "Gallery" -listingPage.listGroups.byCategory.title: "Groups - by Category" -listingPage.listGroups.byCategory.title.short: "...by Category" -listingPage.listGroups.byCategory.chunk.title: "{CATEGORY}" -listingPage.listGroups.byCategory.chunk.item: "{GROUP} ({GALLERY})" -listingPage.listGroups.byCategory.chunk.item.gallery: "Gallery" -listingPage.listGroups.byAlbums.title: "Groups - by Albums" -listingPage.listGroups.byAlbums.title.short: "...by Albums" -listingPage.listGroups.byAlbums.item: "{GROUP} ({ALBUMS})" -listingPage.listGroups.byTracks.title: "Groups - by Tracks" -listingPage.listGroups.byTracks.title.short: "...by Tracks" -listingPage.listGroups.byTracks.item: "{GROUP} ({TRACKS})" -listingPage.listGroups.byDuration.title: "Groups - by Duration" -listingPage.listGroups.byDuration.title.short: "...by Duration" -listingPage.listGroups.byDuration.item: "{GROUP} ({DURATION})" -listingPage.listGroups.byLatest.title: "Groups - by Latest Album" -listingPage.listGroups.byLatest.title.short: "...by Latest Album" -listingPage.listGroups.byLatest.item: "{GROUP} ({DATE})" - -listingPage.listTracks.byName.title: "Tracks - by Name" -listingPage.listTracks.byName.title.short: "...by Name" -listingPage.listTracks.byName.item: "{TRACK}" -listingPage.listTracks.byAlbum.title: "Tracks - by Album" -listingPage.listTracks.byAlbum.title.short: "...by Album" -listingPage.listTracks.byAlbum.chunk.title: "{ALBUM}" -listingPage.listTracks.byAlbum.chunk.item: "{TRACK}" -listingPage.listTracks.byDate.title: "Tracks - by Date" -listingPage.listTracks.byDate.title.short: "...by Date" -listingPage.listTracks.byDate.chunk.title: "{ALBUM} ({DATE})" -listingPage.listTracks.byDate.chunk.item: "{TRACK}" -listingPage.listTracks.byDate.chunk.item.rerelease: "{TRACK} (re-release)" -listingPage.listTracks.byDuration.title: "Tracks - by Duration" -listingPage.listTracks.byDuration.title.short: "...by Duration" -listingPage.listTracks.byDuration.item: "{TRACK} ({DURATION})" -listingPage.listTracks.byDurationInAlbum.title: "Tracks - by Duration (in Album)" -listingPage.listTracks.byDurationInAlbum.title.short: "...by Duration (in Album)" -listingPage.listTracks.byDurationInAlbum.chunk.title: "{ALBUM}" -listingPage.listTracks.byDurationInAlbum.chunk.item: "{TRACK} ({DURATION})" -listingPage.listTracks.byTimesReferenced.title: "Tracks - by Times Referenced" -listingPage.listTracks.byTimesReferenced.title.short: "...by Times Referenced" -listingPage.listTracks.byTimesReferenced.item: "{TRACK} ({TIMES_REFERENCED})" -listingPage.listTracks.inFlashes.byAlbum.title: "Tracks - in Flashes & Games (by Album)" -listingPage.listTracks.inFlashes.byAlbum.title.short: "...in Flashes & Games (by Album)" -listingPage.listTracks.inFlashes.byAlbum.chunk.title: "{ALBUM}" -listingPage.listTracks.inFlashes.byAlbum.chunk.item: "{TRACK} (in {FLASHES})" -listingPage.listTracks.inFlashes.byFlash.title: "Tracks - in Flashes & Games (by Flash)" -listingPage.listTracks.inFlashes.byFlash.title.short: "...in Flashes & Games (by Flash)" -listingPage.listTracks.inFlashes.byFlash.chunk.title: "{FLASH}" -listingPage.listTracks.inFlashes.byFlash.chunk.item: "{TRACK} (from {ALBUM})" -listingPage.listTracks.withLyrics.title: "Tracks - with Lyrics" -listingPage.listTracks.withLyrics.title.short: "...with Lyrics" -listingPage.listTracks.withLyrics.chunk.title: "{ALBUM}" -listingPage.listTracks.withLyrics.chunk.title.withDate: "{ALBUM} ({DATE})" -listingPage.listTracks.withLyrics.chunk.item: "{TRACK}" -listingPage.listTracks.withSheetMusicFiles.title: "Tracks - with Sheet Music Files" -listingPage.listTracks.withSheetMusicFiles.title.short: "...with Sheet Music Files" -listingPage.listTracks.withSheetMusicFiles.chunk.title: "{ALBUM}" -listingPage.listTracks.withSheetMusicFiles.chunk.title.withDate: "{ALBUM} ({DATE})" -listingPage.listTracks.withSheetMusicFiles.chunk.item: "{TRACK}" -listingPage.listTracks.withMidiProjectFiles.title: "Tracks - with MIDI & Project Files" -listingPage.listTracks.withMidiProjectFiles.title.short: "...with MIDI & Project Files" -listingPage.listTracks.withMidiProjectFiles.chunk.title: "{ALBUM}" -listingPage.listTracks.withMidiProjectFiles.chunk.title.withDate: "{ALBUM} ({DATE})" -listingPage.listTracks.withMidiProjectFiles.chunk.item: "{TRACK}" - -listingPage.listTags.byName.title: "Tags - by Name" -listingPage.listTags.byName.title.short: "...by Name" -listingPage.listTags.byName.item: "{TAG} ({TIMES_USED})" -listingPage.listTags.byUses.title: "Tags - by Uses" -listingPage.listTags.byUses.title.short: "...by Uses" -listingPage.listTags.byUses.item: "{TAG} ({TIMES_USED})" - -listingPage.other.allSheetMusic.title: "All Sheet Music" -listingPage.other.allSheetMusic.title.short: "All Sheet Music" -listingPage.other.allSheetMusic.albumFiles: "Album sheet music:" -listingPage.other.allSheetMusic.file: "{TITLE}" -listingPage.other.allSheetMusic.file.withMultipleFiles: "{TITLE} ({FILES})" -listingPage.other.allMidiProjectFiles.title: "All MIDI/Project Files" -listingPage.other.allMidiProjectFiles.title.short: "All MIDI/Project Files" -listingPage.other.allMidiProjectFiles.albumFiles: "Album MIDI/project files:" -listingPage.other.allMidiProjectFiles.file: "{TITLE}" -listingPage.other.allMidiProjectFiles.file.withMultipleFiles: "{TITLE} ({FILES})" -listingPage.other.allAdditionalFiles.title: "All Additional Files" -listingPage.other.allAdditionalFiles.title.short: "All Additional Files" -listingPage.other.allAdditionalFiles.albumFiles: "Album additional files:" -listingPage.other.allAdditionalFiles.file: "{TITLE}" -listingPage.other.allAdditionalFiles.file.withMultipleFiles: "{TITLE} ({FILES})" - -listingPage.other.randomPages.title: "Random Pages" -listingPage.other.randomPages.title.short: "Random Pages" -listingPage.other.randomPages.chooseLinkLine: >- - Choose a link to go to a random page in that category or album! - If your browser doesn't support relatively modern JavaScript - or you've disabled it, these links won't work - sorry. -listingPage.other.randomPages.dataLoadingLine: >- - (Data files are downloading in the background! Please wait for data to load.) -listingPage.other.randomPages.dataLoadedLine: >- - (Data files have finished being downloaded. The links should work!) -listingPage.other.randomPages.misc: "Miscellaneous:" -listingPage.other.randomPages.misc.randomArtist: "Random Artist" -listingPage.other.randomPages.misc.atLeastTwoContributions: "at least 2 contributions" -listingPage.other.randomPages.misc.randomAlbumWholeSite: "Random Album (whole site)" -listingPage.other.randomPages.misc.randomTrackWholeSite: "Random Track (whole site)" -listingPage.other.randomPages.group: "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})" -listingPage.other.randomPages.group.randomAlbum: "Random Album" -listingPage.other.randomPages.group.randomTrack: "Random Track" -listingPage.other.randomPages.album: "{ALBUM}" - -listingPage.misc.trackContributors: "Track Contributors" -listingPage.misc.artContributors: "Art Contributors" -listingPage.misc.flashContributors: "Flash & Game Contributors" -listingPage.misc.artAndFlashContributors: "Art & Flash Contributors" - -newsIndex.title: "News" -newsIndex.entry.viewRest: "(View rest of entry!)" - -newsEntryPage.title: "{ENTRY}" -newsEntryPage.published: "(Published {DATE}.)" - -redirectPage.title: "Moved to {TITLE}" -redirectPage.infoLine: "This page has been moved to {TARGET}." - -tagPage.title: "{TAG}" -tagPage.infoLine: "Appears in {COVER_ARTS}." -tagPage.nav.tag: "Tag: {TAG}" - -trackPage.title: "{TRACK}" -trackPage.referenceList.fandom: "Fandom:" -trackPage.referenceList.official: "Official:" -trackPage.nav.track: "{TRACK}" -trackPage.nav.track.withNumber: "{NUMBER}. {TRACK}" -trackPage.nav.random: "Random" -trackPage.socialEmbed.heading: "{ALBUM}" -trackPage.socialEmbed.title: "{TRACK}" -trackPage.socialEmbed.body.withArtists.withCoverArtists: "By {ARTISTS}; art by {COVER_ARTISTS}." -trackPage.socialEmbed.body.withArtists: "By {ARTISTS}." -trackPage.socialEmbed.body.withCoverArtists: "Art by {COVER_ARTISTS}." +count: + tracks: + _: "{TRACKS}" + withUnit: + zero: "" + one: "{TRACKS} track" + two: "" + few: "" + many: "" + other: "{TRACKS} tracks" + + additionalFiles: + _: "{FILES}" + withUnit: + zero: "" + one: "{FILES} file" + two: "" + few: "" + many: "" + other: "{FILES} files" + + albums: + _: "{ALBUMS}" + withUnit: + zero: "" + one: "{ALBUMS} album" + two: "" + few: "" + many: "" + other: "{ALBUMS} albums" + + artworks: + _: "{ARTWORKS}" + withUnit: + zero: "" + one: "{ARTWORKS} artwork" + two: "" + few: "" + many: "" + other: "{ARTWORKS} artworks" + + commentaryEntries: + _: "{ENTRIES}" + withUnit: + zero: "" + one: "{ENTRIES} entry" + two: "" + few: "" + many: "" + other: "{ENTRIES} entries" + + contributions: + _: "{CONTRIBUTIONS}" + withUnit: + zero: "" + one: "{CONTRIBUTIONS} contribution" + two: "" + few: "" + many: "" + other: "{CONTRIBUTIONS} contributions" + + coverArts: + _: "{COVER_ARTS}" + withUnit: + zero: "" + one: "{COVER_ARTS} cover art" + two: "" + few: "" + many: "" + other: "{COVER_ARTS} cover arts" + + flashes: + _: "{FLASHES}" + withUnit: + zero: "" + one: "{FLASHES} flashes & games" + two: "" + few: "" + many: "" + other: "{FLASHES} flashes & games" + + timesReferenced: + _: "{TIMES_REFERENCED}" + withUnit: + zero: "" + one: "{TIMES_REFERENCED} time referenced" + two: "" + few: "" + many: "" + other: "{TIMES_REFERENCED} times referenced" + + words: + _: "{WORDS}" + thousand: "{WORDS}k" + withUnit: + zero: "" + one: "{WORDS} word" + two: "" + few: "" + many: "" + other: "{WORDS} words" + + timesUsed: + _: "{TIMES_USED}" + withUnit: + zero: "" + one: "used {TIMES_USED} time" + two: "" + few: "" + many: "" + other: "used {TIMES_USED} times" + + index: + zero: "" + one: "{INDEX}st" + two: "{INDEX}nd" + few: "{INDEX}rd" + many: "" + other: "{INDEX}th" + + duration: + missing: "_:__" + approximate: "~{DURATION}" + hours: + _: "{HOURS}:{MINUTES}:{SECONDS}" + withUnit: "{HOURS}:{MINUTES}:{SECONDS} hours" + minutes: + _: "{MINUTES}:{SECONDS}" + withUnit: "{MINUTES}:{SECONDS} minutes" + + fileSize: + terabytes: "{TERABYTES} TB" + gigabytes: "{GIGABYTES} GB" + megabytes: "{MEGABYTES} MB" + kilobytes: "{KILOBYTES} kB" + bytes: "{BYTES} bytes" + +releaseInfo: + by: "By {ARTISTS}." + from: "From {ALBUM}." + + coverArtBy: "Cover art by {ARTISTS}." + wallpaperArtBy: "Wallpaper art by {ARTISTS}." + bannerArtBy: "Banner art by {ARTISTS}." + + released: "Released {DATE}." + artReleased: "Art released {DATE}." + addedToWiki: "Added to wiki {DATE}." + + duration: "Duration: {DURATION}." + + viewCommentary: + _: "View {LINK}!" + link: "commentary page" + + viewGallery: + _: "View {LINK}!" + link: "gallery page" + + viewGalleryOrCommentary: + _: "View {GALLERY} or {COMMENTARY}!" + gallery: "gallery page" + commentary: "commentary page" + + viewOriginalFile: + _: "View {LINK}." + withSize: "View {LINK} ({SIZE})." + link: "original file" + sizeWarning: >- + (Heads up! If you're on a mobile plan, this is a large download.) + + listenOn: + _: "Listen on {LINKS}." + noLinks: >- + This wiki doesn't have any listening links for {NAME}. + + visitOn: "Visit on {LINKS}." + playOn: "Play on {LINKS}." + + readCommentary: + _: "Read {LINK}." + link: "artist commentary" + + alsoReleasedAs: + _: "Also released as:" + item: "{TRACK} (on {ALBUM})" + + contributors: "Contributors:" + + tracksReferenced: "Tracks that {TRACK} references:" + tracksThatReference: "Tracks that reference {TRACK}:" + tracksSampled: "Tracks that {TRACK} samples:" + tracksThatSample: "Tracks that sample {TRACK}:" + + flashesThatFeature: + _: "Flashes & games that feature {TRACK}:" + item: + _: "{FLASH}" + asDifferentRelease: "{FLASH} (as {TRACK})" + + tracksFeatured: "Tracks that {FLASH} features:" + + lyrics: "Lyrics:" + note: "Context notes:" + + artistCommentary: + _: "Artist commentary:" + seeOriginalRelease: "See {ORIGINAL}!" + + artTags: + _: "Tags:" + inline: "Tags: {TAGS}" + + additionalFiles: + heading: "View or download {ADDITIONAL_FILES}:" + + entry: + _: "{TITLE}" + withDescription: "{TITLE}: {DESCRIPTION}" + + file: + _: "{FILE}" + withSize: "{FILE} ({SIZE})" + + shortcut: + _: "View {ANCHOR_LINK}: {TITLES}" + anchorLink: "additional files" + + sheetMusicFiles: + heading: "Print or download sheet music files:" + + shortcut: + _: "Download {LINK}." + link: "sheet music files" + + midiProjectFiles: + heading: "Download MIDI/project files:" + + shortcut: + _: "Download {LINK}." + link: "MIDI/project files" + +trackList: + section: + withDuration: "{SECTION} ({DURATION}):" + + group: + _: "From {GROUP}:" + fromOther: "From somewhere else:" + + item: + withDuration: "({DURATION}) {TRACK}" + withDuration.withArtists: "({DURATION}) {TRACK} {BY}" + withArtists: "{TRACK} {BY}" + withArtists.by: "by {ARTISTS}" + rerelease: "{TRACK} (re-release)" + +misc: + alt: + albumCover: "album cover" + albumBanner: "album banner" + trackCover: "track cover" + artistAvatar: "artist avatar" + flashArt: "flash art" + + artistLink: + _: "{ARTIST}" + withContribution: "{ARTIST} ({CONTRIB})" + withExternalLinks: "{ARTIST} ({LINKS})" + withContribution.withExternalLinks: "{ARTIST} ({CONTRIB}) ({LINKS})" + + chronology: + seeArtistPages: "(See artist pages for chronology info!)" + withNavigation: "{HEADING} ({NAVIGATION})" + + heading: + coverArt: "{INDEX} cover art by {ARTIST}" + flash: "{INDEX} flash/game by {ARTIST}" + track: "{INDEX} track by {ARTIST}" + + external: + domain: "External ({DOMAIN})" + local: "Wiki Archive (local upload)" + deviantart: "DeviantArt" + instagram: "Instagram" + newgrounds: "Newgrounds" + patreon: "Patreon" + poetryFoundation: "Poetry Foundation" + soundcloud: "SoundCloud" + spotify: "Spotify" + tumblr: "Tumblr" + twitter: "Twitter" + wikipedia: "Wikipedia" + + bandcamp: + _: "Bandcamp" + domain: "Bandcamp ({DOMAIN})" + + mastodon: + _: "Mastodon" + domain: "Mastodon ({DOMAIN})" + + youtube: + _: "YouTube" + playlist: "YouTube (playlist)" + fullAlbum: "YouTube (full album)" + + flash: + bgreco: "{LINK} (HQ Audio)" + youtube: "{LINK} (on any device)" + homestuck: + page: "{LINK} (page {PAGE})" + secret: "{LINK} (secret page)" + + missingImage: "(This image file is missing)" + missingLinkContent: "(Missing link content)" + + nav: + previous: "Previous" + next: "Next" + info: "Info" + gallery: "Gallery" + + pageTitle: + _: "{TITLE}" + withWikiName: "{TITLE} | {WIKI_NAME}" + + skippers: + skipTo: "Skip to:" + + content: "Content" + header: "Header" + footer: "Footer" + + sidebar: + _: "Sidebar" + left: "Sidebar (left)" + right: "Sidebar (right)" + + tracks: "Tracks" + art: "Artworks" + flashes: "Flashes & Games" + contributors: "Contributors" + + references: "References..." + referencedBy: "Referenced by..." + samples: "Samples..." + sampledBy: "Sampled by..." + features: "Features..." + featuredIn: "Featured in..." + + lyrics: "Lyrics" + sheetMusicFiles: "Sheet music files" + midiProjectFiles: "MIDI/project files" + additionalFiles: "Additional files" + commentary: "Commentary" + artistCommentary: "Commentary" + + socialEmbed: + heading: "{WIKI_NAME} | {HEADING}" + + jumpTo: + _: "Jump to:" + withLinks: "Jump to: {LINKS}." + + contentWarnings: + _: "cw: {WARNINGS}" + reveal: "click to show" + + albumGrid: + noCoverArt: "{ALBUM}" + + details: + _: "({TRACKS}, {TIME})" + coverArtists: "(Illust. {ARTISTS})" + otherCoverArtists: "(With {ARTISTS})" + + albumGalleryGrid: + noCoverArt: "{NAME}" + + uiLanguage: "UI Language: {LANGUAGES" + +homepage: + title: "{TITLE}" + + news: + title: "News" + + entry: + viewRest: "(View rest of entry!)" + +albumSidebar: + trackList: + fallbackSectionName: "Track list" + item: "{TRACK}" + + group: + _: "{GROUP}" + withRange: "{GROUP} ({RANGE})" + + groupBox: + title: "{GROUP}" + next: "Next: {ALBUM}" + previous: "Previous: {ALBUM}" + +albumPage: + title: "{ALBUM}" + + nav: + album: "{ALBUM}" + randomTrack: "Random Track" + gallery: "Gallery" + commentary: "Commentary" + + socialEmbed: + heading: "{GROUP}" + title: "{ALBUM}" + + body: + withDuration: "{DURATION}." + withTracks: "{TRACKS}." + withReleaseDate: Released {DATE}. + withDuration.withTracks: "{DURATION}, {TRACKS}." + withDuration.withReleaseDate: "{DURATION}. Released {DATE}." + withTracks.withReleaseDate: "{TRACKS}. Released {DATE}." + withDuration.withTracks.withReleaseDate: "{DURATION}, {TRACKS}. Released {DATE}." + +albumGalleryPage: + title: "{ALBUM} - Gallery" + + statsLine: >- + {TRACKS} totaling {DURATION}. + + statsLine.withDate: >- + {TRACKS} totaling {DURATION}. Released {DATE}. + + coverArtistsLine: >- + All track artwork by {ARTISTS}. + + noTrackArtworksLine: >- + This album doesn't have any track artwork. + +albumCommentaryPage: + title: "{ALBUM} - Commentary" + + nav: + album: "Album: {ALBUM}" + + infoLine: >- + {WORDS} across {ENTRIES}. + + entry: + title: + albumCommentary: "Album commentary" + trackCommentary: "{TRACK}" + +artistPage: + title: "{ARTIST}" + + nav: + artist: "Artist: {ARTIST}" + + creditList: + album: + _: "{ALBUM}" + withDate: "{ALBUM} ({DATE})" + withDuration: "{ALBUM} ({DURATION})" + withDate.withDuration: "{ALBUM} ({DATE}; {DURATION})" + + flashAct: + _: "{ACT}" + withDate: "{ACT} ({DATE})" + withDateRange: "{ACT} ({DATE_RANGE})" + + entry: + rerelease: "{ENTRY} (re-release)" + withContribution: "{ENTRY} ({CONTRIBUTION})" + withArtists: "{ENTRY} (with {ARTISTS})" + withArtists.withContribution: "{ENTRY} ({CONTRIBUTION}; with {ARTISTS})" + + track: + _: "{TRACK}" + withDuration: "({DURATION}) {TRACK}" + + album: + coverArt: "(cover art)" + wallpaperArt: "(wallpaper art)" + bannerArt: "(banner art)" + commentary: "(album commentary)" + + flash: + _: "{FLASH}" + + contributedDurationLine: >- + {ARTIST} has contributed {DURATION} of music shared on this wiki. + + musicGroupsLine: >- + Contributed music to groups: {GROUPS} + + artGroupsLine: >- + Contributed art to groups: {GROUPS} + + groupsLine: + item: + withCount: "{GROUP} ({COUNT})" + withDuration: "{GROUP} ({DURATION})" + + groupContributions: + title: + music: "Contributed music to groups:" + artworks: "Contributed artworks to groups:" + withSortButton: "{TITLE} ({SORT})" + + sorting: + count: "Sorting by count." + duration: "Sorting by duration." + + item: + countAccent: "({COUNT})" + durationAccent: "({DURATION})" + countDurationAccent: "({COUNT} — {DURATION})" + durationCountAccent: "({DURATION} — {COUNT})" + + trackList: + title: "Tracks" + + artList: + title: "Artworks" + + flashList: + title: "Flashes & Games" + + commentaryList: + title: "Commentary" + + viewArtGallery: + _: "View {LINK}!" + orBrowseList: "View {LINK}! Or browse the list:" + link: "art gallery" + +artistGalleryPage: + title: "{ARTIST} - Gallery" + + infoLine: >- + Contributed to {COVER_ARTS}. + +commentaryIndex: + title: "Commentary" + + infoLine: >- + {WORDS} across {ENTRIES}, in all. + + albumList: + title: "Choose an album:" + item: "{ALBUM} ({WORDS} across {ENTRIES})" + +flashIndex: + title: "Flashes & Games" + +flashPage: + title: "{FLASH}" + + nav: + flash: "{FLASH}" + +flashSidebar: + flashList: + flashesInThisAct: "Flashes in this act" + entriesInThisSection: "Entries in this section" + +groupSidebar: + title: "Groups" + + groupList: + category: "{CATEGORY}" + item: "{GROUP}" + +groupPage: + nav: + group: "Group: {GROUP}" + +groupInfoPage: + title: "{GROUP}" + + viewAlbumGallery: + _: "View {LINK}! Or browse the list:" + link: "album gallery" + + albumList: + title: "Albums" + + item: + _: "({YEAR}) {ALBUM}" + withoutYear: "{ALBUM}" + withAccent: "{ITEM} {ACCENT}" + otherGroupAccent: "(from {GROUP})" + +groupGalleryPage: + title: "{GROUP} - Gallery" + + infoLine: >- + {TRACKS} across {ALBUMS}, totaling {TIME}. + +listingIndex: + title: "Listings" + + infoLine: >- + {WIKI}: {TRACKS} across {ALBUMS}, totaling {DURATION}. + + exploreList: >- + Feel free to explore any of the listings linked below and in the sidebar! + +listingPage: + target: + album: "Albums" + artist: "Artists" + group: "Groups" + track: "Tracks" + tag: "Tags" + other: "Other" + + listingsFor: "Listings for {TARGET}: {LISTINGS}" + seeAlso: "Also check out: {LISTINGS}" + + listAlbums: + byName: + title: "Albums - by Name" + title.short: "...by Name" + item: "{ALBUM} ({TRACKS})" + + byTracks: + title: "Albums - by Tracks" + title.short: "...by Tracks" + item: "{ALBUM} ({TRACKS})" + + byDuration: + title: "Albums - by Duration" + title.short: "...by Duration" + item: "{ALBUM} ({DURATION})" + + byDate: + title: "Albums - by Date" + title.short: "...by Date" + item: "{ALBUM} ({DATE})" + + byDateAdded: + title: "Albums - by Date Added to Wiki" + title.short: "...by Date Added to Wiki" + chunk: + title: "{DATE}" + item: "{ALBUM}" + + listArtists: + byName: + title: "Artists - by Name" + title.short: "...by Name" + item: "{ARTIST} ({CONTRIBUTIONS})" + + byContribs: + title: "Artists - by Contributions" + title.short: "...by Contributions" + item: "{ARTIST} ({CONTRIBUTIONS})" + + byCommentary: + title: "Artists - by Commentary Entries" + title.short: "...by Commentary Entries" + item: "{ARTIST} ({ENTRIES})" + + byDuration: + title: "Artists - by Duration" + title.short: "...by Duration" + item: "{ARTIST} ({DURATION})" + + byLatest: + title: "Artists - by Latest Contribution" + title.short: "...by Latest Contribution" + + chunk: + title.album: "{ALBUM} ({DATE})" + title.flash: "{FLASH} ({DATE})" + item: "{ARTIST}" + + dateless: + title: "These artists' contributions aren't dated:" + item: "{ARTIST}" + + listGroups: + byName: + title: "Groups - by Name" + title.short: "...by Name" + item: "{GROUP} ({GALLERY})" + item.gallery: "Gallery" + + byCategory: + title: "Groups - by Category" + title.short: "...by Category" + + chunk: + title: "{CATEGORY}" + item: "{GROUP} ({GALLERY})" + item.gallery: "Gallery" + + byAlbums: + title: "Groups - by Albums" + title.short: "...by Albums" + item: "{GROUP} ({ALBUMS})" + + byTracks: + title: "Groups - by Tracks" + title.short: "...by Tracks" + item: "{GROUP} ({TRACKS})" + + byDuration: + title: "Groups - by Duration" + title.short: "...by Duration" + item: "{GROUP} ({DURATION})" + + byLatest: + title: "Groups - by Latest Album" + title.short: "...by Latest Album" + item: "{GROUP} ({DATE})" + + listTracks: + byName: + title: "Tracks - by Name" + title.short: "...by Name" + item: "{TRACK}" + + byAlbum: + title: "Tracks - by Album" + title.short: "...by Album" + + chunk: + title: "{ALBUM}" + item: "{TRACK}" + + byDate: + title: "Tracks - by Date" + title.short: "...by Date" + + chunk: + title: "{ALBUM} ({DATE})" + item: "{TRACK}" + item.rerelease: "{TRACK} (re-release)" + + byDuration: + title: "Tracks - by Duration" + title.short: "...by Duration" + item: "{TRACK} ({DURATION})" + + byDurationInAlbum: + title: "Tracks - by Duration (in Album)" + title.short: "...by Duration (in Album)" + + chunk: + title: "{ALBUM}" + item: "{TRACK} ({DURATION})" + + byTimesReferenced: + title: "Tracks - by Times Referenced" + title.short: "...by Times Referenced" + item: "{TRACK} ({TIMES_REFERENCED})" + + inFlashes.byAlbum: + title: "Tracks - in Flashes & Games (by Album)" + title.short: "...in Flashes & Games (by Album)" + + chunk: + title: "{ALBUM}" + item: "{TRACK} (in {FLASHES})" + + inFlashes.byFlash: + title: "Tracks - in Flashes & Games (by Flash)" + title.short: "...in Flashes & Games (by Flash)" + + chunk: + title: "{FLASH}" + item: "{TRACK} (from {ALBUM})" + + withLyrics: + title: "Tracks - with Lyrics" + title.short: "...with Lyrics" + + chunk: + title: "{ALBUM}" + title.withDate: "{ALBUM} ({DATE})" + item: "{TRACK}" + + withSheetMusicFiles: + title: "Tracks - with Sheet Music Files" + title.short: "...with Sheet Music Files" + + chunk: + title: "{ALBUM}" + title.withDate: "{ALBUM} ({DATE})" + item: "{TRACK}" + + withMidiProjectFiles: + title: "Tracks - with MIDI & Project Files" + title.short: "...with MIDI & Project Files" + + chunk: + title: "{ALBUM}" + title.withDate: "{ALBUM} ({DATE})" + item: "{TRACK}" + + listTags: + byName: + title: "Tags - by Name" + title.short: "...by Name" + item: "{TAG} ({TIMES_USED})" + + byUses: + title: "Tags - by Uses" + title.short: "...by Uses" + item: "{TAG} ({TIMES_USED})" + + other: + allSheetMusic: + title: "All Sheet Music" + title.short: "All Sheet Music" + albumFiles: "Album sheet music:" + + file: + _: "{TITLE}" + withMultipleFiles: "{TITLE} ({FILES})" + + allMidiProjectFiles: + title: "All MIDI/Project Files" + title.short: "All MIDI/Project Files" + albumFiles: "Album MIDI/project files:" + + file: + _: "{TITLE}" + withMultipleFiles: "{TITLE} ({FILES})" + + allAdditionalFiles: + title: "All Additional Files" + title.short: "All Additional Files" + albumFiles: "Album additional files:" + + file: + _: "{TITLE}" + withMultipleFiles: "{TITLE} ({FILES})" + + randomPages: + title: "Random Pages" + title.short: "Random Pages" + + chooseLinkLine: >- + Choose a link to go to a random page in that category or album! + If your browser doesn't support relatively modern JavaScript + or you've disabled it, these links won't work - sorry. + + dataLoadingLine: >- + (Data files are downloading in the background! Please wait for data to load.) + + dataLoadedLine: >- + (Data files have finished being downloaded. The links should work!) + + misc: + _: "Miscellaneous:" + randomArtist: "Random Artist" + atLeastTwoContributions: "at least 2 contributions" + randomAlbumWholeSite: "Random Album (whole site)" + randomTrackWholeSite: "Random Track (whole site)" + + group: + _: "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})" + randomAlbum: "Random Album" + randomTrack: "Random Track" + + album: "{ALBUM}" + + misc: + trackContributors: "Track Contributors" + artContributors: "Art Contributors" + flashContributors: "Flash & Game Contributors" + artAndFlashContributors: "Art & Flash Contributors" + +newsIndex: + title: "News" + + entry: + viewRest: "(View rest of entry!)" + +newsEntryPage: + title: "{ENTRY}" + published: "(Published {DATE}.)" + +redirectPage: + title: "Moved to {TITLE}" + + infoLine: >- + This page has been moved to {TARGET}. + +tagPage: + title: "{TAG}" + + nav: + tag: "Tag: {TAG}" + + infoLine: >- + Appears in {COVER_ARTS}. + +trackPage: + title: "{TRACK}" + + nav: + random: "Random" + + track: + _: "{TRACK}" + withNumber: "{NUMBER}. {TRACK}" + + referenceList: + fandom: "Fandom:" + official: "Official:" + + socialEmbed: + heading: "{ALBUM}" + title: "{TRACK}" + + body: + withArtists.withCoverArtists: "By {ARTISTS}; art by {COVER_ARTISTS}." + withArtists: "By {ARTISTS}." + withCoverArtists: "Art by {COVER_ARTISTS}." -- cgit 1.3.0-6-gf8a5 From a10e27f93c8e7965c51b2e0372a7f4b19640452e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 7 Nov 2023 08:09:14 -0400 Subject: upd8: infer custom default language from internal default code --- src/upd8.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/upd8.js b/src/upd8.js index c011b660..7d7e48b9 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -1233,11 +1233,6 @@ async function main() { timeStart: Date.now(), }); - const customDefaultLanguage = - (wikiData.wikiInfo.defaultLanguage - ? languages[wikiData.wikiInfo.defaultLanguage] - : null); - let finalDefaultLanguage; let finalDefaultLanguageWatcher; let finalDefaultLanguageAnnotation; @@ -1262,14 +1257,25 @@ async function main() { return false; } - customDefaultLanguage.inheritedStrings = internalDefaultLanguage.strings; - logInfo`Applying new default strings from custom ${customDefaultLanguage.code} language file.`; + // The custom default language will be the new one providing fallback strings + // for other languages, but on its own, it still might not be a complete list + // of strings. So it falls back to the internal default language - which won't + // otherwise be presented on the site. + customDefaultLanguage.inheritedStrings = internalDefaultLanguage.strings; + finalDefaultLanguage = customDefaultLanguage; finalDefaultLanguageWatcher = customLanguageWatchers.find(({language}) => language === customDefaultLanguage); finalDefaultLanguageAnnotation = `using wiki-specified custom default language`; + } else if (languages[internalDefaultLanguage.code]) { + const customDefaultLanguage = languages[internalDefaultLanguage.code]; + customDefaultLanguage.inheritedStrings = internalDefaultLanguage.strings; + finalDefaultLanguage = customDefaultLanguage; + finalDefaultLanguageWatcher = + customLanguageWatchers.find(({language}) => language === customDefaultLanguage); + finalDefaultLanguageAnnotation = `using inferred custom default language`; } else { languages[internalDefaultLanguage.code] = internalDefaultLanguage; -- cgit 1.3.0-6-gf8a5 From 4001e3c16c0acacc6c6d89589e57996701058dc0 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 7 Nov 2023 08:17:01 -0400 Subject: upd8: handle internal language updates in custom default language --- src/upd8.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/upd8.js b/src/upd8.js index 7d7e48b9..91f9a090 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -1259,19 +1259,12 @@ async function main() { logInfo`Applying new default strings from custom ${customDefaultLanguage.code} language file.`; - // The custom default language will be the new one providing fallback strings - // for other languages, but on its own, it still might not be a complete list - // of strings. So it falls back to the internal default language - which won't - // otherwise be presented on the site. - customDefaultLanguage.inheritedStrings = internalDefaultLanguage.strings; - finalDefaultLanguage = customDefaultLanguage; finalDefaultLanguageWatcher = customLanguageWatchers.find(({language}) => language === customDefaultLanguage); finalDefaultLanguageAnnotation = `using wiki-specified custom default language`; } else if (languages[internalDefaultLanguage.code]) { const customDefaultLanguage = languages[internalDefaultLanguage.code]; - customDefaultLanguage.inheritedStrings = internalDefaultLanguage.strings; finalDefaultLanguage = customDefaultLanguage; finalDefaultLanguageWatcher = customLanguageWatchers.find(({language}) => language === customDefaultLanguage); @@ -1284,6 +1277,12 @@ async function main() { finalDefaultLanguageAnnotation = `no custom default language specified`; } + const inheritStringsFromInternalLanguage = () => { + if (finalDefaultLanguage === internalDefaultLanguage) return; + const {strings: inheritedStrings} = internalDefaultLanguage; + Object.assign(finalDefaultLanguage, {inheritedStrings}); + }; + const inheritStringsFromDefaultLanguage = () => { const {strings: inheritedStrings} = finalDefaultLanguage; for (const language of Object.values(languages)) { @@ -1292,8 +1291,19 @@ async function main() { } }; - inheritStringsFromDefaultLanguage(); + // The custom default language, if set, will be the new one providing fallback + // strings for other languages. But on its own, it still might not be a complete + // list of strings - so it falls back to the internal default language, which + // won't otherwise be presented in the build. + if (finalDefaultLanguage !== internalDefaultLanguage) { + inheritStringsFromInternalLanguage(); + internalDefaultLanguageWatcher.on('update', () => { + inheritStringsFromInternalLanguage(); + inheritStringsFromDefaultLanguage(); + }); + } + inheritStringsFromDefaultLanguage(); finalDefaultLanguageWatcher.on('update', () => { inheritStringsFromDefaultLanguage(); }); -- cgit 1.3.0-6-gf8a5 From 8901b8bb4c9966945519de9a0b7115fb9c5a9564 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 7 Nov 2023 08:19:09 -0400 Subject: upd8: quick eslint fixes --- src/upd8.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/upd8.js b/src/upd8.js index 91f9a090..ea4629ee 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -39,7 +39,7 @@ import {fileURLToPath} from 'node:url'; import wrap from 'word-wrap'; import {displayCompositeCacheAnalysis} from '#composite'; -import {processLanguageFile, watchLanguageFile} from '#language'; +import {watchLanguageFile} from '#language'; import {isMain, traverse} from '#node-utils'; import bootRepl from '#repl'; import {empty, showAggregate, withEntries} from '#sugar'; @@ -56,7 +56,6 @@ import { logError, parseOptions, progressCallAll, - progressPromiseAll, } from '#cli'; import genThumbs, { -- cgit 1.3.0-6-gf8a5 From 505a2bf05216de928d667655ee2670ad6c3ff46d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 7 Nov 2023 08:27:19 -0400 Subject: upd8: stub --no-input option --- src/upd8.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/upd8.js b/src/upd8.js index ea4629ee..40683744 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -290,6 +290,11 @@ async function main() { type: 'flag', }, + 'no-input': { + help: `Don't wait on input from stdin - assume the device is headless`, + type: 'flag', + }, + // Want sweet, sweet trace8ack info in aggreg8te error messages? This // will print all the juicy details (or at least the first relevant // line) right to your output, 8ut also pro8a8ly give you a headache @@ -456,6 +461,7 @@ async function main() { const thumbsOnly = cliOptions['thumbs-only'] ?? false; const skipReferenceValidation = cliOptions['skip-reference-validation'] ?? false; const noBuild = cliOptions['no-build'] ?? false; + const noInput = cliOptions['no-input'] ?? false; showStepStatusSummary = cliOptions['show-step-summary'] ?? false; -- cgit 1.3.0-6-gf8a5 From 7fa4f92c8a41754e198ade96a7d5d0dd5b0aa59e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 7 Nov 2023 09:12:47 -0400 Subject: upd8: add --no-language-reloading option, default for static-build --- src/data/language.js | 2 +- src/upd8.js | 318 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 204 insertions(+), 116 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index 99eaa58f..15c11933 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -17,7 +17,7 @@ import { const {Language} = T; -export function processLanguageSpec(spec, {existingCode = null}) { +export function processLanguageSpec(spec, {existingCode = null} = {}) { const { 'meta.languageCode': code, 'meta.languageName': name, diff --git a/src/upd8.js b/src/upd8.js index 40683744..764ee0c3 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -39,7 +39,7 @@ import {fileURLToPath} from 'node:url'; import wrap from 'word-wrap'; import {displayCompositeCacheAnalysis} from '#composite'; -import {watchLanguageFile} from '#language'; +import {processLanguageFile, watchLanguageFile} from '#language'; import {isMain, traverse} from '#node-utils'; import bootRepl from '#repl'; import {empty, showAggregate, withEntries} from '#sugar'; @@ -295,6 +295,13 @@ async function main() { type: 'flag', }, + 'no-language-reloading': { + help: `Don't reload language files while the build is running\n\nApplied by default for --static-build`, + type: 'flag', + }, + + 'no-language-reload': {alias: 'no-language-reloading'}, + // Want sweet, sweet trace8ack info in aggreg8te error messages? This // will print all the juicy details (or at least the first relevant // line) right to your output, 8ut also pro8a8ly give you a headache @@ -462,6 +469,7 @@ async function main() { const skipReferenceValidation = cliOptions['skip-reference-validation'] ?? false; const noBuild = cliOptions['no-build'] ?? false; const noInput = cliOptions['no-input'] ?? false; + let noLanguageReloading = cliOptions['no-language-reloading'] ?? null; // Will get default later. showStepStatusSummary = cliOptions['show-step-summary'] ?? false; @@ -572,12 +580,24 @@ async function main() { } if (noBuild) { + logInfo`Won't generate any site or page files this run (--no-build passed).`; + Object.assign(stepStatusSummary.performBuild, { status: STATUS_NOT_APPLICABLE, annotation: `--no-build provided`, }); + } else if (usingDefaultBuildMode) { + logInfo`No build mode specified, will use default: ${selectedBuildModeFlag}`; + } else { + logInfo`Will use specified build mode: ${selectedBuildModeFlag}`; } + noLanguageReloading ??= + ({ + 'static-build': true, + 'live-dev-server': false, + })[selectedBuildModeFlag]; + if (skipThumbs && thumbsOnly) { logInfo`Well, you've put yourself rather between a roc and a hard place, hmmmm?`; return false; @@ -771,14 +791,6 @@ async function main() { thumbsCache = result.cache; } - if (noBuild) { - logInfo`Not generating any site or page files this run (--no-build passed).`; - } else if (usingDefaultBuildMode) { - logInfo`No build mode specified, using default: ${selectedBuildModeFlag}`; - } else { - logInfo`Using specified build mode: ${selectedBuildModeFlag}`; - } - if (showInvalidPropertyAccesses) { CacheableObject.DEBUG_SLOW_TRACK_INVALID_PROPERTIES = true; } @@ -1090,35 +1102,54 @@ async function main() { }); let internalDefaultLanguage; + let internalDefaultLanguageWatcher; - const internalDefaultLanguageWatcher = - watchLanguageFile(path.join(__dirname, DEFAULT_STRINGS_FILE)); + const internalDefaultStringsFile = path.join(__dirname, DEFAULT_STRINGS_FILE); - try { - await new Promise((resolve, reject) => { - const watcher = internalDefaultLanguageWatcher; - - const onReady = () => { - watcher.removeListener('ready', onReady); - watcher.removeListener('error', onError); - resolve(); - }; - - const onError = error => { - watcher.removeListener('ready', onReady); - watcher.removeListener('error', onError); - watcher.close(); - reject(error); - }; - - watcher.on('ready', onReady); - watcher.on('error', onError); - }); + let errorLoadingInternalDefaultLanguage = false; - internalDefaultLanguage = internalDefaultLanguageWatcher.language; - } catch (_error) { - // No need to display the error here - it's already printed by - // watchLanguageFile. + if (noLanguageReloading) { + internalDefaultLanguageWatcher = null; + + try { + internalDefaultLanguage = await processLanguageFile(internalDefaultStringsFile); + } catch (error) { + niceShowAggregate(error); + errorLoadingInternalDefaultLanguage = true; + } + } else { + internalDefaultLanguageWatcher = watchLanguageFile(internalDefaultStringsFile); + + try { + await new Promise((resolve, reject) => { + const watcher = internalDefaultLanguageWatcher; + + const onReady = () => { + watcher.removeListener('ready', onReady); + watcher.removeListener('error', onError); + resolve(); + }; + + const onError = error => { + watcher.removeListener('ready', onReady); + watcher.removeListener('error', onError); + watcher.close(); + reject(error); + }; + + watcher.on('ready', onReady); + watcher.on('error', onError); + }); + + internalDefaultLanguage = internalDefaultLanguageWatcher.language; + } catch (_error) { + // No need to display the error here - it's already printed by + // watchLanguageFile. + errorLoadingInternalDefaultLanguage = true; + } + } + + if (errorLoadingInternalDefaultLanguage) { logError`There was an error reading the internal language file.`; fileIssue(); @@ -1131,8 +1162,10 @@ async function main() { return false; } - // Bypass node.js special-case handling for uncaught error events - internalDefaultLanguageWatcher.on('error', () => {}); + if (!noLanguageReloading) { + // Bypass node.js special-case handling for uncaught error events + internalDefaultLanguageWatcher.on('error', () => {}); + } Object.assign(stepStatusSummary.loadInternalDefaultLanguage, { status: STATUS_DONE_CLEAN, @@ -1153,81 +1186,118 @@ async function main() { pathStyle: 'device', }); - customLanguageWatchers = - languageDataFiles.map(file => { - const watcher = watchLanguageFile(file); + let errorLoadingCustomLanguages = false; - // Bypass node.js special-case handling for uncaught error events - watcher.on('error', () => {}); + if (noLanguageReloading) { + languages = {}; - return watcher; - }); + const results = + await Promise.allSettled( + languageDataFiles + .map(file => processLanguageFile(file))); - const waitingOnWatchers = new Set(customLanguageWatchers); - - const initialResults = - await Promise.allSettled( - customLanguageWatchers.map(watcher => - new Promise((resolve, reject) => { - const onReady = () => { - watcher.removeListener('ready', onReady); - watcher.removeListener('error', onError); - waitingOnWatchers.delete(watcher); - resolve(); - }; - - const onError = error => { - watcher.removeListener('ready', onReady); - watcher.removeListener('error', onError); - reject(error); - }; - - watcher.on('ready', onReady); - watcher.on('error', onError); - }))); - - if (initialResults.some(({status}) => status === 'rejected')) { - logWarn`There were errors loading custom languages from the language path`; - logWarn`provided: ${langPath}`; - - if (noInput) { - logError`Failed to load language files. Please investigate these, or don't provide`; - logError`--lang-path (or HSMUSIC_LANG) and build again.`; - - Object.assign(stepStatusSummary.loadLanguageFiles, { - status: STATUS_FATAL_ERROR, - annotation: `see log for details`, - timeEnd: Date.now(), + for (const {status, value: language, reason: error} of results) { + if (status === 'rejected') { + errorLoadingCustomLanguages = true; + niceShowAggregate(error); + } else { + languages[language.code] = language; + } + } + } else watchCustomLanguages: { + customLanguageWatchers = + languageDataFiles.map(file => { + const watcher = watchLanguageFile(file); + + // Bypass node.js special-case handling for uncaught error events + watcher.on('error', () => {}); + + return watcher; }); - return false; - } + const waitingOnWatchers = new Set(customLanguageWatchers); + + const initialResults = + await Promise.allSettled( + customLanguageWatchers + .map(watcher => new Promise((resolve, reject) => { + const onReady = () => { + watcher.removeListener('ready', onReady); + watcher.removeListener('error', onError); + waitingOnWatchers.delete(watcher); + resolve(); + }; + + const onError = error => { + watcher.removeListener('ready', onReady); + watcher.removeListener('error', onError); + reject(error); + }; + + watcher.on('ready', onReady); + watcher.on('error', onError); + }))); + + if (initialResults.some(({status}) => status === 'rejected')) { + logWarn`There were errors loading custom languages from the language path`; + logWarn`provided: ${langPath}`; + + if (noInput) { + internalDefaultLanguageWatcher.close(); + + for (const watcher of Object.values(customLanguageWatchers)) { + watcher.close(); + } - logWarn`The build should start automatically if you investigate these.`; - logWarn`Or, exit by pressing ^C here (control+C) and run again without`; - logWarn`providing ${'--lang-path'} (or ${'HSMUSIC_LANG'}) to build without custom`; - logWarn`languages.`; - - await new Promise(resolve => { - for (const watcher of waitingOnWatchers) { - watcher.once('ready', () => { - waitingOnWatchers.remove(watcher); - if (empty(waitingOnWatchers)) { - resolve(); - } - }); + errorLoadingCustomLanguages = true; + break watchCustomLanguages; } - }); + + logWarn`The build should start automatically if you investigate these.`; + logWarn`Or, exit by pressing ^C here (control+C) and run again without`; + logWarn`providing ${'--lang-path'} (or ${'HSMUSIC_LANG'}) to build without custom`; + logWarn`languages.`; + + await new Promise(resolve => { + for (const watcher of waitingOnWatchers) { + watcher.once('ready', () => { + waitingOnWatchers.remove(watcher); + if (empty(waitingOnWatchers)) { + resolve(); + } + }); + } + }); + } + + languages = + Object.fromEntries( + customLanguageWatchers + .map(({language}) => [language.code, language])); } - languages = - Object.fromEntries( - customLanguageWatchers - .map(watcher => [watcher.language.code, watcher.language])); + if (errorLoadingCustomLanguages) { + logError`Failed to load language files. Please investigate these, or don't provide`; + logError`--lang-path (or HSMUSIC_LANG) and build again.`; + + Object.assign(stepStatusSummary.loadLanguageFiles, { + status: STATUS_FATAL_ERROR, + annotation: `see log for details`, + timeEnd: Date.now(), + }); + + return false; + } Object.assign(stepStatusSummary.loadLanguageFiles, { status: STATUS_DONE_CLEAN, timeEnd: Date.now(), + annotation: + (noLanguageReloading + ? (selectedBuildModeFlag === 'static-build' + ? `loaded statically, default for --static-build` + : `loaded statically, --no-language-reloading provided`) + : `watching for changes`), }); } else { languages = {}; @@ -1265,24 +1335,40 @@ async function main() { logInfo`Applying new default strings from custom ${customDefaultLanguage.code} language file.`; finalDefaultLanguage = customDefaultLanguage; - finalDefaultLanguageWatcher = - customLanguageWatchers.find(({language}) => language === customDefaultLanguage); finalDefaultLanguageAnnotation = `using wiki-specified custom default language`; + + if (!noLanguageReloading) { + finalDefaultLanguageWatcher = + customLanguageWatchers + .find(({language}) => language === customDefaultLanguage); + } } else if (languages[internalDefaultLanguage.code]) { const customDefaultLanguage = languages[internalDefaultLanguage.code]; + finalDefaultLanguage = customDefaultLanguage; - finalDefaultLanguageWatcher = - customLanguageWatchers.find(({language}) => language === customDefaultLanguage); finalDefaultLanguageAnnotation = `using inferred custom default language`; + + if (!noLanguageReloading) { + finalDefaultLanguageWatcher = + customLanguageWatchers + .find(({language}) => language === customDefaultLanguage); + } } else { languages[internalDefaultLanguage.code] = internalDefaultLanguage; finalDefaultLanguage = internalDefaultLanguage; - finalDefaultLanguageWatcher = internalDefaultLanguageWatcher; finalDefaultLanguageAnnotation = `no custom default language specified`; + + if (!noLanguageReloading) { + finalDefaultLanguageWatcher = internalDefaultLanguageWatcher; + } } const inheritStringsFromInternalLanguage = () => { + // The custom default language, if set, will be the new one providing fallback + // strings for other languages. But on its own, it still might not be a complete + // list of strings - so it falls back to the internal default language, which + // won't otherwise be presented in the build. if (finalDefaultLanguage === internalDefaultLanguage) return; const {strings: inheritedStrings} = internalDefaultLanguage; Object.assign(finalDefaultLanguage, {inheritedStrings}); @@ -1296,22 +1382,24 @@ async function main() { } }; - // The custom default language, if set, will be the new one providing fallback - // strings for other languages. But on its own, it still might not be a complete - // list of strings - so it falls back to the internal default language, which - // won't otherwise be presented in the build. if (finalDefaultLanguage !== internalDefaultLanguage) { inheritStringsFromInternalLanguage(); - internalDefaultLanguageWatcher.on('update', () => { - inheritStringsFromInternalLanguage(); - inheritStringsFromDefaultLanguage(); - }); } inheritStringsFromDefaultLanguage(); - finalDefaultLanguageWatcher.on('update', () => { - inheritStringsFromDefaultLanguage(); - }); + + if (!noLanguageReloading) { + if (finalDefaultLanguage !== internalDefaultLanguage) { + internalDefaultLanguageWatcher.on('update', () => { + inheritStringsFromInternalLanguage(); + inheritStringsFromDefaultLanguage(); + }); + } + + finalDefaultLanguageWatcher.on('update', () => { + inheritStringsFromDefaultLanguage(); + }); + } logInfo`Loaded language strings: ${Object.keys(languages).join(', ')}`; -- cgit 1.3.0-6-gf8a5 From 9bf4b59f608022ab0ca2cadc4acff7bb6760ebd2 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 8 Nov 2023 19:54:01 -0400 Subject: content, repl: minor fixes and tweaks --- src/content/dependencies/generatePageLayout.js | 3 ++- src/content/dependencies/linkExternal.js | 25 ++++++++++++++++++++++--- src/repl.js | 4 +++- src/upd8.js | 2 +- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js index cd831ba7..72dfbae5 100644 --- a/src/content/dependencies/generatePageLayout.js +++ b/src/content/dependencies/generatePageLayout.js @@ -449,7 +449,8 @@ export default { {[html.onlyIfContent]: true, class: 'skipper-list'}, processSkippers([ {id: 'tracks', string: 'tracks'}, - {id: 'art', string: 'flashes'}, + {id: 'art', string: 'artworks'}, + {id: 'flashes', string: 'flashes'}, {id: 'contributors', string: 'contributors'}, {id: 'references', string: 'references'}, {id: 'referenced-by', string: 'referencedBy'}, diff --git a/src/content/dependencies/linkExternal.js b/src/content/dependencies/linkExternal.js index 73c656e3..5de612e2 100644 --- a/src/content/dependencies/linkExternal.js +++ b/src/content/dependencies/linkExternal.js @@ -3,10 +3,20 @@ const BANDCAMP_DOMAINS = ['bc.s3m.us', 'music.solatrux.com']; const MASTODON_DOMAINS = ['types.pl']; export default { - extraDependencies: ['html', 'language'], + extraDependencies: ['html', 'language', 'wikiData'], - data(url) { - return {url}; + sprawl: ({wikiInfo}) => ({wikiInfo}), + + data(sprawl, url) { + const data = {url}; + + const {canonicalBase} = sprawl.wikiInfo; + if (canonicalBase) { + const {hostname: canonicalDomain} = new URL(canonicalBase); + Object.assign(data, {canonicalDomain}); + } + + return data; }, slots: { @@ -20,6 +30,7 @@ export default { let isLocal; let domain; let pathname; + try { const url = new URL(data.url); domain = url.hostname; @@ -28,6 +39,14 @@ export default { // No support for relative local URLs yet, sorry! (I.e, local URLs must // be absolute relative to the domain name in order to work.) isLocal = true; + domain = null; + pathname = null; + } + + // isLocal also applies for URLs which match the 'Canonical Base' under + // wiki-info.yaml, if present. + if (data.canonicalDomain && domain === data.canonicalDomain) { + isLocal = true; } const link = html.tag('a', diff --git a/src/repl.js b/src/repl.js index ead01567..7a6f5c45 100644 --- a/src/repl.js +++ b/src/repl.js @@ -16,6 +16,8 @@ import * as serialize from '#serialize'; import * as sugar from '#sugar'; import * as wikiDataUtils from '#wiki-data'; +import {DEFAULT_STRINGS_FILE} from './upd8.js'; + const __dirname = path.dirname(fileURLToPath(import.meta.url)); export async function getContextAssignments({ @@ -46,7 +48,7 @@ export async function getContextAssignments({ language = await processLanguageFile( path.join( path.dirname(fileURLToPath(import.meta.url)), - 'strings-default.json')); + DEFAULT_STRINGS_FILE)); } catch (error) { console.error(error); logWarn`Failed to create Language object`; diff --git a/src/upd8.js b/src/upd8.js index 868bfee4..24d0b92b 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -93,7 +93,7 @@ try { const BUILD_TIME = new Date(); -const DEFAULT_STRINGS_FILE = 'strings-default.yaml'; +export const DEFAULT_STRINGS_FILE = 'strings-default.yaml'; const STATUS_NOT_STARTED = `not started`; const STATUS_NOT_APPLICABLE = `not applicable`; -- cgit 1.3.0-6-gf8a5 From b62622d3cd8ffe1ed517ceb873d9352943c4a601 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 8 Nov 2023 19:55:37 -0400 Subject: content: strings-default.yaml documentation and clean-up --- src/strings-default.yaml | 880 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 799 insertions(+), 81 deletions(-) diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 2fd905d1..a5a09280 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -1,16 +1,41 @@ meta.languageCode: en meta.languageName: English +# +# count: +# +# This covers pretty much any time that a specific number of things +# is represented! It's sectioned... like an alignment chart meme... +# +# First counting specific wiki objects, then more abstract stuff, +# and finally numerical representations of kinds of quantities that +# aren't really "counting", per se. +# +# These must be filled out according to the Unicode Common Locale +# Data Repository (Unicode CLDR). Check out info on their site: +# https://cldr.unicode.org +# +# Specifically, you'll want to look into the Plural Rules for your +# language. Here's a summary on what those even are: +# https://cldr.unicode.org/index/cldr-spec/plural-rules +# +# CLDR's charts are available online! This should bring you to the +# most recent table of plural rules: +# https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html +# +# Counting is generally done with the "Type: cardinal" section on +# that chart - for example, if the chart lists "one", "many", and +# "other" under the cardinal plural rules for your language, then +# your job is to fill in the correct pluralizations of the specific +# term for each of those. +# +# If you adore technical details or want to better understand the +# "Rules" column, you'll want to check out the syntax outline here: +# https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules +# count: - tracks: - _: "{TRACKS}" - withUnit: - zero: "" - one: "{TRACKS} track" - two: "" - few: "" - many: "" - other: "{TRACKS} tracks" + + # Count things and objects additionalFiles: _: "{FILES}" @@ -82,26 +107,37 @@ count: many: "" other: "{FLASHES} flashes & games" - timesReferenced: - _: "{TIMES_REFERENCED}" + tracks: + _: "{TRACKS}" withUnit: zero: "" - one: "{TIMES_REFERENCED} time referenced" + one: "{TRACKS} track" two: "" few: "" many: "" - other: "{TIMES_REFERENCED} times referenced" + other: "{TRACKS} tracks" - words: - _: "{WORDS}" - thousand: "{WORDS}k" + # Count more abstract stuff + + days: + _: "{DAYS}" withUnit: zero: "" - one: "{WORDS} word" + one: "{DAYS} day" two: "" few: "" many: "" - other: "{WORDS} words" + other: "{DAYS} days" + + timesReferenced: + _: "{TIMES_REFERENCED}" + withUnit: + zero: "" + one: "{TIMES_REFERENCED} time referenced" + two: "" + few: "" + many: "" + other: "{TIMES_REFERENCED} times referenced" timesUsed: _: "{TIMES_USED}" @@ -113,13 +149,18 @@ count: many: "" other: "used {TIMES_USED} times" - index: - zero: "" - one: "{INDEX}st" - two: "{INDEX}nd" - few: "{INDEX}rd" - many: "" - other: "{INDEX}th" + words: + _: "{WORDS}" + thousand: "{WORDS}k" + withUnit: + zero: "" + one: "{WORDS} word" + two: "" + few: "" + many: "" + other: "{WORDS} words" + + # Numerical things that aren't exactly counting, per se duration: missing: "_:__" @@ -138,7 +179,28 @@ count: kilobytes: "{KILOBYTES} kB" bytes: "{BYTES} bytes" + # Indexes in a list + # These use "Type: ordinal" on CLDR's chart of plural rules. + + index: + zero: "" + one: "{INDEX}st" + two: "{INDEX}nd" + few: "{INDEX}rd" + many: "" + other: "{INDEX}th" + +# +# releaseInfo: +# +# This covers a lot of generic strings - they're used in a variety +# of contexts. They're sorted below with descriptions first, then +# actions further down. +# releaseInfo: + + # Descriptions + by: "By {ARTISTS}." from: "From {ALBUM}." @@ -152,6 +214,33 @@ releaseInfo: duration: "Duration: {DURATION}." + contributors: "Contributors:" + lyrics: "Lyrics:" + note: "Context notes:" + + alsoReleasedAs: + _: "Also released as:" + item: "{TRACK} (on {ALBUM})" + + tracksReferenced: "Tracks that {TRACK} references:" + tracksThatReference: "Tracks that reference {TRACK}:" + tracksSampled: "Tracks that {TRACK} samples:" + tracksThatSample: "Tracks that sample {TRACK}:" + + flashesThatFeature: + _: "Flashes & games that feature {TRACK}:" + item: + _: "{FLASH}" + asDifferentRelease: "{FLASH} (as {TRACK})" + + tracksFeatured: "Tracks that {FLASH} features:" + + artTags: + _: "Tags:" + inline: "Tags: {TAGS}" + + # Actions + viewCommentary: _: "View {LINK}!" link: "commentary page" @@ -184,36 +273,10 @@ releaseInfo: _: "Read {LINK}." link: "artist commentary" - alsoReleasedAs: - _: "Also released as:" - item: "{TRACK} (on {ALBUM})" - - contributors: "Contributors:" - - tracksReferenced: "Tracks that {TRACK} references:" - tracksThatReference: "Tracks that reference {TRACK}:" - tracksSampled: "Tracks that {TRACK} samples:" - tracksThatSample: "Tracks that sample {TRACK}:" - - flashesThatFeature: - _: "Flashes & games that feature {TRACK}:" - item: - _: "{FLASH}" - asDifferentRelease: "{FLASH} (as {TRACK})" - - tracksFeatured: "Tracks that {FLASH} features:" - - lyrics: "Lyrics:" - note: "Context notes:" - artistCommentary: _: "Artist commentary:" seeOriginalRelease: "See {ORIGINAL}!" - artTags: - _: "Tags:" - inline: "Tags: {TAGS}" - additionalFiles: heading: "View or download {ADDITIONAL_FILES}:" @@ -243,6 +306,15 @@ releaseInfo: _: "Download {LINK}." link: "MIDI/project files" +# +# trackList: +# +# A list of tracks! These are used pretty much across the wiki. +# Track lists can be split into sections, groups, or not split at +# all. "Track sections" are divisions in the list which suit the +# album as a whole, like if it has multiple discs or bonus tracks. +# "Groups" are actual group objects (see ex. groupInfoPage). +# trackList: section: withDuration: "{SECTION} ({DURATION}):" @@ -258,7 +330,18 @@ trackList: withArtists.by: "by {ARTISTS}" rerelease: "{TRACK} (re-release)" +# +# misc: +# +# These cover a whole host of general things across the wiki, and +# aren't specially organized. Sorry! See each entry for details. +# misc: + + # alt: + # Fallback text for the alt text of images and artworks - these + # are read aloud by screen readers. + alt: albumCover: "album cover" albumBanner: "album banner" @@ -266,14 +349,47 @@ misc: artistAvatar: "artist avatar" flashArt: "flash art" + # artistLink: + # Artist links have special accents which are made conditionally + # present in a variety of places across the wiki. + artistLink: _: "{ARTIST}" + + # Contribution to a track, artwork, or other thing. withContribution: "{ARTIST} ({CONTRIB})" + + # External links to visit the artist's own websites or profiles. withExternalLinks: "{ARTIST} ({LINKS})" + + # Combination of above. withContribution.withExternalLinks: "{ARTIST} ({CONTRIB}) ({LINKS})" + # chronology: + # + # "Chronology links" are a section that appear in the nav bar for + # most things with individual contributors across the wiki! These + # allow for quick navigation between older and newer releases of + # a given artist, or seeing at a glance how many contributions an + # artist made before the one you're currently viewing. + # + # Chronology information is described for each artist and shows + # the kind of thing which is being contributed to, since all of + # the entries are displayed together in one list. + # + chronology: + + # seeArtistPages: + # If the thing you're viewing has a lot of contributors, their + # chronology info will be exempt from the nav bar, which'll + # show this message instead. + seeArtistPages: "(See artist pages for chronology info!)" + + # withNavigation: + # Navigation refers to previous/next links. + withNavigation: "{HEADING} ({NAVIGATION})" heading: @@ -281,9 +397,26 @@ misc: flash: "{INDEX} flash/game by {ARTIST}" track: "{INDEX} track by {ARTIST}" + # external: + # Links which will generally bring you somewhere off of the wiki. + # The list of sites is hard-coded into the wiki software, so it + # may be out of date or missing ones that are relevant to another + # wiki - sorry! + external: + + # domain: + # General domain when one the URL doesn't match one of the + # sites below. + domain: "External ({DOMAIN})" + + # local: + # Files which are locally available on the wiki (under its media + # directory). + local: "Wiki Archive (local upload)" + deviantart: "DeviantArt" instagram: "Instagram" newgrounds: "Newgrounds" @@ -315,19 +448,53 @@ misc: page: "{LINK} (page {PAGE})" secret: "{LINK} (secret page)" + # missingImage: + # Fallback text displayed in an image when it's sourced to a file + # that isn't available under the wiki's media directory. While it + # shouldn't display on a correct build of the site, it may be + # displayed when working on data locally (for example adding a + # track before you've brought in its cover art). + missingImage: "(This image file is missing)" + + # misingLinkContent: + # Generic fallback when a link is completely missing its content. + # This is only to make those links visible in the first place - + # it should never appear on the website and is only intended for + # debugging. + missingLinkContent: "(Missing link content)" + # nav: + # Generic navigational elements. These usually only appear in the + # wiki's nav bar, at the top of the page. + nav: previous: "Previous" next: "Next" info: "Info" gallery: "Gallery" + # pageTitle: + # Title set under the page's HTML element, which is + # displayed in the browser tab bar, bookmarks list, etc. + pageTitle: _: "{TITLE}" withWikiName: "{TITLE} | {WIKI_NAME}" + # skippers: + # + # These are navigational links that only show up when you're + # navigating the wiki using the Tab key (or some other method of + # "tabbing" between links and interactive elements). They move + # the browser's nav focus to the selected element when pressed. + # + # There are a lot of definitions here, and they're mostly shown + # conditionally, based on the elements that are actually apparent + # on the current page. + # + skippers: skipTo: "Skip to:" @@ -340,11 +507,18 @@ misc: left: "Sidebar (left)" right: "Sidebar (right)" + # Displayed on artist info page. + tracks: "Tracks" - art: "Artworks" + artworks: "Artworks" flashes: "Flashes & Games" + + # Displayed on track and flash info pages. + contributors: "Contributors" + # Displayed on track info page. + references: "References..." referencedBy: "Referenced by..." samples: "Samples..." @@ -353,23 +527,48 @@ misc: featuredIn: "Featured in..." lyrics: "Lyrics" + sheetMusicFiles: "Sheet music files" midiProjectFiles: "MIDI/project files" - additionalFiles: "Additional files" + + # Displayed on track and album info pages. + commentary: "Commentary" + artistCommentary: "Commentary" + additionalFiles: "Additional files" + + # socialEmbed: + # Social embeds describe how the page should be represented on + # social platforms, chat messaging apps, and so on. socialEmbed: heading: "{WIKI_NAME} | {HEADING}" + # jumpTo: + # Generic action displayed at the top of some longer pages, for + # quickly scrolling down to a particular section. + jumpTo: _: "Jump to:" withLinks: "Jump to: {LINKS}." + # contentWarnings: + # Displayed for some artworks, informing of possibly sensitive + # content and giving the viewer a chance to consider before + # clicking through. + contentWarnings: _: "cw: {WARNINGS}" reveal: "click to show" + # albumGrid: + # Generic strings for various sorts of gallery grids, displayed + # on the homepage, album galleries, artist artwork galleries, and + # so on. These get the name of the thing being represented and, + # often, a bit of text providing pertinent extra details about + # that thing. + albumGrid: noCoverArt: "{ALBUM}" @@ -381,31 +580,83 @@ misc: albumGalleryGrid: noCoverArt: "{NAME}" - uiLanguage: "UI Language: {LANGUAGES" + # uiLanguage: + # Displayed in the footer, for switching between languages. + + uiLanguage: "UI Language: {LANGUAGES}" +# +# homepage: +# This is the main index and home for the whole wiki! There isn't +# much for strings here as the layout is very customizable and +# includes mostly wiki-provided content. +# homepage: title: "{TITLE}" + # news: + # If the wiki has news entries enabled, then there's a box in the + # homepage's sidebar (beneath custom sidebar content, if any) + # which displays the bodies the latest few entries up to a split. + news: title: "News" entry: viewRest: "(View rest of entry!)" +# +# albumSidebar: +# This sidebar is displayed on both the album and track info pages! +# It displays the groups that the album is from (each getting its +# own box on the album page, all conjoined in one box on the track +# page) and the list of tracks in the album, which can be sectioned +# similarly to normal track lists, but displays the range of tracks +# in each section rather than the section's duration. +# albumSidebar: trackList: - fallbackSectionName: "Track list" item: "{TRACK}" + # fallbackSectionName: + # If an album's track list isn't sectioned, the track list here + # will still have all the tracks grouped under a list that can + # be toggled open and closed. This controls how that list gets + # titled. + + fallbackSectionName: "Track list" + + # group: + # "Group" is a misnomer - these are track sections. Some albums + # don't use track numbers at all, and for these, the default + # string will be used instead of group.withRange. + group: _: "{GROUP}" withRange: "{GROUP} ({RANGE})" + # groupBox: + # This is the box for groups. Apart from the next and previous + # links, it also gets "visit on" and the group's descripton + # (up to a split). + groupBox: title: "{GROUP}" next: "Next: {ALBUM}" previous: "Previous: {ALBUM}" +# +# albumPage: +# +# Albums group together tracks and provide quick access to each of +# their pages, have release data (and sometimes credits) that are +# generally inherited by the album's tracks plus commentary and +# other goodies of their own, and are generally the main object on +# the wiki! +# +# Most of the strings on the album info page are tracked under +# releaseInfo, so there isn't a lot here. +# albumPage: title: "{ALBUM}" @@ -419,6 +670,10 @@ albumPage: heading: "{GROUP}" title: "{ALBUM}" + # body: + # These permutations are a bit awkward. "Tracks" is a counted + # string, ex. "63 tracks". + body: withDuration: "{DURATION}." withTracks: "{TRACKS}." @@ -428,21 +683,52 @@ albumPage: withTracks.withReleaseDate: "{TRACKS}. Released {DATE}." withDuration.withTracks.withReleaseDate: "{DURATION}, {TRACKS}. Released {DATE}." +# +# albumGalleryPage: +# Album galleries provide an alternative way to navigate the album, +# and put all its artwork - including for each track - into the +# spotlight. Apart from the main gallery grid (which usually lists +# each artwork's illustrators), this page also has a quick stats +# line about the album, and may display a message about all of the +# artworks if one applies. +# albumGalleryPage: title: "{ALBUM} - Gallery" + # statsLine: + # Most albums have release dates, but not all. These strings + # react accordingly. + statsLine: >- {TRACKS} totaling {DURATION}. statsLine.withDate: >- {TRACKS} totaling {DURATION}. Released {DATE}. + # coverArtistsLine: + # This is displayed if every track (which has artwork at all) + # has the same illustration credits. + coverArtistsLine: >- All track artwork by {ARTISTS}. + # noTrackArtworksLine: + # This is displayed if none of the tracks on the album have any + # artwork at all. Generally, this means the album gallery won't + # be linked from the album's other pages, but it is possible to + # end up on "stub galleries" using nav links on another gallery. + noTrackArtworksLine: >- This album doesn't have any track artwork. +# +# albumCommentaryPage: +# The album commentary page is a more minimal layout that brings +# the commentary for the album, and each of its tracks, to the +# front. It's basically inspired by reading in a library, or by +# following along with an album's booklet or liner notes while +# playing it back on a treasured dinky CD player late at night. +# albumCommentaryPage: title: "{ALBUM} - Commentary" @@ -457,6 +743,14 @@ albumCommentaryPage: albumCommentary: "Album commentary" trackCommentary: "{TRACK}" +# +# artistInfoPage: +# The artist info page is an artist's main home on the wiki, and +# automatically includes a full list of all the things they've +# contributed to and been credited on. It's split into a section +# for each of the kinds of things the artist is credited for, +# including tracks, artworks, flashes/games, and commentary. +# artistPage: title: "{ARTIST}" @@ -464,27 +758,73 @@ artistPage: artist: "Artist: {ARTIST}" creditList: + + # album: + # Tracks are chunked by albums, as long as the tracks are all + # of the same date (if applicable). + album: _: "{ALBUM}" withDate: "{ALBUM} ({DATE})" withDuration: "{ALBUM} ({DURATION})" withDate.withDuration: "{ALBUM} ({DATE}; {DURATION})" + # flashAct: + # Flashes are chunked by flash act, though a single flash act + # might be split into multiple chunks if it spans a long range + # and the artist contributed to a flash from some other act + # between. A date range will be shown if an act has at least + # two differently dated flashes. + flashAct: _: "{ACT}" withDate: "{ACT} ({DATE})" withDateRange: "{ACT} ({DATE_RANGE})" + # entry: + # This section covers strings for all kinds of individual + # things which an artist has contributed to, and refers to the + # items in each of the chunks described above. + entry: - rerelease: "{ENTRY} (re-release)" + + # withContribution: + # The specific contribution that an artist made to a given + # thing may be described with a word or two, and that's shown + # in the list. + withContribution: "{ENTRY} ({CONTRIBUTION})" + + # withArtists: + # This lists co-artists or co-contributors, depending on how + # the artist themselves was credited. + withArtists: "{ENTRY} (with {ARTISTS})" + withArtists.withContribution: "{ENTRY} ({CONTRIBUTION}; with {ARTISTS})" + # rerelease: + # Tracks which aren't the original release don't display co- + # artists or contributors, and get dimmed a little compared + # to original release track entries. + + rerelease: "{ENTRY} (re-release)" + + # track: + # The string without duration is used in both the artist's + # track credits list as well as their commentary list. + track: _: "{TRACK}" withDuration: "({DURATION}) {TRACK}" + # album: + # The artist info page doesn't display if the artist is + # musically credited outright for the album as a whole, + # opting to show each of the tracks from that album instead. + # But other parts belonging specifically to the album have + # credits too, and those entreis get the strings below. + album: coverArt: "(cover art)" wallpaperArt: "(wallpaper art)" @@ -494,19 +834,18 @@ artistPage: flash: _: "{FLASH}" + # contributedDurationLine: + # This is shown at the top of the artist's track list, provided + # any of their tracks have durations at all. + contributedDurationLine: >- {ARTIST} has contributed {DURATION} of music shared on this wiki. - musicGroupsLine: >- - Contributed music to groups: {GROUPS} - - artGroupsLine: >- - Contributed art to groups: {GROUPS} - - groupsLine: - item: - withCount: "{GROUP} ({COUNT})" - withDuration: "{GROUP} ({DURATION})" + # groupContributions: + # This is a special "chunk" shown at the top of an artist's + # track and artwork lists. It lists which groups an artist has + # contributed the most (and least) to, and is interactive - + # it can be sorted by count or, for tracks, by duration. groupContributions: title: @@ -536,17 +875,35 @@ artistPage: commentaryList: title: "Commentary" + # viewArtGallery: + # This is shown twice on the page - once at almost the very top + # of the page, just beneath visiting links, and once above the + # list of credited artworks, where it gets the longer + # orBrowseList form. + viewArtGallery: _: "View {LINK}!" orBrowseList: "View {LINK}! Or browse the list:" link: "art gallery" +# +# artistGalleryPage: +# The artist gallery page shows a neat grid of all of the album and +# track artworks an artist has contributed to! Co-illustrators are +# also displayed when applicable. +# artistGalleryPage: title: "{ARTIST} - Gallery" infoLine: >- Contributed to {COVER_ARTS}. +# +# commentaryIndex: +# The commentary index page shows a summary of all the commentary +# across the entire wiki, with a list linking to each album's +# dedicated commentary page. +# commentaryIndex: title: "Commentary" @@ -557,20 +914,50 @@ commentaryIndex: title: "Choose an album:" item: "{ALBUM} ({WORDS} across {ENTRIES})" +# +# flashIndex: +# The flash index page shows a very long grid including every flash +# on the wiki, sectioned with big headings for each act. It's also +# got jump links at the top to skip to a specific overarching +# section ("side") of flash acts. +# flashIndex: title: "Flashes & Games" +# +# flashSidebar: +# The flash sidebar is used on both the flash info and flash act +# gallery pages, and has two boxes - one showing all the flashes in +# the current flash act, and one showing all the flash acts on the +# wiki, sectioned by "side". +# +flashSidebar: + flashList: + + # These two strings are the default ones used when a flash act + # doesn't specify a custom phrasing. + flashesInThisAct: "Flashes in this act" + entriesInThisSection: "Entries in this section" + +# +# flashPage: +# The flash info page shows release information, links to check the +# flash out, and lists of contributors and featured tracks. Most of +# those strings are under releaseInfo, so there aren't a lot of +# strings here. +# flashPage: title: "{FLASH}" nav: flash: "{FLASH}" -flashSidebar: - flashList: - flashesInThisAct: "Flashes in this act" - entriesInThisSection: "Entries in this section" - +# +# groupSidebar: +# The group sidebar is used on both the group info and group +# gallery pages, and is formed of just one box, showing all the +# groups on the wiki, sectioned by "category". +# groupSidebar: title: "Groups" @@ -578,10 +965,19 @@ groupSidebar: category: "{CATEGORY}" item: "{GROUP}" +# +# groupPage: +# This section represents strings common to multiple group pages. +# groupPage: nav: group: "Group: {GROUP}" +# +# groupInfoPage: +# The group info page shows visiting links, the group's full +# description, and a list of albums from the group. +# groupInfoPage: title: "{GROUP}" @@ -589,6 +985,11 @@ groupInfoPage: _: "View {LINK}! Or browse the list:" link: "album gallery" + # albumList: + # Many albums are present under multiple groups, and these get an + # accent indicating what other group is highest on the album's + # list of groups. + albumList: title: "Albums" @@ -598,12 +999,24 @@ groupInfoPage: withAccent: "{ITEM} {ACCENT}" otherGroupAccent: "(from {GROUP})" +# +# groupGalleryPage: +# The group gallery page shows a grid of all the albums from that +# group, each including the number of tracks and duration, as well +# as a stats line for the group as a whole, and a neat carousel, if +# pre-configured! +# groupGalleryPage: title: "{GROUP} - Gallery" infoLine: >- {TRACKS} across {ALBUMS}, totaling {TIME}. +# +# listingIndex: +# The listing index page shows all available listings on the wiki, +# and a very exciting stats line for the wiki as a whole. +# listingIndex: title: "Listings" @@ -613,7 +1026,26 @@ listingIndex: exploreList: >- Feel free to explore any of the listings linked below and in the sidebar! +# +# listingPage: +# +# There are a lot of listings! Each is automatically generated and +# sorts or organizes the data on the wiki in some way that provides +# useful or interesting information. Most listings work primarily +# with one kind of data and are sectioned accordingly, for example +# "listAlbums.byDuration" or "listTracks.byDate". +# +# There are also some miscellaneous strings here, most of which are +# common to a variety of listings, and are often navigational in +# nature. +# listingPage: + + # target: + # Just the names for each of the sections - each chunk on the + # listing index (and in the sidebar) gets is titled with one of + # these. + target: album: "Albums" artist: "Artists" @@ -622,30 +1054,77 @@ listingPage: tag: "Tags" other: "Other" + # misc: + # Common, generic terminology across multiple listings. + + misc: + trackContributors: "Track Contributors" + artContributors: "Art Contributors" + flashContributors: "Flash & Game Contributors" + artAndFlashContributors: "Art & Flash Contributors" + + # listingFor: + # Displays quick links to navigate to other listings for the + # current target. + listingsFor: "Listings for {TARGET}: {LISTINGS}" + + # seeAlso: + # Displays directly related listings, which might be from other + # targets besides the current one. + seeAlso: "Also check out: {LISTINGS}" listAlbums: + + # listAlbums.byName: + # Lists albums alphabetically without sorting or chunking by + # any other criteria. Also displays the number of tracks for + # each album. + byName: title: "Albums - by Name" title.short: "...by Name" item: "{ALBUM} ({TRACKS})" + # listAlbums.byTracks: + # Lists albums by number of tracks, most to least, or by name + # alphabetically, if two albums have the same track count. + # Albums without any tracks are totally excluded. + byTracks: title: "Albums - by Tracks" title.short: "...by Tracks" item: "{ALBUM} ({TRACKS})" + # listAlbums.byDuration: + # Lists albums by total duration of all tracks, longest to + # shortest, falling back to an alphabetical sort if two albums + # are the same duration. Albums with zero duration are totally + # excluded. + byDuration: title: "Albums - by Duration" title.short: "...by Duration" item: "{ALBUM} ({DURATION})" + # listAlbums.byDate: + # Lists albums by release date, oldest to newest, falling back + # to an alphabetical sort if two albums were released on the + # same date. Dateless albums are totally excluded. + byDate: title: "Albums - by Date" title.short: "...by Date" item: "{ALBUM} ({DATE})" + # listAlbums.byDateAdded: + # Lists albums by the date they were added to the wiki, oldest + # to newest, and chunks these by date, since albums are usually + # added in bunches at a time. The albums in each chunk are + # sorted alphabetically, and albums which are missing the + # "Date Added" field are totally excluded. + byDateAdded: title: "Albums - by Date Added to Wiki" title.short: "...by Date Added to Wiki" @@ -654,26 +1133,66 @@ listingPage: item: "{ALBUM}" listArtists: + + # listArtists.byName: + # Lists artists alphabetically without sorting or chunking by + # any other criteria. Also displays the number of contributions + # from each artist. + byName: title: "Artists - by Name" title.short: "...by Name" item: "{ARTIST} ({CONTRIBUTIONS})" + # listArtists.byContribs: + # Lists artists by number of contributions, most to least, + # with separate lists for contributions to tracks, artworks, + # and flashes. Falls back alphabetically if two artists have + # the same number of contributions. Artists who aren't credited + # for any contributions to each of these categories are + # excluded from the respective list. + byContribs: title: "Artists - by Contributions" title.short: "...by Contributions" item: "{ARTIST} ({CONTRIBUTIONS})" + # listArtists.byCommentary: + # Lists artists by number of commentary entries, most to least, + # falling back to an alphabetical sort if two artists have the + # same count. Artists who don't have any commentary entries are + # totally excluded. + byCommentary: title: "Artists - by Commentary Entries" title.short: "...by Commentary Entries" item: "{ARTIST} ({ENTRIES})" + # listArtists.byDuration: + # Lists artists by total duration of the tracks which they're + # credited on (as either artist or contributor), longest sum to + # shortest, falling back alphabetically if two artists have + # the same duration. Artists who haven't contributed any music, + # or whose tracks all lack durations, are totally excluded. + byDuration: title: "Artists - by Duration" title.short: "...by Duration" item: "{ARTIST} ({DURATION})" + # listArtists.byLatest: + # Lists artists by the date of their latest musical, artwork, + # or flash contributions (with a separate section for each), + # latest to longest ago, and chunks artists together by the + # album/flash which their contribution was to. If two albums + # (or flashes) released on the same date, they're sorted by + # name, and artists within each album/flash are also sorted + # alphabetically. If an artist has contributions of a given + # kind, but those contributions aren't dated at all, they're + # listed at the bottom; artists who aren't credited for any + # contributions to each category are totally excluded from the + # respective lists. + byLatest: title: "Artists - by Latest Contribution" title.short: "...by Latest Contribution" @@ -688,12 +1207,24 @@ listingPage: item: "{ARTIST}" listGroups: + + # listGroups.byName: + # Lists groups alphabetically without sorting or chunking by + # any other criteria. Also displays a link to each group's + # gallery page. + byName: title: "Groups - by Name" title.short: "...by Name" item: "{GROUP} ({GALLERY})" item.gallery: "Gallery" + # listGroups.byCategory: + # Lists groups directly reflecting the way they're sorted in + # the wiki's groups.yaml data file, with no automatic sorting, + # chunked (as sectioned in groups.yaml) by category. Also shows + # a link to each group's gallery page. + byCategory: title: "Groups - by Category" title.short: "...by Category" @@ -703,32 +1234,70 @@ listingPage: item: "{GROUP} ({GALLERY})" item.gallery: "Gallery" + # listGroups.byAlbums: + # Lists groups by number of belonging albums, most to least, + # falling back alphabetically if two groups have the same + # number of albums. Groups without any albums are totally + # excluded. + byAlbums: title: "Groups - by Albums" title.short: "...by Albums" item: "{GROUP} ({ALBUMS})" + # listGroups.byTracks: + # Lists groups by number of tracks under each group's albums, + # most to least, falling back to an alphabetical sort if two + # groups have the same track counts. Groups without any tracks + # are totally excluded. + byTracks: title: "Groups - by Tracks" title.short: "...by Tracks" item: "{GROUP} ({TRACKS})" + # listGroups.byDuration: + # Lists groups by sum of durations of all the tracks under each + # of the group's albums, longest to shortest, falling back to + # an alphabetical sort if two groups have the same duration. + # Groups whose total duration is zero are totally excluded. + byDuration: title: "Groups - by Duration" title.short: "...by Duration" item: "{GROUP} ({DURATION})" + # listGroups.byLatest: + # List groups by release date of each group's most recent + # album, most recent to longest ago, falling back to sorting + # alphabetically if two groups' latest albums were released + # on the same date. Groups which don't have any albums, or + # whose albums are all dateless, are totally excluded. + byLatest: title: "Groups - by Latest Album" title.short: "...by Latest Album" item: "{GROUP} ({DATE})" listTracks: + + # listTracks.byName: + # List tracks alphabetically without sorting or chunking by + # any other criteria. + byName: title: "Tracks - by Name" title.short: "...by Name" item: "{TRACK}" + # listTracks.byAlbum: + # List tracks chunked by the album they're from, retaining the + # position each track occupies in its album, and sorting albums + # from oldest to newest (or alphabetically, if two albums were + # released on the same date). Dateless albums are included at + # the bottom of the list. Custom "Date First Released" fields + # on individual tracks are totally ignored. + byAlbum: title: "Tracks - by Album" title.short: "...by Album" @@ -737,6 +1306,15 @@ listingPage: title: "{ALBUM}" item: "{TRACK}" + # listTracks.byDate: + # List tracks according to their own release dates, which may + # differ from that of the album via the "Date First Released" + # field, oldest to newest, and chunked by album when multiple + # tracks from one album were released on the same date. Track + # order within a given album is preserved where possible. + # Dateless albums are excluded, except for contained tracks + # which have custom "Date First Released" fields. + byDate: title: "Tracks - by Date" title.short: "...by Date" @@ -746,11 +1324,22 @@ listingPage: item: "{TRACK}" item.rerelease: "{TRACK} (re-release)" + # listTracks.byDuration: + # List tracks by duration, longest to shortest, falling back to + # an alphabetical sort if two tracks have the same duration. + # Tracks which don't have any duration are totally excluded. + byDuration: title: "Tracks - by Duration" title.short: "...by Duration" item: "{TRACK} ({DURATION})" + # listTracks.byDurationInAlbum: + # List tracks chunked by the album they're from, then sorted + # by duration, longest to shortest; albums are sorted by date, + # oldest to newest, and both sorts fall back alphabetically. + # Dateless albums are included at the bottom of the list. + byDurationInAlbum: title: "Tracks - by Duration (in Album)" title.short: "...by Duration (in Album)" @@ -759,11 +1348,25 @@ listingPage: title: "{ALBUM}" item: "{TRACK} ({DURATION})" + # listTracks.byTimesReferenced: + # List tracks by how many other tracks' reference lists each + # appears in, most times referenced to fewest, falling back + # alphabetically if two tracks have been referenced the same + # number of times. Tracks that aren't referenced by any other + # tracks are totally excluded from the list. + byTimesReferenced: title: "Tracks - by Times Referenced" title.short: "...by Times Referenced" item: "{TRACK} ({TIMES_REFERENCED})" + # listTracks.inFlashes.byAlbum: + # List tracks, chunked by album (which are sorted by date, + # falling back alphabetically) and in their usual track order, + # and display the list of flashes that eack track is featured + # in. Tracks which aren't featured in any flashes are totally + # excluded from the list. + inFlashes.byAlbum: title: "Tracks - in Flashes & Games (by Album)" title.short: "...in Flashes & Games (by Album)" @@ -772,6 +1375,14 @@ listingPage: title: "{ALBUM}" item: "{TRACK} (in {FLASHES})" + # listTracks.inFlashes.byFlash: + # List tracks, chunked by flash (which are sorted by date, + # retaining their positions in a common act where applicable, + # or else by the two acts' names) and sorted according to the + # featured list of the flash, and display a link to the album + # each track is contained in. Tracks which aren't featured in + # any flashes are totally excluded from the list. + inFlashes.byFlash: title: "Tracks - in Flashes & Games (by Flash)" title.short: "...in Flashes & Games (by Flash)" @@ -780,6 +1391,13 @@ listingPage: title: "{FLASH}" item: "{TRACK} (from {ALBUM})" + # listTracks.withLyrics: + # List tracks, chunked by album (which are sorted by date, + # falling back alphabetically) and in their usual track order, + # displaying only tracks which have lyrics. The chunk titles + # also display the date each album was released, and tracks' + # own custom "Date First Released" fields are totally ignored. + withLyrics: title: "Tracks - with Lyrics" title.short: "...with Lyrics" @@ -789,6 +1407,14 @@ listingPage: title.withDate: "{ALBUM} ({DATE})" item: "{TRACK}" + # listTracks.withSheetMusicFiles: + # List tracks, chunked by album (which are sorted by date, + # falling back alphabetically) and in their usual track order, + # displaying only tracks which have sheet music files. The + # chunk titles also display the date each album was released, + # and tracks' own custom "Date First Released" fields are + # totally ignored. + withSheetMusicFiles: title: "Tracks - with Sheet Music Files" title.short: "...with Sheet Music Files" @@ -798,6 +1424,14 @@ listingPage: title.withDate: "{ALBUM} ({DATE})" item: "{TRACK}" + # listTracks.withMidiProjectFiles: + # List tracks, chunked by album (which are sorted by date, + # falling back alphabetically) and in their usual track order, + # displaying only tracks which have MIDI & project files. The + # chunk titles also display the date each album was released, + # and tracks' own custom "Date First Released" fields are + # totally ignored. + withMidiProjectFiles: title: "Tracks - with MIDI & Project Files" title.short: "...with MIDI & Project Files" @@ -808,17 +1442,38 @@ listingPage: item: "{TRACK}" listTags: + + # listTags.byName: + # List art tags alphabetically without sorting or chunking by + # any other criteria. Also displays the number of times each + # art tag has been featured. + byName: title: "Tags - by Name" title.short: "...by Name" item: "{TAG} ({TIMES_USED})" + # listTags.byUses: + # List art tags by number of times used, falling back to an + # alphabetical sort if two art tags have been featured the same + # number of times. Art tags which haven't haven't been featured + # at all yet are totally excluded from the list. + byUses: title: "Tags - by Uses" title.short: "...by Uses" item: "{TAG} ({TIMES_USED})" other: + + # other.allSheetMusic: + # List all sheet music files, sectioned by album (which are + # sorted by date, falling back alphabetically) and then by + # track (which retain album ordering). If one "file" entry + # contains multiple files, then it's displayed as an expandable + # list, collapsed by default, accented with the number of + # downloadable files. + allSheetMusic: title: "All Sheet Music" title.short: "All Sheet Music" @@ -828,6 +1483,9 @@ listingPage: _: "{TITLE}" withMultipleFiles: "{TITLE} ({FILES})" + # other.midiProjectFiles: + # Same as other.allSheetMusic, but for MIDI & project files. + allMidiProjectFiles: title: "All MIDI/Project Files" title.short: "All MIDI/Project Files" @@ -837,6 +1495,9 @@ listingPage: _: "{TITLE}" withMultipleFiles: "{TITLE} ({FILES})" + # other.additionalFiles: + # Same as other.allSheetMusic, but for additional files. + allAdditionalFiles: title: "All Additional Files" title.short: "All Additional Files" @@ -846,21 +1507,38 @@ listingPage: _: "{TITLE}" withMultipleFiles: "{TITLE} ({FILES})" + # other.randomPages: + # Special listing which shows a bunch of buttons that each + # link to a random page on the wiki under a particular scope. + randomPages: title: "Random Pages" title.short: "Random Pages" + # chooseLinkLine: + # Introductory line explaining the links on this listing. + chooseLinkLine: >- Choose a link to go to a random page in that category or album! If your browser doesn't support relatively modern JavaScript or you've disabled it, these links won't work - sorry. + # dataLoadingLine, dataLoadedLine: + # Since the links on this page depend on access to a fairly + # large data file that is downloaded separately and in the + # background, these messages indicate the status of that + # download and whether or not the links will work yet. + dataLoadingLine: >- (Data files are downloading in the background! Please wait for data to load.) dataLoadedLine: >- (Data files have finished being downloaded. The links should work!) + # misc: + # The first chunk in the list includes general links which + # bring you to some random page across the whole site! + misc: _: "Miscellaneous:" randomArtist: "Random Artist" @@ -868,35 +1546,65 @@ listingPage: randomAlbumWholeSite: "Random Album (whole site)" randomTrackWholeSite: "Random Track (whole site)" + # group: + # The remaining chunks are one for each of the main groups on + # the site, and each includes a list of all the albums from + # that group - clicking one brings to a random track from the + # album. + group: _: "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})" randomAlbum: "Random Album" randomTrack: "Random Track" - album: "{ALBUM}" + # album: + # Album entries under each group. - misc: - trackContributors: "Track Contributors" - artContributors: "Art Contributors" - flashContributors: "Flash & Game Contributors" - artAndFlashContributors: "Art & Flash Contributors" + album: "{ALBUM}" +# +# newsIndex: +# The news index page shows a list of every news entry on the wiki! +# (If it's got news entries enabled.) Each entry gets a stylized +# heading with its name of and date, sorted newest to oldest, as +# well as its body (up to a split) and a link to view the rest of +# the entry on its dedicated news entry page. +# newsIndex: title: "News" entry: viewRest: "(View rest of entry!)" +# +# newsEntryPage: +# The news entry page displays all the content of a news entry, +# as well as its date published, in one big list, and has nav links +# to go to the previous and next news entry. +# newsEntryPage: title: "{ENTRY}" published: "(Published {DATE}.)" +# +# redirectPage: +# Static "placeholder" pages when redirecting a visitor from one +# page to another - this generally happens automatically, before +# you have a chance to read the page, so content is concise. +# redirectPage: title: "Moved to {TITLE}" infoLine: >- This page has been moved to {TARGET}. +# +# tagPage: +# The tag gallery page displays all the artworks that a tag has +# been featured in, in one neat grid, with each artwork displaying +# its illustrators, as well as a short info line that indicates +# how many artworks the tag's part of. +# tagPage: title: "{TAG}" @@ -906,6 +1614,20 @@ tagPage: infoLine: >- Appears in {COVER_ARTS}. +# +# trackPage: +# +# The track info page is pretty much the most discrete and common +# chunk of information across the whole site, displaying info about +# the track like its release date, artists, cover illustrators, +# commentary, and more, as well as relational info, like the tracks +# it references and tracks which reference it, and flashes which +# it's been featured in. Tracks can also have extra related files, +# like sheet music and MIDI/project files. +# +# Most of the details about tracks use strings that are defined +# under releaseInfo, so this section is a little sparse. +# trackPage: title: "{TRACK}" @@ -916,10 +1638,6 @@ trackPage: _: "{TRACK}" withNumber: "{NUMBER}. {TRACK}" - referenceList: - fandom: "Fandom:" - official: "Official:" - socialEmbed: heading: "{ALBUM}" title: "{TRACK}" -- cgit 1.3.0-6-gf8a5 From 150c414044662134ddf785e7411560e3a6051a03 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 14:50:13 -0400 Subject: content: listRandomPageLinks: don't hard-code parentheses --- src/content/dependencies/listRandomPageLinks.js | 23 ++++++++++++----------- src/strings-default.yaml | 6 ++++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index 599a82d3..87e5f5aa 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -61,17 +61,18 @@ export default { html.tag('dd', html.tag('ul', [ - html.tag('li', [ - html.tag('a', - {href: '#', 'data-random': 'artist'}, - language.$('listingPage.other.randomPages.misc.randomArtist')), - - '(' + - html.tag('a', - {href: '#', 'data-random': 'artist-more-than-one-contrib'}, - language.$('listingPage.other.randomPages.misc.atLeastTwoContributions')) + - ')', - ]), + html.tag('li', + language.$('listingPage.other.randomPages.misc.randomArtist', { + mainLink: + html.tag('a', + {href: '#', 'data-random': 'artist'}, + language.$('listingPage.other.randomPages.misc.randomArtist.mainLink')), + + atLeastTwoContributions: + html.tag('a', + {href: '#', 'data-random': 'artist-more-than-one-contrib'}, + language.$('listingPage.other.randomPages.misc.randomArtist.atLeastTwoContributions')), + })), html.tag('li', html.tag('a', diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 6e975de7..bb244279 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -1574,8 +1574,10 @@ listingPage: misc: _: "Miscellaneous:" - randomArtist: "Random Artist" - atLeastTwoContributions: "at least 2 contributions" + randomArtist: + _: "{MAIN_LINK} ({AT_LEAST_TWO_CONTRIBUTIONS})" + mainLink: "Random Artist" + atLeastTwoContributions: "at least 2 contributions" randomAlbumWholeSite: "Random Album (whole site)" randomTrackWholeSite: "Random Track (whole site)" -- cgit 1.3.0-6-gf8a5 From 443c2e42ad2731e63f40c9575e2c27001ed55bae Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 15:48:36 -0400 Subject: content: generateListingPage: add chunkRowAttributes slot This refactors out the hard-coded 'rerelease' behavior. --- src/content/dependencies/generateListingPage.js | 24 ++++++++++++++++++------ src/content/dependencies/listTracksByDate.js | 9 ++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 45b7dc1b..403f891f 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -68,6 +68,7 @@ export default { chunkTitles: {validate: v => v.strictArrayOf(v.isObject)}, chunkRows: {validate: v => v.strictArrayOf(v.isObject)}, + chunkRowAttributes: {validate: v => v.strictArrayOf(v.optional(v.isObject))}, showSkipToSection: {type: 'boolean', default: false}, chunkIDs: {validate: v => v.strictArrayOf(v.isString)}, @@ -165,9 +166,17 @@ export default { stitchArrays({ title: slots.chunkTitles, - rows: slots.chunkRows, id: slots.chunkIDs, - }).map(({title, rows, id}) => [ + + rows: slots.chunkRows, + rowAttributes: slots.chunkRowAttributes, + }).map(({ + title, + id, + + rows, + rowAttributes, + }) => [ relations.chunkHeading .clone() .slots({ @@ -178,10 +187,13 @@ export default { html.tag('dd', html.tag(listTag, - rows.map(row => - html.tag('li', - {class: row.stringsKey === 'rerelease' && 'rerelease'}, - formatListingString('chunk.item', row))))), + stitchArrays({ + row: rows, + attributes: rowAttributes ?? rows.map(() => null), + }).map(({row, attributes}) => + html.tag('li', + attributes, + formatListingString('chunk.item', row))))), ]), ]), ], diff --git a/src/content/dependencies/listTracksByDate.js b/src/content/dependencies/listTracksByDate.js index d6546e67..25beb739 100644 --- a/src/content/dependencies/listTracksByDate.js +++ b/src/content/dependencies/listTracksByDate.js @@ -71,8 +71,15 @@ export default { rerelease: rereleases, }).map(({trackLink, rerelease}) => (rerelease - ? {track: trackLink, stringsKey: 'rerelease'} + ? {stringsKey: 'rerelease', track: trackLink} : {track: trackLink}))), + + chunkRowAttributes: + data.rereleases.map(rereleases => + rereleases.map(rerelease => + (rerelease + ? {class: 'rerelease'} + : null))), }); }, }; -- cgit 1.3.0-6-gf8a5 From 44cebe7bfaf8f69ff6806e98524d3b5955f2cef2 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 15:49:31 -0400 Subject: content: generateListingPage: specially handle 'href' row attribute But not that specially. --- src/content/dependencies/generateListingPage.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 403f891f..4c86431d 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -191,9 +191,14 @@ export default { row: rows, attributes: rowAttributes ?? rows.map(() => null), }).map(({row, attributes}) => - html.tag('li', - attributes, - formatListingString('chunk.item', row))))), + (attributes?.href + ? html.tag('li', + html.tag('a', + attributes, + formatListingString('chunk.item', row))) + : html.tag('li', + attributes, + formatListingString('chunk.item', row)))))), ]), ]), ], -- cgit 1.3.0-6-gf8a5 From 2a7c3b90a8d66cea9b0f041a33f4c145628587eb Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 15:58:50 -0400 Subject: content: generateListingPage: code cleanup, add rowAttributes slot --- src/content/dependencies/generateListingPage.js | 90 +++++++++++++++---------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 4c86431d..3878d0eb 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -62,16 +62,38 @@ export default { }, slots: { - type: {validate: v => v.is('rows', 'chunks', 'custom')}, + type: { + validate: v => v.is('rows', 'chunks', 'custom'), + }, + + rows: { + validate: v => v.strictArrayOf(v.isObject), + }, + + rowAttributes: { + validate: v => v.strictArrayOf(v.optional(v.isObject)) + }, + + chunkTitles: { + validate: v => v.strictArrayOf(v.isObject), + }, + + chunkRows: { + validate: v => v.strictArrayOf(v.isObject), + }, - rows: {validate: v => v.strictArrayOf(v.isObject)}, + chunkRowAttributes: { + validate: v => v.strictArrayOf(v.optional(v.isObject)), + }, - chunkTitles: {validate: v => v.strictArrayOf(v.isObject)}, - chunkRows: {validate: v => v.strictArrayOf(v.isObject)}, - chunkRowAttributes: {validate: v => v.strictArrayOf(v.optional(v.isObject))}, + showSkipToSection: { + type: 'boolean', + default: false, + }, - showSkipToSection: {type: 'boolean', default: false}, - chunkIDs: {validate: v => v.strictArrayOf(v.isString)}, + chunkIDs: { + validate: v => v.strictArrayOf(v.isString), + }, listStyle: { validate: v => v.is('ordered', 'unordered'), @@ -82,11 +104,6 @@ export default { }, generate(data, relations, slots, {html, language}) { - const listTag = - (slots.listStyle === 'ordered' - ? 'ol' - : 'ul'); - const formatListingString = (contextStringsKey, options = {}) => { const baseStringsKey = `listingPage.${data.stringsKey}`; @@ -101,6 +118,24 @@ export default { return language.formatString(parts.join('.'), passOptions); }; + const formatRow = ({row, attributes}) => + (attributes?.href + ? html.tag('li', + html.tag('a', + attributes, + formatListingString('chunk.item', row))) + : html.tag('li', + attributes, + formatListingString('chunk.item', row))); + + const formatRowList = ({rows, rowAttributes}) => + html.tag( + (slots.listStyle === 'ordered' ? 'ol' : 'ul'), + stitchArrays({ + row: rows, + attributes: rowAttributes ?? rows.map(() => null), + }).map(formatRow)); + return relations.layout.slots({ title: formatListingString('title'), headingMode: 'sticky', @@ -133,10 +168,10 @@ export default { slots.content, slots.type === 'rows' && - html.tag(listTag, - slots.rows.map(row => - html.tag('li', - formatListingString('item', row)))), + formatRowList({ + rows: slots.rows, + rowAttributes: slots.rowAttributes, + }), slots.type === 'chunks' && html.tag('dl', [ @@ -167,16 +202,9 @@ export default { stitchArrays({ title: slots.chunkTitles, id: slots.chunkIDs, - rows: slots.chunkRows, rowAttributes: slots.chunkRowAttributes, - }).map(({ - title, - id, - - rows, - rowAttributes, - }) => [ + }).map(({title, id, rows, rowAttributes}) => [ relations.chunkHeading .clone() .slots({ @@ -186,19 +214,7 @@ export default { }), html.tag('dd', - html.tag(listTag, - stitchArrays({ - row: rows, - attributes: rowAttributes ?? rows.map(() => null), - }).map(({row, attributes}) => - (attributes?.href - ? html.tag('li', - html.tag('a', - attributes, - formatListingString('chunk.item', row))) - : html.tag('li', - attributes, - formatListingString('chunk.item', row)))))), + formatRowList({rows, rowAttributes})), ]), ]), ], -- cgit 1.3.0-6-gf8a5 From 7166db580d57504b5ec42d9d07078ea24f0b1149 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 16:01:05 -0400 Subject: content: listRandomPageLinks: port to chunks layout --- .../generateListRandomPageLinksAllAlbumsSection.js | 35 ---- .../generateListRandomPageLinksGroupSection.js | 51 ------ src/content/dependencies/listRandomPageLinks.js | 181 ++++++++++++++------- src/strings-default.yaml | 66 ++++---- 4 files changed, 156 insertions(+), 177 deletions(-) delete mode 100644 src/content/dependencies/generateListRandomPageLinksAllAlbumsSection.js delete mode 100644 src/content/dependencies/generateListRandomPageLinksGroupSection.js diff --git a/src/content/dependencies/generateListRandomPageLinksAllAlbumsSection.js b/src/content/dependencies/generateListRandomPageLinksAllAlbumsSection.js deleted file mode 100644 index e03252c9..00000000 --- a/src/content/dependencies/generateListRandomPageLinksAllAlbumsSection.js +++ /dev/null @@ -1,35 +0,0 @@ -import {sortChronologically} from '#wiki-data'; - -export default { - contentDependencies: ['generateListRandomPageLinksAlbumLink', 'linkGroup'], - extraDependencies: ['html', 'language', 'wikiData'], - - sprawl: ({albumData}) => ({albumData}), - - query: (sprawl) => ({ - albums: - sortChronologically(sprawl.albumData.slice()) - .filter(album => album.tracks.length > 1), - }), - - relations: (relation, query) => ({ - albumLinks: - query.albums - .map(album => relation('generateListRandomPageLinksAlbumLink', album)), - }), - - generate: (relations, {html, language}) => - html.tags([ - html.tag('dt', - language.$('listingPage.other.randomPages.fromAlbum')), - - html.tag('dd', - html.tag('ul', - relations.albumLinks - .map(albumLink => - html.tag('li', - language.$('listingPage.other.randomPages.album', { - album: albumLink, - }))))), - ]), -}; diff --git a/src/content/dependencies/generateListRandomPageLinksGroupSection.js b/src/content/dependencies/generateListRandomPageLinksGroupSection.js deleted file mode 100644 index d05be8f7..00000000 --- a/src/content/dependencies/generateListRandomPageLinksGroupSection.js +++ /dev/null @@ -1,51 +0,0 @@ -import {sortChronologically} from '#wiki-data'; - -export default { - contentDependencies: ['generateListRandomPageLinksAlbumLink', 'linkGroup'], - extraDependencies: ['html', 'language', 'wikiData'], - - sprawl: ({albumData}) => ({albumData}), - - query: (sprawl, group) => ({ - albums: - sortChronologically(sprawl.albumData.slice()) - .filter(album => album.groups.includes(group)) - .filter(album => album.tracks.length > 1), - }), - - relations: (relation, query, sprawl, group) => ({ - groupLink: - relation('linkGroup', group), - - albumLinks: - query.albums - .map(album => relation('generateListRandomPageLinksAlbumLink', album)), - }), - - generate: (relations, {html, language}) => - html.tags([ - html.tag('dt', - language.$('listingPage.other.randomPages.fromGroup', { - group: relations.groupLink, - - randomAlbum: - html.tag('a', - {href: '#', 'data-random': 'album-in-group-dl'}, - language.$('listingPage.other.randomPages.fromGroup.randomAlbum')), - - randomTrack: - html.tag('a', - {href: '#', 'data-random': 'track-in-group-dl'}, - language.$('listingPage.other.randomPages.fromGroup.randomTrack')), - })), - - html.tag('dd', - html.tag('ul', - relations.albumLinks - .map(albumLink => - html.tag('li', - language.$('listingPage.other.randomPages.album', { - album: albumLink, - }))))), - ]), -}; diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index 87e5f5aa..5e74b4ac 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -1,47 +1,102 @@ import {empty} from '#sugar'; +import {sortChronologically} from '#wiki-data'; export default { contentDependencies: [ 'generateListingPage', - 'generateListRandomPageLinksAllAlbumsSection', - 'generateListRandomPageLinksGroupSection', + 'generateListRandomPageLinksAlbumLink', + 'linkGroup', ], extraDependencies: ['html', 'language', 'wikiData'], sprawl: ({wikiInfo}) => ({wikiInfo}), - query: ({wikiInfo: {divideTrackListsByGroups: groups}}, spec) => ({ - spec, - groups, - divideByGroups: !empty(groups), - }), + query(sprawl, spec) { + const query = {spec}; - relations: (relation, query) => ({ - page: relation('generateListingPage', query.spec), + const groups = sprawl.wikiInfo.divideTrackListsByGroups; - allAlbumsSection: - (query.divideByGroups - ? null - : relation('generateListRandomPageLinksAllAlbumsSection')), + query.divideByGroups = !empty(groups); - groupSections: - (query.divideByGroups - ? query.groups - .map(group => relation('generateListRandomPageLinksGroupSection', group)) - : null), - }), + if (query.divideByGroups) { + query.groups = groups; + + query.groupAlbums = + groups + .map(group => + group.albums.filter(album => album.tracks.length > 1)); + } else { + query.undividedAlbums = + sortChronologically(sprawl.albumData.slice()) + .filter(album => album.tracks.length > 1); + } + + return query; + }, + + relations(relation, query) { + const relations = {}; + + relations.page = + relation('generateListingPage', query.spec); + + if (query.divideByGroups) { + relations.groupLinks = + query.groups + .map(group => relation('linkGroup', group)); + + relations.groupAlbumLinks = + query.groupAlbums + .map(albums => albums + .map(album => + relation('generateListRandomPageLinksAlbumLink', album))); + } else { + relations.undividedAlbumLinks = + query.undividedAlbums + .map(album => + relation('generateListRandomPageLinksAlbumLink', album)); + } + + return relations; + }, generate(relations, {html, language}) { + const miscellaneousChunkRows = [ + { + stringsKey: 'randomArtist', + + mainLink: + html.tag('a', + {href: '#', 'data-random': 'artist'}, + language.$('listingPage.other.randomPages.chunk.item.randomArtist.mainLink')), + + atLeastTwoContributions: + html.tag('a', + {href: '#', 'data-random': 'artist-more-than-one-contrib'}, + language.$('listingPage.other.randomPages.chunk.item.randomArtist.atLeastTwoContributions')), + }, + + {stringsKey: 'randomAlbumWholeSite'}, + {stringsKey: 'randomTrackWholeSite'}, + ]; + + const miscellaneousChunkRowAttributes = [ + null, + {href: '#', 'data-random': 'album'}, + {href: '#','data-random': 'track'}, + ]; + return relations.page.slots({ - type: 'custom', + type: 'chunks', + content: [ html.tag('p', language.$('listingPage.other.randomPages.chooseLinkLine', { fromPart: - (empty(relations.groupSections) - ? language.$('listingPage.other.randomPages.chooseLinkLine.fromPart.notDividedByGroups') - : language.$('listingPage.other.randomPages.chooseLinkLine.fromPart.dividedByGroups')), + (relations.groupLinks + ? language.$('listingPage.other.randomPages.chooseLinkLine.fromPart.dividedByGroups') + : language.$('listingPage.other.randomPages.chooseLinkLine.fromPart.notDividedByGroups')), browserSupportPart: language.$('listingPage.other.randomPages.chooseLinkLine.browserSupportPart'), @@ -54,40 +109,54 @@ export default { html.tag('p', {class: 'js-show-once-data'}, language.$('listingPage.other.randomPages.dataLoadedLine')), + ], + + chunkTitles: [ + {stringsKey: 'misc'}, + + ... + (relations.groupLinks + ? relations.groupLinks.map(groupLink => ({ + stringsKey: 'fromGroup', + + group: groupLink, + + randomAlbum: + html.tag('a', + {href: '#', 'data-random': 'album-in-group-dl'}, + language.$('listingPage.other.randomPages.chunk.title.fromGroup.randomAlbum')), + + randomTrack: + html.tag('a', + {href: '#', 'data-random': 'track-in-group-dl'}, + language.$('listingPage.other.randomPages.chunk.title.fromGroup.randomTrack')), + })) + : [{stringsKey: 'fromAlbum'}]), + ], + + chunkRows: [ + miscellaneousChunkRows, + + ... + (relations.groupAlbumLinks + ? relations.groupAlbumLinks.map(albumLinks => + albumLinks.map(albumLink => ({ + stringsKey: 'album', + album: albumLink, + }))) + : relations.albumLinks.map(albumLink => ({ + stringsKey: 'album', + album: albumLink, + }))), + ], - html.tag('dl', [ - html.tag('dt', - language.$('listingPage.other.randomPages.misc')), - - html.tag('dd', - html.tag('ul', [ - html.tag('li', - language.$('listingPage.other.randomPages.misc.randomArtist', { - mainLink: - html.tag('a', - {href: '#', 'data-random': 'artist'}, - language.$('listingPage.other.randomPages.misc.randomArtist.mainLink')), - - atLeastTwoContributions: - html.tag('a', - {href: '#', 'data-random': 'artist-more-than-one-contrib'}, - language.$('listingPage.other.randomPages.misc.randomArtist.atLeastTwoContributions')), - })), - - html.tag('li', - html.tag('a', - {href: '#', 'data-random': 'album'}, - language.$('listingPage.other.randomPages.misc.randomAlbumWholeSite'))), - - html.tag('li', - html.tag('a', - {href: '#', 'data-random': 'track'}, - language.$('listingPage.other.randomPages.misc.randomTrackWholeSite'))), - ])), - - relations.allAlbumsSection, - relations.groupSections, - ]), + chunkRowAttributes: [ + miscellaneousChunkRowAttributes, + ... + (relations.groupAlbumLinks + ? relations.groupAlbumLinks.map(albumLinks => + albumLinks.map(() => null)) + : [relations.albumLinks.map(() => null)]), ], }); }, diff --git a/src/strings-default.yaml b/src/strings-default.yaml index bb244279..86a46e68 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -1568,43 +1568,39 @@ listingPage: dataLoadedLine: >- (Data files have finished being downloaded. The links should work!) - # misc: - # The first chunk in the list includes general links which - # bring you to some random page across the whole site! - - misc: - _: "Miscellaneous:" - randomArtist: - _: "{MAIN_LINK} ({AT_LEAST_TWO_CONTRIBUTIONS})" - mainLink: "Random Artist" - atLeastTwoContributions: "at least 2 contributions" - randomAlbumWholeSite: "Random Album (whole site)" - randomTrackWholeSite: "Random Track (whole site)" - - # fromGroup: - # Provided the wiki has "Divide Track Lists By Groups" set, - # the remaining chunks are one for each of those groups, each - # with a list of links for albums from the group that bring - # you to a random track from the chosen album. - - fromGroup: - _: "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})" - randomAlbum: "Random Album" - randomTrack: "Random Track" - - # fromAlbum: - # If the wiki doesn't have "Divide Track Lists By Groups", - # all albums across the wiki are grouped in one list. - # (There aren't "random album" and "random track" links like - # for groups because those are already included at the top, - # under the "miscellaneous" chunk.) - - fromAlbum: "From an album:" + chunk: - # album: - # Album entries under each group. + title: + misc: "Miscellaneous:" + + # fromAlbum: + # If the wiki hasn't got "Divide Track Lists By Groups" + # set, all albums across the wiki are grouped in one + # long chunk. + + fromAlbum: "From an album:" + + # fromGroup: + # If the wiki does have "Divide Track Lists By Groups" + # set, there's one chunk past Miscellaneous for each of + # those groups, listing all the albums from that group, + # each of which links to a random track from that album. + + fromGroup: + _: "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})" + randomAlbum: "Random Album" + randomTrack: "Random Track" + + item: + album: "{ALBUM}" + + randomArtist: + _: "{MAIN_LINK} ({AT_LEAST_TWO_CONTRIBUTIONS})" + mainLink: "Random Artist" + atLeastTwoContributions: "at least 2 contributions" - album: "{ALBUM}" + randomAlbumWholeSite: "Random Album (whole site)" + randomTrackWholeSite: "Random Track (whole site)" # # newsIndex: -- cgit 1.3.0-6-gf8a5 From b8e612f9723ef1b890a1af8745e3f165220ce9d1 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Tue, 7 Nov 2023 20:31:57 -0400 Subject: content, client, css: accents in content headings --- src/content/dependencies/generateContentHeading.js | 26 +++++++++++++++++++--- src/static/client2.js | 4 ++++ src/static/site5.css | 6 +++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/content/dependencies/generateContentHeading.js b/src/content/dependencies/generateContentHeading.js index ccaf1076..56f68cb3 100644 --- a/src/content/dependencies/generateContentHeading.js +++ b/src/content/dependencies/generateContentHeading.js @@ -1,19 +1,39 @@ export default { extraDependencies: ['html'], + contentDependencies: ['generateColorStyleVariables'], + + relations: (relation) => ({ + colorVariables: relation('generateColorStyleVariables'), + }), slots: { title: {type: 'html'}, + accent: {type: 'html'}, + + color: {validate: v => v.isColor}, + id: {type: 'string'}, tag: {type: 'string', default: 'p'}, }, - generate(slots, {html}) { + generate(relations, slots, {html}) { return html.tag(slots.tag, { class: 'content-heading', id: slots.id, tabindex: '0', - }, - slots.title); + + style: + slots.color && + relations.colorVariables + .slot('color', slots.color) + .content, + }, [ + slots.title, + + html.tag('span', + {[html.onlyIfContent]: true, class: 'content-heading-accent'}, + slots.accent), + ]); } } diff --git a/src/static/client2.js b/src/static/client2.js index 28882a88..0ec052bd 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -879,6 +879,10 @@ function updateStickySubheadingContent(index) { } for (const child of closestHeading.childNodes) { + if (child.classList?.contains('content-heading-accent')) { + continue; + } + if (child.tagName === 'A') { for (const grandchild of child.childNodes) { stickySubheading.appendChild(grandchild.cloneNode(true)); diff --git a/src/static/site5.css b/src/static/site5.css index 0eb7dcda..afce9b0f 100644 --- a/src/static/site5.css +++ b/src/static/site5.css @@ -1275,6 +1275,12 @@ html[data-url-key="localized.home"] .carousel-container { animation-delay: 125ms; } +.content-heading .content-heading-accent { + font-weight: normal; + font-size: 1rem; + margin-left: 0.25em; +} + h3.content-heading { clear: both; } -- cgit 1.3.0-6-gf8a5 From 869548723002ebf2f3a501c4105cdf6db7ac8aa7 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 16:33:20 -0400 Subject: content: generateListingPage: formatListingString cleanup --- src/content/dependencies/generateListingPage.js | 54 +++++++++++++++++-------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 3878d0eb..6eee45b8 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -104,29 +104,43 @@ export default { }, generate(data, relations, slots, {html, language}) { - const formatListingString = (contextStringsKey, options = {}) => { - const baseStringsKey = `listingPage.${data.stringsKey}`; - - const parts = [baseStringsKey, contextStringsKey]; - - const {stringsKey, ...passOptions} = options; + function formatListingString({ + context, + provided = {}, + }) { + const parts = ['listingPage', data.stringsKey]; + + if (Array.isArray(context)) { + parts.push(...context); + } else { + parts.push(context); + } - if (stringsKey) { - parts.push(options.stringsKey); + if (provided.stringsKey) { + parts.push(provided.stringsKey); } - return language.formatString(parts.join('.'), passOptions); - }; + const options = {...provided}; + delete options.stringsKey; + + return language.formatString(...parts, options); + } const formatRow = ({row, attributes}) => (attributes?.href ? html.tag('li', html.tag('a', attributes, - formatListingString('chunk.item', row))) + formatListingString({ + context: 'chunk.item', + provided: row, + }))) : html.tag('li', attributes, - formatListingString('chunk.item', row))); + formatListingString({ + context: 'chunk.item', + provided: row, + }))); const formatRowList = ({rows, rowAttributes}) => html.tag( @@ -137,7 +151,8 @@ export default { }).map(formatRow)); return relations.layout.slots({ - title: formatListingString('title'), + title: formatListingString({context: 'title'}), + headingMode: 'sticky', mainContent: [ @@ -193,8 +208,10 @@ export default { hash: id, content: html.normalize( - formatListingString('chunk.title', title) - .toString() + formatListingString({ + context: 'chunk.title', + provided: title, + }).toString() .replace(/:$/, '')), }))))), ], @@ -209,8 +226,13 @@ export default { .clone() .slots({ tag: 'dt', - title: formatListingString('chunk.title', title), id, + + title: + formatListingString({ + context: 'chunk.title', + provided: title, + }), }), html.tag('dd', -- cgit 1.3.0-6-gf8a5 From f187e32c858e46af9ee2717a20ae4095f0fef325 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 16:34:15 -0400 Subject: content: generateListingPage: add chunkTitleAccents slot --- src/content/dependencies/generateListingPage.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 6eee45b8..b3d6899d 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -78,6 +78,10 @@ export default { validate: v => v.strictArrayOf(v.isObject), }, + chunkTitleAccents: { + validate: v => v.strictArrayOf(v.optional(v.isObject)), + }, + chunkRows: { validate: v => v.strictArrayOf(v.isObject), }, @@ -218,10 +222,11 @@ export default { stitchArrays({ title: slots.chunkTitles, + titleAccent: slots.chunkTitleAccents, id: slots.chunkIDs, rows: slots.chunkRows, rowAttributes: slots.chunkRowAttributes, - }).map(({title, id, rows, rowAttributes}) => [ + }).map(({title, titleAccent, id, rows, rowAttributes}) => [ relations.chunkHeading .clone() .slots({ @@ -233,6 +238,13 @@ export default { context: 'chunk.title', provided: title, }), + + accent: + titleAccent && + formatListingString({ + context: ['chunk.title', title.stringsKey, 'accent'], + provided: titleAccent, + }), }), html.tag('dd', -- cgit 1.3.0-6-gf8a5 From 082f693274a62c9f826fd84def417f243c21d612 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 16:35:10 -0400 Subject: content: listRandomPageLinks: use chunk title accents --- src/content/dependencies/listRandomPageLinks.js | 16 ++++++++++++---- src/strings-default.yaml | 9 ++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index 5e74b4ac..089289f7 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -118,20 +118,28 @@ export default { (relations.groupLinks ? relations.groupLinks.map(groupLink => ({ stringsKey: 'fromGroup', - group: groupLink, + })) + : [{stringsKey: 'fromAlbum'}]), + ], + chunkTitleAccents: [ + null, + + ... + (relations.groupLinks + ? relations.groupLinks.map(() => ({ randomAlbum: html.tag('a', {href: '#', 'data-random': 'album-in-group-dl'}, - language.$('listingPage.other.randomPages.chunk.title.fromGroup.randomAlbum')), + language.$('listingPage.other.randomPages.chunk.title.fromGroup.accent.randomAlbum')), randomTrack: html.tag('a', {href: '#', 'data-random': 'track-in-group-dl'}, - language.$('listingPage.other.randomPages.chunk.title.fromGroup.randomTrack')), + language.$('listingPage.other.randomPages.chunk.title.fromGroup.accent.randomTrack')), })) - : [{stringsKey: 'fromAlbum'}]), + : [null]), ], chunkRows: [ diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 86a46e68..a21758e7 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -1587,9 +1587,12 @@ listingPage: # each of which links to a random track from that album. fromGroup: - _: "From {GROUP}: ({RANDOM_ALBUM}, {RANDOM_TRACK})" - randomAlbum: "Random Album" - randomTrack: "Random Track" + _: "From {GROUP}:" + + accent: + _: "({RANDOM_ALBUM}, {RANDOM_TRACK})" + randomAlbum: "Random Album" + randomTrack: "Random Track" item: album: "{ALBUM}" -- cgit 1.3.0-6-gf8a5 From f6a0bf1d7b4652a7dd04ed3340010ee2a6e47b7f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 16:40:18 -0400 Subject: content: listRandomPageLinks: show skip to section --- src/content/dependencies/generateListingPage.js | 2 +- src/content/dependencies/listRandomPageLinks.js | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index b3d6899d..95c039eb 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -96,7 +96,7 @@ export default { }, chunkIDs: { - validate: v => v.strictArrayOf(v.isString), + validate: v => v.strictArrayOf(v.optional(v.isString)), }, listStyle: { diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index 089289f7..375a72d7 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -61,7 +61,19 @@ export default { return relations; }, - generate(relations, {html, language}) { + data(query) { + const data = {}; + + if (query.divideByGroups) { + data.groupDirectories = + query.groups + .map(group => group.directory); + } + + return data; + }, + + generate(data, relations, {html, language}) { const miscellaneousChunkRows = [ { stringsKey: 'randomArtist', @@ -111,6 +123,13 @@ export default { language.$('listingPage.other.randomPages.dataLoadedLine')), ], + showSkipToSection: true, + + chunkIDs: [ + null, + ...data.groupDirectories, + ], + chunkTitles: [ {stringsKey: 'misc'}, -- cgit 1.3.0-6-gf8a5 From 1d991bb4bc877363532971a74f70e55939c637bb Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 20:16:30 -0400 Subject: upd8, data, test: export internal strings path cleanly, fix tests --- src/data/language.js | 9 +++++++++ src/repl.js | 9 ++------- src/upd8.js | 7 ++----- test/lib/content-function.js | 4 ++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/data/language.js b/src/data/language.js index 6ffc31e0..3fc14da7 100644 --- a/src/data/language.js +++ b/src/data/language.js @@ -1,6 +1,7 @@ import EventEmitter from 'node:events'; import {readFile} from 'node:fs/promises'; import path from 'node:path'; +import {fileURLToPath} from 'node:url'; import chokidar from 'chokidar'; import he from 'he'; // It stands for "HTML Entities", apparently. Cursed. @@ -18,6 +19,14 @@ import { const {Language} = T; +export const DEFAULT_STRINGS_FILE = 'strings-default.yaml'; + +export const internalDefaultStringsFile = + path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../', + DEFAULT_STRINGS_FILE); + export function processLanguageSpec(spec, {existingCode = null} = {}) { const { 'meta.languageCode': code, diff --git a/src/repl.js b/src/repl.js index 7a6f5c45..3f5d752a 100644 --- a/src/repl.js +++ b/src/repl.js @@ -5,7 +5,7 @@ import {fileURLToPath} from 'node:url'; import {logError, logWarn, parseOptions} from '#cli'; import {isMain} from '#node-utils'; -import {processLanguageFile} from '#language'; +import {internalDefaultStringsFile, processLanguageFile} from '#language'; import {bindOpts, showAggregate} from '#sugar'; import {generateURLs, urlSpec} from '#urls'; import {quickLoadAllFromYAML} from '#yaml'; @@ -16,8 +16,6 @@ import * as serialize from '#serialize'; import * as sugar from '#sugar'; import * as wikiDataUtils from '#wiki-data'; -import {DEFAULT_STRINGS_FILE} from './upd8.js'; - const __dirname = path.dirname(fileURLToPath(import.meta.url)); export async function getContextAssignments({ @@ -45,10 +43,7 @@ export async function getContextAssignments({ let language; try { - language = await processLanguageFile( - path.join( - path.dirname(fileURLToPath(import.meta.url)), - DEFAULT_STRINGS_FILE)); + language = await processLanguageFile(internalDefaultStringsFile); } catch (error) { console.error(error); logWarn`Failed to create Language object`; diff --git a/src/upd8.js b/src/upd8.js index 24d0b92b..db73c412 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -39,7 +39,8 @@ import {fileURLToPath} from 'node:url'; import wrap from 'word-wrap'; import {displayCompositeCacheAnalysis} from '#composite'; -import {processLanguageFile, watchLanguageFile} from '#language'; +import {processLanguageFile, watchLanguageFile, internalDefaultStringsFile} + from '#language'; import {isMain, traverse} from '#node-utils'; import bootRepl from '#repl'; import {empty, showAggregate, withEntries} from '#sugar'; @@ -93,8 +94,6 @@ try { const BUILD_TIME = new Date(); -export const DEFAULT_STRINGS_FILE = 'strings-default.yaml'; - const STATUS_NOT_STARTED = `not started`; const STATUS_NOT_APPLICABLE = `not applicable`; const STATUS_STARTED_NOT_DONE = `started but not yet done`; @@ -1104,8 +1103,6 @@ async function main() { let internalDefaultLanguage; let internalDefaultLanguageWatcher; - const internalDefaultStringsFile = path.join(__dirname, DEFAULT_STRINGS_FILE); - let errorLoadingInternalDefaultLanguage = false; if (noLanguageReloading) { diff --git a/test/lib/content-function.js b/test/lib/content-function.js index 5cb499b1..24363e60 100644 --- a/test/lib/content-function.js +++ b/test/lib/content-function.js @@ -8,7 +8,7 @@ import {getColors} from '#colors'; import {quickLoadContentDependencies} from '#content-dependencies'; import {quickEvaluate} from '#content-function'; import * as html from '#html'; -import {processLanguageFile} from '#language'; +import {internalDefaultStringsFile, processLanguageFile} from '#language'; import {empty, showAggregate} from '#sugar'; import {generateURLs, thumb, urlSpec} from '#urls'; @@ -22,7 +22,7 @@ export function testContentFunctions(t, message, fn) { t.test(message, async t => { let loadedContentDependencies; - const language = await processLanguageFile('./src/strings-default.json'); + const language = await processLanguageFile(internalDefaultStringsFile); const mocks = []; const evaluate = ({ -- cgit 1.3.0-6-gf8a5 From 0bc5cfceebed54f357369daec80ee43e2bf7cb76 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Thu, 9 Nov 2023 20:23:52 -0400 Subject: test: provide some sensible defaults for more extraDependencies --- test/lib/content-function.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/lib/content-function.js b/test/lib/content-function.js index 24363e60..a4c5dac1 100644 --- a/test/lib/content-function.js +++ b/test/lib/content-function.js @@ -50,8 +50,15 @@ export function testContentFunctions(t, message, fn) { thumb, to, urls, + + pagePath: ['home'], appendIndexHTML: false, getColors: c => getColors(c, {chroma}), + + wikiData: { + wikiInfo: {}, + }, + ...extraDependencies, }, }); -- cgit 1.3.0-6-gf8a5 From 6f9d122f9d1ad5bb7e0aad917bbc1ff218e9b4df Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Fri, 10 Nov 2023 17:46:45 -0400 Subject: content: listRandomPageLinks: general syntax/slot fixes --- src/content/dependencies/listRandomPageLinks.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/content/dependencies/listRandomPageLinks.js b/src/content/dependencies/listRandomPageLinks.js index 375a72d7..0b904019 100644 --- a/src/content/dependencies/listRandomPageLinks.js +++ b/src/content/dependencies/listRandomPageLinks.js @@ -10,7 +10,7 @@ export default { extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({wikiInfo}), + sprawl: ({albumData, wikiInfo}) => ({albumData, wikiInfo}), query(sprawl, spec) { const query = {spec}; @@ -125,10 +125,10 @@ export default { showSkipToSection: true, - chunkIDs: [ - null, - ...data.groupDirectories, - ], + chunkIDs: + (data.groupDirectories + ? [null, ...data.groupDirectories] + : null), chunkTitles: [ {stringsKey: 'misc'}, @@ -171,10 +171,12 @@ export default { stringsKey: 'album', album: albumLink, }))) - : relations.albumLinks.map(albumLink => ({ - stringsKey: 'album', - album: albumLink, - }))), + : [ + relations.undividedAlbumLinks.map(albumLink => ({ + stringsKey: 'album', + album: albumLink, + })), + ]), ], chunkRowAttributes: [ @@ -183,7 +185,7 @@ export default { (relations.groupAlbumLinks ? relations.groupAlbumLinks.map(albumLinks => albumLinks.map(() => null)) - : [relations.albumLinks.map(() => null)]), + : [relations.undividedAlbumLinks.map(() => null)]), ], }); }, -- cgit 1.3.0-6-gf8a5 From 2de59682e52cd9121e53aec94a27ba78a09abb3e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Fri, 10 Nov 2023 17:47:16 -0400 Subject: content: generateFlashActSidebar: try to behave without hsmusic data --- src/content/dependencies/generateFlashActSidebar.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/content/dependencies/generateFlashActSidebar.js b/src/content/dependencies/generateFlashActSidebar.js index bd6063c9..80072483 100644 --- a/src/content/dependencies/generateFlashActSidebar.js +++ b/src/content/dependencies/generateFlashActSidebar.js @@ -1,5 +1,6 @@ import find from '#find'; import {stitchArrays} from '#sugar'; +import {filterMultipleArrays} from '#wiki-data'; export default { contentDependencies: ['linkFlash', 'linkFlashAct', 'linkFlashIndex'], @@ -11,10 +12,12 @@ export default { query(sprawl, act, flash) { const findFlashAct = directory => - find.flashAct(directory, sprawl.flashActData, {mode: 'error'}); + find.flashAct(directory, sprawl.flashActData, {mode: 'quiet'}); + + const homestuckSide1 = findFlashAct('flash-act:a1'); const sideFirstActs = [ - findFlashAct('flash-act:a1'), + sprawl.flashActData[0], findFlashAct('flash-act:a6a1'), findFlashAct('flash-act:hiveswap'), findFlashAct('flash-act:cool-and-new-web-comic'), @@ -22,7 +25,9 @@ export default { ]; const sideNames = [ - `Side 1 (Acts 1-5)`, + (homestuckSide1 + ? `Side 1 (Acts 1-5)` + : `All flashes & games`), `Side 2 (Acts 6-7)`, `Additional Canon`, `Fan Adventures`, @@ -30,13 +35,18 @@ export default { ]; const sideColors = [ - '#4ac925', + (homestuckSide1 + ? '#4ac925' + : null), '#3796c6', '#f2a400', '#c466ff', '#32c7fe', ]; + filterMultipleArrays(sideFirstActs, sideNames, sideColors, + firstAct => firstAct); + const sideFirstActIndexes = sideFirstActs .map(act => sprawl.flashActData.indexOf(act)); -- cgit 1.3.0-6-gf8a5 From ad943caefcacf62347199a73a90dc704cd8e369c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Fri, 10 Nov 2023 17:47:44 -0400 Subject: content: generateListingPage: fix row-based listings... oops... --- src/content/dependencies/generateListingPage.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 95c039eb..2050d62d 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -1,4 +1,4 @@ -import {empty, stitchArrays} from '#sugar'; +import {bindOpts, empty, stitchArrays} from '#sugar'; export default { contentDependencies: [ @@ -130,29 +130,33 @@ export default { return language.formatString(...parts, options); } - const formatRow = ({row, attributes}) => + const formatRow = ({context, row, attributes}) => (attributes?.href ? html.tag('li', html.tag('a', attributes, formatListingString({ - context: 'chunk.item', + context, provided: row, }))) : html.tag('li', attributes, formatListingString({ - context: 'chunk.item', + context, provided: row, }))); - const formatRowList = ({rows, rowAttributes}) => + const formatRowList = ({context, rows, rowAttributes}) => html.tag( (slots.listStyle === 'ordered' ? 'ol' : 'ul'), stitchArrays({ row: rows, attributes: rowAttributes ?? rows.map(() => null), - }).map(formatRow)); + }).map( + bindOpts(formatRow, { + [bindOpts.bindIndex]: 0, + context, + }))); return relations.layout.slots({ title: formatListingString({context: 'title'}), @@ -188,6 +192,7 @@ export default { slots.type === 'rows' && formatRowList({ + context: 'item', rows: slots.rows, rowAttributes: slots.rowAttributes, }), @@ -248,7 +253,11 @@ export default { }), html.tag('dd', - formatRowList({rows, rowAttributes})), + formatRowList({ + context: 'chunk.item', + rows, + rowAttributes, + })), ]), ]), ], -- cgit 1.3.0-6-gf8a5 From f7007f0a090f22929b450ac816757c49e17b9ef1 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Fri, 10 Nov 2023 17:48:07 -0400 Subject: content: generatePageLayout: don't assume custom footer content --- src/content/dependencies/generatePageLayout.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js index 72dfbae5..5fa6e751 100644 --- a/src/content/dependencies/generatePageLayout.js +++ b/src/content/dependencies/generatePageLayout.js @@ -85,8 +85,10 @@ export default { relations.stickyHeadingContainer = relation('generateStickyHeadingContainer'); - relations.defaultFooterContent = - relation('transformContent', sprawl.footerContent); + if (sprawl.footerContent) { + relations.defaultFooterContent = + relation('transformContent', sprawl.footerContent); + } relations.colorStyleRules = relation('generateColorStyleRules'); @@ -231,7 +233,7 @@ export default { let footerContent = slots.footerContent; - if (html.isBlank(footerContent)) { + if (html.isBlank(footerContent) && relations.defaultFooterContent) { footerContent = relations.defaultFooterContent .slot('mode', 'multiline'); } -- cgit 1.3.0-6-gf8a5 From 3f236319355b093b336e70119a7127bd23693ec2 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Fri, 10 Nov 2023 17:48:26 -0400 Subject: content: generateWikiHomeAlbumsRow: support albums without covers --- src/content/dependencies/generateWikiHomeAlbumsRow.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/content/dependencies/generateWikiHomeAlbumsRow.js b/src/content/dependencies/generateWikiHomeAlbumsRow.js index cb0860f5..2c6a147e 100644 --- a/src/content/dependencies/generateWikiHomeAlbumsRow.js +++ b/src/content/dependencies/generateWikiHomeAlbumsRow.js @@ -11,7 +11,7 @@ export default { 'transformContent', ], - extraDependencies: ['wikiData'], + extraDependencies: ['language', 'wikiData'], sprawl({albumData}, row) { const sprawl = {}; @@ -90,12 +90,14 @@ export default { data.paths = sprawl.albums .map(album => - ['media.albumCover', album.directory, album.coverArtFileExtension]); + (album.hasCoverArt + ? ['media.albumCover', album.directory, album.coverArtFileExtension] + : null)); return data; }, - generate(data, relations) { + generate(data, relations, {language}) { // Grids and carousels share some slots! Very convenient. const commonSlots = {}; @@ -106,8 +108,15 @@ export default { stitchArrays({ image: relations.images, path: data.paths, - }).map(({image, path}) => - image.slot('path', path)); + name: data.names, + }).map(({image, path, name}) => + image.slots({ + path, + missingSourceContent: + language.$('misc.albumGrid.noCoverArt', { + album: name, + }), + })); commonSlots.actionLinks = (relations.actionLinks -- cgit 1.3.0-6-gf8a5 From 75ec07ac18cb91eb2e019aefce8f60488d794de1 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Fri, 10 Nov 2023 17:48:48 -0400 Subject: data: provide default wiki color in data, not css Fixes #169! --- src/data/things/wiki-info.js | 12 +++++++++--- src/static/site5.css | 8 +------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/data/things/wiki-info.js b/src/data/things/wiki-info.js index 89053d62..3db9727b 100644 --- a/src/data/things/wiki-info.js +++ b/src/data/things/wiki-info.js @@ -1,9 +1,8 @@ import {input} from '#composite'; import find from '#find'; -import {isLanguageCode, isName, isURL} from '#validators'; +import {isColor, isLanguageCode, isName, isURL} from '#validators'; import { - color, flag, name, referenceList, @@ -32,7 +31,14 @@ export class WikiInfo extends Thing { }, }, - color: color(), + color: { + flags: {update: true, expose: true}, + update: {validate: isColor}, + + expose: { + transform: color => color ?? '#0088ff', + }, + }, // One-line description used for <meta rel="description"> tag. description: simpleString(), diff --git a/src/static/site5.css b/src/static/site5.css index afce9b0f..014e6d25 100644 --- a/src/static/site5.css +++ b/src/static/site5.css @@ -3,13 +3,7 @@ * no need to re-run upd8.js when tweaking values here. Handy! */ -:root { - --primary-color: #0088ff; -} - -/* Layout - Common - * - */ +/* Layout - Common */ body { margin: 10px; -- cgit 1.3.0-6-gf8a5 From 95b1ed2f163c1a2be7b6582499cecb8963548155 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Sun, 12 Nov 2023 15:03:21 -0400 Subject: content: generateFlashIndexPage: don't show jump links if empty --- src/content/dependencies/generateFlashIndexPage.js | 38 ++++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/content/dependencies/generateFlashIndexPage.js b/src/content/dependencies/generateFlashIndexPage.js index ad1dab94..5fc62ab3 100644 --- a/src/content/dependencies/generateFlashIndexPage.js +++ b/src/content/dependencies/generateFlashIndexPage.js @@ -1,4 +1,4 @@ -import {stitchArrays} from '#sugar'; +import {empty, stitchArrays} from '#sugar'; export default { contentDependencies: [ @@ -95,23 +95,25 @@ export default { mainClasses: ['flash-index'], mainContent: [ - html.tag('p', - {class: 'quick-info'}, - language.$('misc.jumpTo')), - - html.tag('ul', - {class: 'quick-info'}, - stitchArrays({ - colorVariables: relations.jumpLinkColorVariables, - anchor: data.jumpLinkAnchors, - color: data.jumpLinkColors, - label: data.jumpLinkLabels, - }).map(({colorVariables, anchor, color, label}) => - html.tag('li', - html.tag('a', { - href: '#' + anchor, - style: colorVariables.slot('color', color).content, - }, label)))), + !empty(data.jumpLinkLabels) && [ + html.tag('p', + {class: 'quick-info'}, + language.$('misc.jumpTo')), + + html.tag('ul', + {class: 'quick-info'}, + stitchArrays({ + colorVariables: relations.jumpLinkColorVariables, + anchor: data.jumpLinkAnchors, + color: data.jumpLinkColors, + label: data.jumpLinkLabels, + }).map(({colorVariables, anchor, color, label}) => + html.tag('li', + html.tag('a', { + href: '#' + anchor, + style: colorVariables.slot('color', color).content, + }, label)))), + ], stitchArrays({ colorVariables: relations.actColorVariables, -- cgit 1.3.0-6-gf8a5 From 51097802bd2f9d58d4062235858f588a4cf58d93 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Sun, 12 Nov 2023 15:16:19 -0400 Subject: content: generateFooterLocalizationLinks: refactor + hide if empty --- .../generateFooterLocalizationLinks.js | 53 ++++++++++++++-------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/content/dependencies/generateFooterLocalizationLinks.js b/src/content/dependencies/generateFooterLocalizationLinks.js index 5df83566..86e6c61a 100644 --- a/src/content/dependencies/generateFooterLocalizationLinks.js +++ b/src/content/dependencies/generateFooterLocalizationLinks.js @@ -1,3 +1,6 @@ +import {stitchArrays} from '#sugar'; +import {sortByName} from '#wiki-data'; + export default { extraDependencies: [ 'defaultLanguage', @@ -16,25 +19,37 @@ export default { pagePath, to, }) { - const links = Object.entries(languages) - .filter(([code, language]) => code !== 'default' && !language.hidden) - .map(([code, language]) => language) - .sort(({name: a}, {name: b}) => (a < b ? -1 : a > b ? 1 : 0)) - .map((language) => - html.tag('span', - html.tag('a', - { - href: - language === defaultLanguage - ? to( - 'localizedDefaultLanguage.' + pagePath[0], - ...pagePath.slice(1)) - : to( - 'localizedWithBaseDirectory.' + pagePath[0], - language.code, - ...pagePath.slice(1)), - }, - language.name))); + const switchableLanguages = + Object.entries(languages) + .filter(([code, language]) => code !== 'default' && !language.hidden) + .map(([code, language]) => language); + + if (switchableLanguages.length <= 1) { + return html.blank(); + } + + sortByName(switchableLanguages); + + const [pagePathSubkey, ...pagePathArgs] = pagePath; + + const linkPaths = + switchableLanguages.map(language => + (language === defaultLanguage + ? (['localizedDefaultLanguage.' + pagePathSubkey, + ...pagePathArgs]) + : (['localizedWithBaseDirectory.' + pagePathSubkey, + language.code, + ...pagePathArgs]))); + + const links = + stitchArrays({ + language: switchableLanguages, + linkPath: linkPaths, + }).map(({language, linkPath}) => + html.tag('span', + html.tag('a', + {href: to(...linkPath)}, + language.name))); return html.tag('div', {class: 'footer-localization-links'}, language.$('misc.uiLanguage', { -- cgit 1.3.0-6-gf8a5 From 61c8aa5c17ee6fd96f2e72f8d8a47eb0878ffa7e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Sun, 12 Nov 2023 15:25:54 -0400 Subject: upd8: load custom languages from yaml as well as json --- src/upd8.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/upd8.js b/src/upd8.js index db73c412..ff7d7c5c 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -1179,7 +1179,9 @@ async function main() { }); const languageDataFiles = await traverse(langPath, { - filterFile: name => path.extname(name) === '.json', + filterFile: name => + path.extname(name) === '.json' || + path.extname(name) === '.yaml', pathStyle: 'device', }); -- cgit 1.3.0-6-gf8a5 From 52cc83065f41472a4c32c2003b0a715a66d4739a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" <qznebula@protonmail.com> Date: Mon, 13 Nov 2023 16:36:36 -0400 Subject: content: generateWikiHomeAlbumsRow: handle no names in carousel --- src/content/dependencies/generateWikiHomeAlbumsRow.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/content/dependencies/generateWikiHomeAlbumsRow.js b/src/content/dependencies/generateWikiHomeAlbumsRow.js index 2c6a147e..a19f104c 100644 --- a/src/content/dependencies/generateWikiHomeAlbumsRow.js +++ b/src/content/dependencies/generateWikiHomeAlbumsRow.js @@ -108,14 +108,15 @@ export default { stitchArrays({ image: relations.images, path: data.paths, - name: data.names, + name: data.names ?? data.paths.slice().fill(null), }).map(({image, path, name}) => image.slots({ path, missingSourceContent: - language.$('misc.albumGrid.noCoverArt', { - album: name, - }), + name && + language.$('misc.albumGrid.noCoverArt', { + album: name, + }), })); commonSlots.actionLinks = -- cgit 1.3.0-6-gf8a5