« get me outta code hell

content, external-links: pass url entry through, handle annotation - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2026-05-01 08:53:23 -0300
committer(quasar) nebula <qznebula@protonmail.com>2026-05-01 08:53:57 -0300
commitf6aad9a81fbb1b4e619355cbec316988837fb61a (patch)
tree6281b063b7741bd4fb46b087bfabedc8a0b97c8d /src
parent92d82211fdf3d653b1a5a4d2cfdad5c684e2367d (diff)
content, external-links: pass url entry through, handle annotation
Diffstat (limited to 'src')
-rw-r--r--src/content/dependencies/generateAlbumCommentaryPage.js4
-rw-r--r--src/content/dependencies/generateAlbumSidebarGroupBox.js2
-rw-r--r--src/content/dependencies/generateArtTagGalleryPage.js2
-rw-r--r--src/content/dependencies/generateArtTagInfoPage.js2
-rw-r--r--src/content/dependencies/generateArtistInfoPage.js2
-rw-r--r--src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js2
-rw-r--r--src/content/dependencies/generateContributionTooltipExternalLinkSection.js6
-rw-r--r--src/content/dependencies/generateExternalHandle.js4
-rw-r--r--src/content/dependencies/generateExternalIcon.js4
-rw-r--r--src/content/dependencies/generateExternalPlatform.js4
-rw-r--r--src/content/dependencies/generateFlashInfoPage.js2
-rw-r--r--src/content/dependencies/generateGroupInfoPage.js2
-rw-r--r--src/content/dependencies/generateLyricsEntry.js2
-rw-r--r--src/content/dependencies/generateMusicVideo.js2
-rw-r--r--src/content/dependencies/generateReleaseInfoListenLine.js2
-rw-r--r--src/content/dependencies/linkExternal.js72
-rw-r--r--src/content/dependencies/transformContent.js4
-rw-r--r--src/external-links.js83
-rw-r--r--src/strings-default.yaml24
19 files changed, 139 insertions, 86 deletions
diff --git a/src/content/dependencies/generateAlbumCommentaryPage.js b/src/content/dependencies/generateAlbumCommentaryPage.js
index a380f468..d2706918 100644
--- a/src/content/dependencies/generateAlbumCommentaryPage.js
+++ b/src/content/dependencies/generateAlbumCommentaryPage.js
@@ -52,7 +52,7 @@ export default {
 
       relations.albumCommentaryListeningLinks =
         album.urls
-          .map(entry => relation('linkExternal', entry.url));
+          .map(entry => relation('linkExternal', entry));
 
       if (album.hasCoverArt) {
         relations.albumCommentaryCover =
@@ -75,7 +75,7 @@ export default {
     relations.trackCommentaryListeningLinks =
       query.tracksWithCommentary
         .map(track =>
-          track.urls.map(entry => relation('linkExternal', entry.url)));
+          track.urls.map(entry => relation('linkExternal', entry)));
 
     relations.trackCommentaryCovers =
       query.tracksWithCommentary
diff --git a/src/content/dependencies/generateAlbumSidebarGroupBox.js b/src/content/dependencies/generateAlbumSidebarGroupBox.js
index 15fedf75..9aa29cf6 100644
--- a/src/content/dependencies/generateAlbumSidebarGroupBox.js
+++ b/src/content/dependencies/generateAlbumSidebarGroupBox.js
@@ -39,7 +39,7 @@ export default {
 
     relations.externalLinks =
       group.urls
-        .map(entry => relation('linkExternal', entry.url));
+        .map(entry => relation('linkExternal', entry));
 
     if (group.descriptionShort) {
       relations.description =
diff --git a/src/content/dependencies/generateArtTagGalleryPage.js b/src/content/dependencies/generateArtTagGalleryPage.js
index ec0624d1..49f58a9d 100644
--- a/src/content/dependencies/generateArtTagGalleryPage.js
+++ b/src/content/dependencies/generateArtTagGalleryPage.js
@@ -42,7 +42,7 @@ export default {
     if (!empty(artTag.extraReadingURLs)) {
       relations.extraReadingLinks =
         artTag.extraReadingURLs
-          .map(entry => relation('linkExternal', entry.url));
+          .map(entry => relation('linkExternal', entry));
     }
 
     if (!empty(artTag.directAncestorArtTags)) {
diff --git a/src/content/dependencies/generateArtTagInfoPage.js b/src/content/dependencies/generateArtTagInfoPage.js
index db26260b..dc7ee364 100644
--- a/src/content/dependencies/generateArtTagInfoPage.js
+++ b/src/content/dependencies/generateArtTagInfoPage.js
@@ -50,7 +50,7 @@ export default {
 
     extraReadingLinks:
       artTag.extraReadingURLs
-        .map(entry => relation('linkExternal', entry.url)),
+        .map(entry => relation('linkExternal', entry)),
 
     relatedArtTagLinks:
       artTag.relatedArtTags
diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js
index 3961dc49..45b111ed 100644
--- a/src/content/dependencies/generateArtistInfoPage.js
+++ b/src/content/dependencies/generateArtistInfoPage.js
@@ -62,7 +62,7 @@ export default {
 
     visitLinks:
       artist.urls
-        .map(entry => relation('linkExternal', entry.url)),
+        .map(entry => relation('linkExternal', entry)),
 
     tracksChunkedList:
       relation('generateArtistInfoPageTracksChunkedList', artist),
diff --git a/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js b/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js
index 570a984a..4351d590 100644
--- a/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js
+++ b/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js
@@ -38,7 +38,7 @@ export default {
 
     externalLinks:
       query.musicVideo.urls
-        .map(entry => relation('linkExternal', entry.url)),
+        .map(entry => relation('linkExternal', entry)),
   }),
 
   data: (query, _artist, contribs) => ({
diff --git a/src/content/dependencies/generateContributionTooltipExternalLinkSection.js b/src/content/dependencies/generateContributionTooltipExternalLinkSection.js
index cc61fab4..f6af70d2 100644
--- a/src/content/dependencies/generateContributionTooltipExternalLinkSection.js
+++ b/src/content/dependencies/generateContributionTooltipExternalLinkSection.js
@@ -4,15 +4,15 @@ export default {
   relations: (relation, contribution) => ({
     icons:
       contribution.artist.urls
-        .map(entry => relation('generateExternalIcon', entry.url)),
+        .map(entry => relation('generateExternalIcon', entry)),
 
     handles:
       contribution.artist.urls
-        .map(entry => relation('generateExternalHandle', entry.url)),
+        .map(entry => relation('generateExternalHandle', entry)),
 
     platforms:
       contribution.artist.urls
-        .map(entry => relation('generateExternalPlatform', entry.url)),
+        .map(entry => relation('generateExternalPlatform', entry)),
   }),
 
   data: (contribution) => ({
diff --git a/src/content/dependencies/generateExternalHandle.js b/src/content/dependencies/generateExternalHandle.js
index 8653b177..358b4305 100644
--- a/src/content/dependencies/generateExternalHandle.js
+++ b/src/content/dependencies/generateExternalHandle.js
@@ -1,7 +1,7 @@
 import {isExternalLinkContext} from '#external-links';
 
 export default {
-  data: (url) => ({url}),
+  data: (urlEntry) => ({urlEntry}),
 
   slots: {
     context: {
@@ -11,7 +11,7 @@ export default {
   },
 
   generate: (data, slots, {language}) =>
-    language.formatExternalLink(data.url, {
+    language.formatExternalLink(data.urlEntry, {
       style: 'handle',
       context: slots.context,
     }),
diff --git a/src/content/dependencies/generateExternalIcon.js b/src/content/dependencies/generateExternalIcon.js
index 03af643e..0f3ee1b7 100644
--- a/src/content/dependencies/generateExternalIcon.js
+++ b/src/content/dependencies/generateExternalIcon.js
@@ -1,7 +1,7 @@
 import {isExternalLinkContext} from '#external-links';
 
 export default {
-  data: (url) => ({url}),
+  data: (urlEntry) => ({urlEntry}),
 
   slots: {
     context: {
@@ -16,7 +16,7 @@ export default {
         html.tag('use', {
           href:
             to('staticMisc.icon',
-              language.formatExternalLink(data.url, {
+              language.formatExternalLink(data.urlEntry, {
                 style: 'icon-id',
                 context: slots.context,
               })),
diff --git a/src/content/dependencies/generateExternalPlatform.js b/src/content/dependencies/generateExternalPlatform.js
index b2822d64..98c09815 100644
--- a/src/content/dependencies/generateExternalPlatform.js
+++ b/src/content/dependencies/generateExternalPlatform.js
@@ -1,7 +1,7 @@
 import {isExternalLinkContext} from '#external-links';
 
 export default {
-  data: (url) => ({url}),
+  data: (urlEntry) => ({urlEntry}),
 
   slots: {
     context: {
@@ -11,7 +11,7 @@ export default {
   },
 
   generate: (data, slots, {language}) =>
-    language.formatExternalLink(data.url, {
+    language.formatExternalLink(data.urlEntry, {
       style: 'platform',
       context: slots.context,
     }),
diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js
index b334412b..39590832 100644
--- a/src/content/dependencies/generateFlashInfoPage.js
+++ b/src/content/dependencies/generateFlashInfoPage.js
@@ -42,7 +42,7 @@ export default {
 
     externalLinks:
       query.urls
-        .map(entry => relation('linkExternal', entry.url)),
+        .map(entry => relation('linkExternal', entry)),
 
     artworkColumn:
       relation('generateFlashArtworkColumn', flash),
diff --git a/src/content/dependencies/generateGroupInfoPage.js b/src/content/dependencies/generateGroupInfoPage.js
index 261d2212..87d5d2f8 100644
--- a/src/content/dependencies/generateGroupInfoPage.js
+++ b/src/content/dependencies/generateGroupInfoPage.js
@@ -51,7 +51,7 @@ export default {
 
     visitLinks:
       group.urls
-        .map(entry => relation('linkExternal', entry.url)),
+        .map(entry => relation('linkExternal', entry)),
 
     description:
       relation('transformContent', group.description),
diff --git a/src/content/dependencies/generateLyricsEntry.js b/src/content/dependencies/generateLyricsEntry.js
index 0ecf319f..b1113e57 100644
--- a/src/content/dependencies/generateLyricsEntry.js
+++ b/src/content/dependencies/generateLyricsEntry.js
@@ -13,7 +13,7 @@ export default {
 
     sourceLinks:
       entry.sourceURLs
-        .map(url => relation('linkExternal', url)),
+        .map(url => relation('linkExternal', {url})),
 
     originDetails:
       relation('transformContent', entry.originDetails),
diff --git a/src/content/dependencies/generateMusicVideo.js b/src/content/dependencies/generateMusicVideo.js
index 15eb233b..8508fdb6 100644
--- a/src/content/dependencies/generateMusicVideo.js
+++ b/src/content/dependencies/generateMusicVideo.js
@@ -21,7 +21,7 @@ export default {
 
     watchLinks:
       musicVideo.urls
-        .map(entry => relation('linkExternal', entry.url)),
+        .map(entry => relation('linkExternal', entry)),
   }),
 
   data: (musicVideo, _thing) => ({
diff --git a/src/content/dependencies/generateReleaseInfoListenLine.js b/src/content/dependencies/generateReleaseInfoListenLine.js
index cd3baaf2..54e7985f 100644
--- a/src/content/dependencies/generateReleaseInfoListenLine.js
+++ b/src/content/dependencies/generateReleaseInfoListenLine.js
@@ -65,7 +65,7 @@ export default {
   relations: (relation, query, _thing) => ({
     links:
       query.urls
-        .map(entry => relation('linkExternal', entry.url)),
+        .map(entry => relation('linkExternal', entry)),
   }),
 
   data(query, thing) {
diff --git a/src/content/dependencies/linkExternal.js b/src/content/dependencies/linkExternal.js
index e1393455..b47b1a90 100644
--- a/src/content/dependencies/linkExternal.js
+++ b/src/content/dependencies/linkExternal.js
@@ -9,8 +9,12 @@ export default {
       wikiInfo.canonicalMediaBase,
   }),
 
-  data: (sprawl, url) => ({
-    url,
+  data: (sprawl, urlEntry) => ({
+    url:
+      urlEntry.url,
+
+    annotation:
+      urlEntry.annotation,
 
     canonicalBase:
       sprawl.canonicalBase,
@@ -70,11 +74,9 @@ export default {
   },
 
   generate(data, slots, {html, language, to}) {
-    const {url} = data;
-
     let urlIsValid;
     try {
-      new URL(url);
+      new URL(data.url);
       urlIsValid = true;
     } catch {
       urlIsValid = false;
@@ -83,31 +85,39 @@ export default {
     let href;
     if (urlIsValid) {
       const {canonicalBase, canonicalMediaBase} = data;
-      const past = front => decodeURIComponent(url.slice(front.length));
-      if (canonicalMediaBase && url.startsWith(canonicalMediaBase)) {
+      const past = front => decodeURIComponent(data.url.slice(front.length));
+      if (canonicalMediaBase && data.url.startsWith(canonicalMediaBase)) {
         href = to('media.path', past(canonicalMediaBase));
-      } else if (canonicalBase && url.startsWith(canonicalBase)) {
+      } else if (canonicalBase && data.url.startsWith(canonicalBase)) {
         href = to('shared.path', past(canonicalBase));
       } else {
-        href = url;
+        href = data.url;
       }
     }
 
+    const urlEntry = {
+      url: href,
+      annotation: data.annotation,
+    };
+
     let formattedLink;
+    let formattedPlatform;
     if (urlIsValid) {
       formattedLink =
-        language.formatExternalLink(url, {
+        language.formatExternalLink(urlEntry, {
           style: slots.style,
           context: slots.context,
         });
 
+      formattedPlatform =
+        language.formatExternalLink(urlEntry, {
+          style: 'platform',
+          context: slots.context,
+        });
+
       // Fall back to platform if nothing matched the desired style.
-      if (html.isBlank(formattedLink) && slots.style !== 'platform') {
-        formattedLink =
-          language.formatExternalLink(url, {
-            style: 'platform',
-            context: slots.context,
-          });
+      if (html.isBlank(formattedLink)) {
+        formattedLink = formattedPlatform;
       }
     } else {
       formattedLink = null;
@@ -119,6 +129,10 @@ export default {
 
     let linkContent;
     if (urlIsValid) {
+      // For valid URLs, the annotation (if any) is already handled
+      // by formatExternalLink. It is not shown at all, in up-front
+      // presentation, if there is custom content.
+
       linkAttributes.set('href', href);
 
       if (html.isBlank(slots.content)) {
@@ -127,17 +141,25 @@ export default {
         linkContent = slots.content;
       }
     } else {
+      // For invalid URLs, there is no automatically formatted link,
+      // and the annotation (if any) is rolled into presentation below.
+      // However, it's still not shown if there is custom content.
+
       if (html.isBlank(slots.content)) {
-        linkContent =
-          html.tag('i',
-            language.$('misc.external.invalidURL.annotation'));
+        if (data.annotation) {
+          linkContent =
+            language.$('misc.external.withAnnotation', {
+              link: html.tag('i', language.$('misc.external.invalidURL.annotation')),
+              annotation: language.sanitize(data.annotation),
+            });
+        } else {
+          linkContent = html.tag('i', language.$('misc.external.invalidURL'));
+        }
       } else {
         linkContent =
-          language.$('misc.external.invalidURL', {
+          language.$('misc.external.withAnnotation', {
             link: slots.content,
-            annotation:
-              html.tag('i',
-                language.$('misc.external.invalidURL.annotation')),
+            annotation: html.tag('i', language.$('misc.external.invalidURL.annotation')),
           });
       }
     }
@@ -159,9 +181,7 @@ export default {
         } else {
           titleText =
             language.$('misc.external.opensInNewTab', {
-              link: formattedLink,
-              annotation:
-                language.$('misc.external.opensInNewTab.annotation'),
+              platform: formattedPlatform,
             });
         }
       } else if (!html.isBlank(slots.content)) {
diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js
index 775ccfdc..62a547e4 100644
--- a/src/content/dependencies/transformContent.js
+++ b/src/content/dependencies/transformContent.js
@@ -282,14 +282,14 @@ export default {
         nodes
           .filter(({type}) => type === 'external-link')
           .map(({data: {href}}) =>
-            relation('linkExternal', href)),
+            relation('linkExternal', {url: href})),
 
       externalLinksForTooltipNodes:
         nodes
           .filter(({type}) => type === 'tooltip')
           .filter(({data}) => data.link)
           .map(({data: {link: href}}) =>
-            relation('linkExternal', href)),
+            relation('linkExternal', {url: href})),
 
       images:
         nodes
diff --git a/src/external-links.js b/src/external-links.js
index fa4a832e..847970b8 100644
--- a/src/external-links.js
+++ b/src/external-links.js
@@ -733,10 +733,10 @@ function createEmptyResults() {
   return Object.fromEntries(externalLinkStyles.map(style => [style, null]));
 }
 
-export function getMatchingDescriptorsForExternalLink(url, descriptors, {
+export function getMatchingDescriptorsForExternalLink(urlEntry, descriptors, {
   context = 'generic',
 } = {}) {
-  const {domain, pathname, query} = urlParts(url);
+  const {domain, pathname, query} = urlParts(urlEntry.url);
 
   const compareDomain = string => {
     // A dot at the start of the descriptor's domain indicates
@@ -760,7 +760,7 @@ export function getMatchingDescriptorsForExternalLink(url, descriptors, {
   const compareQuery = regex => regex.test(query.slice(1));
 
   const compareExtractSpec = extract =>
-    extractPartFromExternalLink(url, extract, {mode: 'test'});
+    extractPartFromExternalLink(urlEntry, extract, {mode: 'test'});
 
   const contextArray =
     (Array.isArray(context)
@@ -813,12 +813,12 @@ export function getMatchingDescriptorsForExternalLink(url, descriptors, {
   return [...matchingDescriptors, fallbackDescriptor];
 }
 
-export function extractPartFromExternalLink(url, extract, {
+export function extractPartFromExternalLink(urlEntry, extract, {
   // Set to 'test' to just see if this would extract anything.
   // This disables running custom transformations.
   mode = 'extract',
 } = {}) {
-  const {domain, pathname, query} = urlParts(url);
+  const {domain, pathname, query} = urlParts(urlEntry.url);
 
   let regexen = [];
   let tests = [];
@@ -827,7 +827,7 @@ export function extractPartFromExternalLink(url, extract, {
 
   if (extract instanceof RegExp) {
     regexen.push(extract);
-    tests.push(url);
+    tests.push(urlEntry.url);
   } else {
     for (const [key, value] of Object.entries(extract)) {
       switch (key) {
@@ -862,7 +862,7 @@ export function extractPartFromExternalLink(url, extract, {
           continue;
 
         case 'url':
-          tests.push(url);
+          tests.push(urlEntry.url);
           break;
 
         case 'domain':
@@ -917,19 +917,19 @@ export function extractPartFromExternalLink(url, extract, {
   return value;
 }
 
-export function extractAllCustomPartsFromExternalLink(url, custom) {
+export function extractAllCustomPartsFromExternalLink(urlEntry, custom) {
   const customParts = {};
 
   // All or nothing: if one part doesn't match, all results are scrapped.
   for (const [key, value] of Object.entries(custom)) {
-    customParts[key] = extractPartFromExternalLink(url, value);
+    customParts[key] = extractPartFromExternalLink(urlEntry, value);
     if (!customParts[key]) return null;
   }
 
   return customParts;
 }
 
-export function getExternalLinkStringOfStyleFromDescriptor(url, style, descriptor, {language}) {
+export function getExternalLinkStringOfStyleFromDescriptor(urlEntry, style, descriptor, {language}) {
   const prefix = 'misc.external';
 
   function getDetail() {
@@ -939,46 +939,75 @@ export function getExternalLinkStringOfStyleFromDescriptor(url, style, descripto
 
     if (typeof descriptor.detail === 'string') {
       return language.$(prefix, descriptor.platform, descriptor.detail);
-    } else {
+    } else if (descriptor.detial.substring) {
       const {substring, ...rest} = descriptor.detail;
 
       const opts =
         withEntries(rest, entries => entries
           .map(([key, value]) => [
             key,
-            extractPartFromExternalLink(url, value),
+            extractPartFromExternalLink(urlEntry, value),
           ]));
 
       return language.$(prefix, descriptor.platform, substring, opts);
+    } else if (descriptor.detail.annotation) {
+      const annotation =
+        extractPartFromExternalLink(urlEntry, descriptor.detail.annotation);
+
+      if (urlEntry.annotation) {
+        return language.$(prefix, descriptor.platform, 'withAutomaticAndCustomAnnotations', {
+          automatic: annotation,
+          custom: urlEntry.annotation,
+        });
+      } else {
+        return language.$(prefix, descriptor.platform, 'withAnnotation', {
+          annotation,
+        });
+      }
     }
   }
 
   switch (style) {
     case 'platform': {
-      const platform = language.$(prefix, descriptor.platform);
-      const domain = urlParts(url).domain;
+      const platformName = language.$(prefix, descriptor.platform);
+      const domain = urlParts(urlEntry.url).domain;
 
+      let platformPart;
       if (descriptor === fallbackDescriptor) {
         // The fallback descriptor has a "platform" which is just
         // the word "External". This isn't really useful when you're
         // looking for platform info!
         if (domain) {
-          return language.sanitize(domain.replace(/^www\./, ''));
+          platformPart = language.sanitize(domain.replace(/^www\./, ''));
         } else {
-          return platform;
+          platformPart = platformName;
         }
       } else if (descriptor.detail) {
+        // getDetail handles the whole string, including annotation, so just
+        // return its result as-is.
         return getDetail();
       } else if (descriptor.unusualDomain && domain) {
-        return language.$(prefix, 'withDomain', {platform, domain});
+        platformPart = language.$(prefix, 'withUnusualDomain', {
+          platform: platformName,
+          domain,
+        });
+      } else {
+        platformPart = platformName;
+      }
+
+      if (urlEntry.annotation) {
+        return language.$(prefix, 'withAnnotation', {
+          link: platformPart,
+          annotation: language.sanitize(urlEntry.annotation),
+        });
       } else {
-        return platform;
+        return platformPart;
       }
     }
 
     case 'handle': {
       if (descriptor.handle) {
-        return extractPartFromExternalLink(url, descriptor.handle);
+        return extractPartFromExternalLink(urlEntry, descriptor.handle);
       } else {
         return null;
       }
@@ -1008,12 +1037,12 @@ export function couldDescriptorSupportStyle(descriptor, style) {
   }
 }
 
-export function getExternalLinkStringOfStyleFromDescriptors(url, style, descriptors, {
+export function getExternalLinkStringOfStyleFromDescriptors(urlEntry, style, descriptors, {
   language,
   context = 'generic',
 }) {
   const matchingDescriptors =
-    getMatchingDescriptorsForExternalLink(url, descriptors, {context});
+    getMatchingDescriptorsForExternalLink(urlEntry, descriptors, {context});
 
   const styleFilteredDescriptors =
     matchingDescriptors.filter(descriptor =>
@@ -1021,7 +1050,7 @@ export function getExternalLinkStringOfStyleFromDescriptors(url, style, descript
 
   for (const descriptor of styleFilteredDescriptors) {
     const descriptorResult =
-      getExternalLinkStringOfStyleFromDescriptor(url, style, descriptor, {language});
+      getExternalLinkStringOfStyleFromDescriptor(urlEntry, style, descriptor, {language});
 
     if (descriptorResult) {
       return descriptorResult;
@@ -1031,17 +1060,17 @@ export function getExternalLinkStringOfStyleFromDescriptors(url, style, descript
   return null;
 }
 
-export function getExternalLinkStringsFromDescriptor(url, descriptor, {language}) {
+export function getExternalLinkStringsFromDescriptor(urlEntry, descriptor, {language}) {
   return (
     Object.fromEntries(
       externalLinkStyles.map(style =>
         getExternalLinkStringOfStyleFromDescriptor(
-          url,
+          urlEntry,
           style,
           descriptor, {language}))));
 }
 
-export function getExternalLinkStringsFromDescriptors(url, descriptors, {
+export function getExternalLinkStringsFromDescriptors(urlEntry, descriptors, {
   language,
   context = 'generic',
 }) {
@@ -1049,11 +1078,11 @@ export function getExternalLinkStringsFromDescriptors(url, descriptors, {
   const remainingKeys = new Set(Object.keys(results));
 
   const matchingDescriptors =
-    getMatchingDescriptorsForExternalLink(url, descriptors, {context});
+    getMatchingDescriptorsForExternalLink(urlEntry, descriptors, {context});
 
   for (const descriptor of matchingDescriptors) {
     const descriptorResults =
-      getExternalLinkStringsFromDescriptor(url, descriptor, {language});
+      getExternalLinkStringsFromDescriptor(urlEntry, descriptor, {language});
 
     const descriptorKeys =
       new Set(
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index 0745160a..458e85e7 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -676,19 +676,23 @@ misc:
   external:
     external: "External"
 
-    withDomain:
-      "{PLATFORM} ({DOMAIN})"
+    withUnusualDomain: >-
+      {PLATFORM} ({DOMAIN})
 
-    withHandle:
-      "{PLATFORM} ({HANDLE})"
+    withHandle: >-
+      {PLATFORM} ({HANDLE})
 
-    opensInNewTab:
-      _: "{LINK} ({ANNOTATION})"
-      annotation: "opens in new tab"
+    withAnnotation: >-
+      {LINK} ({ANNOTATION})
 
-    invalidURL:
-      _: "{LINK} ({ANNOTATION})"
-      annotation: "invalid URL"
+    withAutomaticAndCustomAnnotations: >-
+      {LINK} ({AUTOMATIC}, {CUSTOM})
+
+    opensInNewTab: "{PLATFORM} (opens in new tab)"
+    opensInNewTab.annotation: "opens in new tab"
+
+    invalidURL: "invalid URL"
+    invalidURL.annotation: "invalid URL"
 
     amazonMusic: "Amazon Music"
     appleMusic: "Apple Music"