diff --git a/pathologic.module b/pathologic.module
index 464410f005a439e9d774e16395ccc87e7220ca68..3231d7d59a57a3a499901e9133fc92b3ce0e027c 100644
--- a/pathologic.module
+++ b/pathologic.module
@@ -26,6 +26,7 @@
 declare(strict_types=1);
 
 use Drupal\Component\Utility\Html;
+use Drupal\Core\StreamWrapper\PublicStream;
 use Drupal\Core\Url;
 
 /**
@@ -128,13 +129,14 @@ function _pathologic_replace($matches) {
   // guess… Note that we don't do the & thing here so that we can modify
   // $cached_settings later and not have the changes be "permanent."
   $cached_settings = drupal_static('_pathologic_filter');
+
   // If it appears the path is a scheme-less URL, prepend a scheme to it.
   // parse_url() cannot properly parse scheme-less URLs. Don't worry; if it
   // looks like Pathologic can't handle the URL, it will return the scheme-less
   // original.
   // @see https://drupal.org/node/1617944
   // @see https://drupal.org/node/2030789
-  if (strpos($matches[2], '//') === 0) {
+  if (str_starts_with($matches[2], '//')) {
     if (\Drupal::request()->isSecure()) {
       $matches[2] = 'https:' . $matches[2];
     }
@@ -180,6 +182,12 @@ function _pathologic_replace($matches) {
     $parts['path'] = '';
   }
 
+  // Variable to define whether we need to rewrite/transform the URL through a
+  // URL object.
+  $dont_rewrite = FALSE;
+  // Variable to define whether the given path is a file path.
+  $is_file = FALSE;
+
   // Check to see if we're dealing with a file.
   // @todo Should we still try to do path correction on these files too?
   if (isset($parts['scheme']) && $parts['scheme'] === 'files') {
@@ -194,10 +202,14 @@ function _pathologic_replace($matches) {
     $new_parts['path'] = rawurldecode($new_parts['path']);
     $parts = $new_parts;
     // Don't do language handling for file paths.
-    $cached_settings['is_file'] = TRUE;
+    $is_file = TRUE;
   }
-  else {
-    $cached_settings['is_file'] = FALSE;
+  // Check to see if instead of a 'files:' scheme we have a normal internal
+  // file url starting with the public base path.
+  elseif (str_contains($parts['path'], PublicStream::basePath())) {
+    // This url should not be turned into a URL object, because we don't want
+    // language handling for this path.
+    $dont_rewrite = TRUE;
   }
 
   // Let's also bail out of this doesn't look like a local path.
@@ -291,12 +303,14 @@ function _pathologic_replace($matches) {
 
   // If we didn't previously identify this as a file, check to see if the file
   // exists now that we have the correct path relative to DRUPAL_ROOT
-  if (!$cached_settings['is_file']) {
-    $cached_settings['is_file'] = !empty($parts['path']) && is_file(DRUPAL_ROOT . '/' . $parts['path']);
+  if (!$is_file) {
+    $is_file = !empty($parts['path']) && is_file(DRUPAL_ROOT . '/' . $parts['path']);
   }
 
   // Okay, deal with language stuff.
-  // Let's see if we can split off a language prefix from the path.
+  // Let's see if path has a language prefix, so we can distinguish the target
+  // language for this path.
+  $specific_language = NULL;
   if (\Drupal::moduleHandler()->moduleExists('language')) {
     // This logic is based on
     // \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::getLangcode().
@@ -310,8 +324,8 @@ function _pathologic_replace($matches) {
     // Search for prefix within added languages.
     foreach ($languages as $language) {
       if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
-        $parts['path'] = implode('/', $path_args);
-        $parts['language_obj'] = $language;
+        // Do not strip off the language code from the path.
+        $specific_language = $language;
         break;
       }
     }
@@ -335,7 +349,7 @@ function _pathologic_replace($matches) {
       'absolute' => $cached_settings['current_settings']['protocol_style'] !== 'path',
       // If we seem to have found a language for the path, pass it along to
       // url(). Otherwise, ignore the 'language' parameter.
-      'language' => isset($parts['language_obj']) ? $parts['language_obj'] : NULL,
+      'language' => $specific_language ?? NULL,
       // A special parameter not actually used by url(), but we use it to see if
       // an alter hook implementation wants us to just pass through the original
       // URL.
@@ -360,8 +374,28 @@ function _pathologic_replace($matches) {
   if ($parts['path'] == '<front>') {
     $url = Url::fromRoute('<front>', [], $url_params['options'])->toString();
   }
+  elseif ($dont_rewrite) {
+    // The URL is just the internal path that doesn't need rewriting.
+    $url = $parts['path'];
+    if (!str_starts_with($url, '/')) {
+      $url = '/' . $url;
+    }
+  }
   else {
-    $path = (empty($url_params['options']['external']) ? 'base://' : '') . $url_params['path'];
+    // If we've been told this is already an external URL, leave it alone.
+    if (!empty($url_params['options']['external'])) {
+      $scheme = '';
+    }
+    // If it's a file, use 'base:' so that the path is not re-written.
+    elseif ($is_file) {
+      $scheme = 'base:/';
+    }
+    // For everything we did not recognize as external or files, we use the
+    // internal scheme so aliases and language prefixes are set correctly.
+    else {
+      $scheme = 'internal:/';
+    }
+    $path = $scheme . $url_params['path'];
     try {
       $url = Url::fromUri($path, $url_params['options'])->toString();
     }
diff --git a/tests/src/Functional/PathologicLanguageAliasTest.php b/tests/src/Functional/PathologicLanguageAliasTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6872909ac03900e0766cd7eb6e7ff6550ef50c75
--- /dev/null
+++ b/tests/src/Functional/PathologicLanguageAliasTest.php
@@ -0,0 +1,152 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\pathologic\Functional;
+
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
+use Drupal\Tests\pathologic\Traits\PathologicFormatTrait;
+use Drupal\Tests\Traits\Core\PathAliasTestTrait;
+use Drupal\Tests\user\Traits\UserCreationTrait;
+
+/**
+ * Test multilingual integration of Pathologic functionality.
+ *
+ * @group pathologic
+ */
+class PathologicLanguageAliasTest extends BrowserTestBase {
+
+  use ContentTypeCreationTrait;
+  use NodeCreationTrait;
+  use PathAliasTestTrait;
+  use PathologicFormatTrait;
+  use UserCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'content_translation',
+    'field',
+    'language',
+    'locale',
+    'node',
+    'path',
+    'path_alias',
+    'pathologic',
+    'text',
+    'user',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->setUpCurrentUser();
+
+    $this->createContentType([
+      'type' => 'page',
+      'name' => 'Basic page',
+    ]);
+
+    // Add more languages.
+    ConfigurableLanguage::createFromLangcode('fr')->save();
+    ConfigurableLanguage::createFromLangcode('pt-br')->save();
+
+    // Enable URL language detection and selection.
+    \Drupal::configFactory()->getEditable('language.negotiation')
+      ->set('url.prefixes.fr', 'fr')
+      ->set('url.prefixes.pt-br', 'pt-br')
+      ->save();
+
+    // Configure Pathologic on a text format.
+    $this->buildFormat([
+      'settings_source' => 'local',
+      'local_settings' => [
+        'protocol_style' => 'path',
+      ],
+    ]);
+
+    // To reflect the changes for a multilingual site, rebuild the container.
+    $this->container->get('kernel')->rebuildContainer();
+  }
+
+  /**
+   * Tests how links to nodes and files are handled with translations.
+   */
+  public function testContentTranslation(): void {
+
+    // Create a node that will be referenced in a link inside another node.
+    $node_to_reference = $this->createNode([
+      'type' => 'page',
+      'title' => 'Reference page',
+    ]);
+
+    // Add translations and path aliases for the reference node, and try a whole
+    // series of possible input texts to see how they are handled.
+    $fr_reference = $node_to_reference->addTranslation('fr', [
+      'title' => 'Page de référence en français',
+    ])->save();
+    $pt_br_reference = $node_to_reference->addTranslation('pt-br', [
+      'title' => 'Página de referência em Português',
+    ])->save();
+
+    $this->createPathAlias('/node/' . $node_to_reference->id(), '/reference-en', 'en');
+    $this->createPathAlias('/node/' . $node_to_reference->id(), '/reference-fr', 'fr');
+    $this->createPathAlias('/node/' . $node_to_reference->id(), '/referencia-pt', 'pt-br');
+
+    global $base_path;
+    $nid = $node_to_reference->id();
+
+    // The link replacement shouldn't change for any of these based on the language the filter runs with.
+    foreach (['en', 'fr', 'pt-br'] as $langcode) {
+      $this->assertSame(
+        '<a href="' . $base_path . 'sites/default/files/test.png">Test file link</a>',
+        $this->runFilter('<a href="/sites/default/files/test.png">Test file link</a>', $langcode),
+        "$langcode: file links do not get a language prefix",
+      );
+      $this->assertSame(
+        '<a href="' . $base_path . 'reference-en">Test node link</a>',
+        $this->runFilter('<a href="/node/' . $nid . '">Test node link</a>', $langcode),
+        "$langcode: node/N link uses EN alias",
+      );
+      $this->assertSame(
+        '<a href="' . $base_path . 'fr/reference-fr">Test node link</a>',
+        $this->runFilter('<a href="/fr/node/' . $nid . '">Test node link</a>', $langcode),
+        "$langcode: fr/node/N link uses the FR alias",
+      );
+      $this->assertSame(
+        '<a href="' . $base_path . 'pt-br/referencia-pt">Test node link</a>',
+        $this->runFilter('<a href="/pt-br/node/' . $nid . '">Test node link</a>', $langcode),
+        "$langcode: pt-br/node/N link uses the PT-BR alias",
+      );
+      $this->assertSame(
+        '<a href="' . $base_path . 'reference-en">Test node link</a>',
+        $this->runFilter('<a href="/reference-en">Test node link</a>', $langcode),
+        "$langcode: /reference-en link uses EN alias",
+      );
+      $this->assertSame(
+        '<a href="' . $base_path . 'fr/reference-fr">Test node link</a>',
+        $this->runFilter('<a href="/fr/reference-fr">Test node link</a>', $langcode),
+        "$langcode: fr/reference-fr link uses the FR alias",
+      );
+      $this->assertSame(
+        '<a href="' . $base_path . 'pt-br/referencia-pt">Test node link</a>',
+        $this->runFilter('<a href="/pt-br/referencia-pt">Test node link</a>', $langcode),
+        "$langcode: pt-br/referencia-pt uses the PT-BR alias",
+      );
+    }
+
+  }
+
+}
diff --git a/tests/src/Kernel/PathologicKernelTestBase.php b/tests/src/Kernel/PathologicKernelTestBase.php
index e5eb77c8c60539496b9316f6ddf4dc432e9d551e..0b11e8a8472299ec7fc3bce922ef2f7fff22c993 100644
--- a/tests/src/Kernel/PathologicKernelTestBase.php
+++ b/tests/src/Kernel/PathologicKernelTestBase.php
@@ -6,14 +6,16 @@ namespace Drupal\Tests\pathologic\Kernel;
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Url;
-use Drupal\filter\Entity\FilterFormat;
 use Drupal\KernelTests\KernelTestBase;
+use Drupal\Tests\pathologic\Traits\PathologicFormatTrait;
 
 /**
  * Base class for all Pathologic Kernel tests.
  */
 abstract class PathologicKernelTestBase extends KernelTestBase {
 
+  use PathologicFormatTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -28,13 +30,6 @@ abstract class PathologicKernelTestBase extends KernelTestBase {
    */
   protected $defaultTheme = 'stark';
 
-  /**
-   * The ID of a text format to be used in a test.
-   *
-   * @var formatId
-   */
-  protected $formatId = '';
-
   /**
    * {@inheritdoc}
    */
@@ -44,42 +39,6 @@ abstract class PathologicKernelTestBase extends KernelTestBase {
     $this->installConfig(['system', 'filter', 'pathologic']);
   }
 
-  /**
-   * Build a text format with Pathologic configured a certain way.
-   *
-   * @param array $settings
-   *   An array of settings for the Pathologic instance on the format.
-   *
-   * @return string
-   *   The randomly generated format machine name for the new format.
-   */
-  protected function buildFormat(array $settings) {
-    $this->formatId = ($settings['local_settings']['protocol_style'] ?? 'unknown') . '_' . $this->randomMachineName(8);
-    $format = FilterFormat::create([
-      'format' => $this->formatId,
-      'name' => $this->formatId,
-    ]);
-    $format->setFilterConfig('filter_pathologic', [
-      'status' => 1,
-      'settings' => $settings,
-    ]);
-    $format->save();
-    return $this->formatId;
-  }
-
-  /**
-   * Runs the given string through the Pathologic text filter.
-   *
-   * @param string $markup
-   *   Raw markup to be processed.
-   *
-   * @return string
-   *   A string of text-format-filtered markup.
-   */
-  protected function runFilter(string $markup): string {
-    return check_markup($markup, $this->formatId)->__toString();
-  }
-
   /**
    * Wrapper around url() which does HTML entity decoding and encoding.
    *
diff --git a/tests/src/Traits/PathologicFormatTrait.php b/tests/src/Traits/PathologicFormatTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..7099865d0150e9614dda1177ed115605344e41ce
--- /dev/null
+++ b/tests/src/Traits/PathologicFormatTrait.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\pathologic\Traits;
+
+use Drupal\filter\Entity\FilterFormat;
+
+/**
+ * Provides management of a text format configured for Pathologic.
+ *
+ * This trait is meant to be used only by test classes.
+ */
+trait PathologicFormatTrait {
+
+  /**
+   * The ID of a text format to be used in a test.
+   *
+   * @var formatId
+   */
+  protected $formatId = '';
+
+  /**
+   * Build a text format with Pathologic configured a certain way.
+   *
+   * @param array $settings
+   *   An array of settings for the Pathologic instance on the format.
+   *
+   * @return string
+   *   The randomly generated format machine name for the new format.
+   */
+  protected function buildFormat(array $settings) {
+    $this->formatId = ($settings['local_settings']['protocol_style'] ?? 'unknown') . '_' . $this->randomMachineName(8);
+    $format = FilterFormat::create([
+      'format' => $this->formatId,
+      'name' => $this->formatId,
+    ]);
+    $format->setFilterConfig('filter_pathologic', [
+      'status' => 1,
+      'settings' => $settings,
+    ]);
+    $format->save();
+    return $this->formatId;
+  }
+
+  /**
+   * Runs the given string through the Pathologic text filter.
+   *
+   * @param string $markup
+   *   Raw markup to be processed.
+   * @param string $langcode
+   *   The optional language to render the text with.
+   *
+   * @return string
+   *   A string of text-format-filtered markup.
+   */
+  protected function runFilter(string $markup, string $langcode = ''): string {
+    return check_markup($markup, $this->formatId, $langcode)->__toString();
+  }
+
+}