diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php
index b4c985c413..c5d9c22de7 100644
--- a/resources/lang/en/messages.php
+++ b/resources/lang/en/messages.php
@@ -219,6 +219,7 @@
     'taxonomy_configure_term_template_instructions' => 'Set this taxonomy\'s default template. Terms can override this setting with a `template` field.',
     'taxonomies_preview_targets_instructions' => 'The URLs to be viewable within Live Preview. Learn more in the [documentation](https://statamic.dev/live-preview#preview-targets).',
     'taxonomies_preview_target_refresh_instructions' => 'Automatically refresh the preview while editing. Disabling this will use postMessage.',
+    'taxonomies_route_instructions' => 'The route controls show and index URL patterns. Learn more in the [documentation](https://statamic.dev/taxonomies#routing).',
     'taxonomy_configure_handle_instructions' => 'Used to reference this taxonomy on the frontend. It\'s non-trivial to change later.',
     'taxonomy_configure_intro' => 'A taxonomy is a system of classifying data around a set of unique characteristics, such as categories, tags, or colors.',
     'taxonomy_configure_title_instructions' => 'We recommend using a plural noun, like "Categories" or "Tags".',
diff --git a/src/Http/Controllers/CP/Taxonomies/TaxonomiesController.php b/src/Http/Controllers/CP/Taxonomies/TaxonomiesController.php
index 15b352d4a2..2a53a7297a 100644
--- a/src/Http/Controllers/CP/Taxonomies/TaxonomiesController.php
+++ b/src/Http/Controllers/CP/Taxonomies/TaxonomiesController.php
@@ -136,6 +136,9 @@ public function edit($taxonomy)
             'collections' => $taxonomy->collections()->map->handle()->all(),
             'sites' => $taxonomy->sites()->all(),
             'preview_targets' => $taxonomy->basePreviewTargets(),
+            'routes' => $taxonomy->routes()->unique()->count() === 1
+                ? $taxonomy->routes()->first()
+                : $taxonomy->routes()->all(),
             'term_template' => $taxonomy->hasCustomTermTemplate() ? $taxonomy->termTemplate() : null,
             'template' => $taxonomy->hasCustomTemplate() ? $taxonomy->template() : null,
             'layout' => $taxonomy->layout(),
@@ -169,6 +172,7 @@ public function update(Request $request, $taxonomy)
         $taxonomy
             ->title($values['title'])
             ->previewTargets($values['preview_targets'])
+            ->routes($values['routes'])
             ->termTemplate($values['term_template'] ?? null)
             ->template($values['template'] ?? null)
             ->layout($values['layout'] ?? null);
@@ -285,6 +289,11 @@ protected function editFormBlueprint($taxonomy)
             'routing' => [
                 'display' => __('Routing & URLs'),
                 'fields' => [
+                    'routes' => [
+                        'display' => __('Route'),
+                        'instructions' => __('statamic::messages.taxonomies_route_instructions'),
+                        'type' => 'collection_routes',
+                    ],
                     'preview_targets' => [
                         'display' => __('Preview Targets'),
                         'instructions' => __('statamic::messages.taxonomies_preview_targets_instructions'),
diff --git a/src/Stache/Repositories/TaxonomyRepository.php b/src/Stache/Repositories/TaxonomyRepository.php
index 50bf566696..a5fde0c417 100644
--- a/src/Stache/Repositories/TaxonomyRepository.php
+++ b/src/Stache/Repositories/TaxonomyRepository.php
@@ -12,11 +12,13 @@
 
 class TaxonomyRepository implements RepositoryContract
 {
+    protected $stache;
     protected $store;
     protected $additionalPreviewTargets = [];
 
     public function __construct(Stache $stache)
     {
+        $this->stache = $stache;
         $this->store = $stache->store('taxonomies');
     }
 
@@ -90,7 +92,7 @@ public function findByUri(string $uri, ?string $site = null): ?Taxonomy
         // the slash trimmed off at this point. We'll make sure it's there.
         $uri = Str::ensureLeft($uri, '/');
 
-        if (! $key = $this->findTaxonomyHandleByUri($uri)) {
+        if (! $key = $this->findTaxonomyHandleByUri($uri, $site)) {
             return null;
         }
 
@@ -104,9 +106,17 @@ public static function bindings(): array
         ];
     }
 
-    private function findTaxonomyHandleByUri($uri)
+    private function findTaxonomyHandleByUri($uri, $site)
     {
-        return $this->store->index('uri')->items()->flip()->get($uri);
+        $site = $site ?? $this->stache->sites()->first();
+
+        $routes = $this->store->index('routes')->items()->map(fn ($item) => $item->get($site))->filter()->flip();
+
+        if ($handle = $routes->get($uri)) {
+            return $handle;
+        }
+
+        return $routes->get(Str::removeLeft($uri, '/'));
     }
 
     public function addPreviewTargets($handle, $targets)
diff --git a/src/Stache/Repositories/TermRepository.php b/src/Stache/Repositories/TermRepository.php
index 2c0ec7e227..0b8b42500d 100644
--- a/src/Stache/Repositories/TermRepository.php
+++ b/src/Stache/Repositories/TermRepository.php
@@ -78,13 +78,16 @@ public function findByUri(string $uri, ?string $site = null): ?Term
 
         $uri = Str::removeLeft($uri, '/');
 
-        [$taxonomy, $slug] = array_pad(explode('/', $uri), 2, null);
+        $uriParts = array_pad(explode('/', $uri), 2, null);
+
+        $slug = array_pop($uriParts);
+        $taxonomy = implode('/', $uriParts);
 
         if (! $slug) {
             return null;
         }
 
-        if (! $taxonomy = $this->findTaxonomyHandleByUri($taxonomy)) {
+        if (! $taxonomy = $this->findTaxonomyHandleByUri($taxonomy, $site)) {
             return null;
         }
 
@@ -170,9 +173,15 @@ public static function bindings(): array
         ];
     }
 
-    private function findTaxonomyHandleByUri($uri)
+    private function findTaxonomyHandleByUri($uri, $site)
     {
-        return $this->stache->store('taxonomies')->index('uri')->items()->flip()->get(Str::ensureLeft($uri, '/'));
+        $routes = $this->stache->store('taxonomies')->index('routes')->items()->map(fn ($item) => $item->get($site))->filter()->flip();
+
+        if ($handle = $routes->get($uri)) {
+            return $handle;
+        }
+
+        return $routes->get(Str::ensureLeft($uri, '/'));
     }
 
     public function substitute($item)
diff --git a/src/Stache/Stores/TaxonomiesStore.php b/src/Stache/Stores/TaxonomiesStore.php
index 4978d9993b..d0ee45cdd1 100644
--- a/src/Stache/Stores/TaxonomiesStore.php
+++ b/src/Stache/Stores/TaxonomiesStore.php
@@ -45,6 +45,7 @@ public function makeItemFromFile($path, $contents)
             ->cascade(Arr::get($data, 'inject', []))
             ->revisionsEnabled(Arr::get($data, 'revisions', false))
             ->searchIndex(Arr::get($data, 'search_index'))
+            ->routes(Arr::get($data, 'route'))
             ->defaultPublishState($this->getDefaultPublishState($data))
             ->sites($sites)
             ->previewTargets($this->normalizePreviewTargets(Arr::get($data, 'preview_targets', [])))
diff --git a/src/Taxonomies/LocalizedTerm.php b/src/Taxonomies/LocalizedTerm.php
index 15808c4083..fcd7e75a68 100644
--- a/src/Taxonomies/LocalizedTerm.php
+++ b/src/Taxonomies/LocalizedTerm.php
@@ -344,7 +344,8 @@ public function apiUrl()
 
     public function route()
     {
-        $route = '/'.str_replace('_', '-', $this->taxonomyHandle()).'/{slug}';
+        $route = '/'.$this->taxonomy()->routes()->get($this->locale()).'/';
+        $route .= Str::contains($route, '{{') ? '{{ slug }}' : '{slug}';
 
         if ($this->collection()) {
             $collectionUrl = $this->collection()->uri($this->locale()) ?? $this->collection()->handle();
diff --git a/src/Taxonomies/Taxonomy.php b/src/Taxonomies/Taxonomy.php
index a118c0f45d..25e4f78ae3 100644
--- a/src/Taxonomies/Taxonomy.php
+++ b/src/Taxonomies/Taxonomy.php
@@ -38,6 +38,7 @@ class Taxonomy implements Arrayable, ArrayAccess, AugmentableContract, Contract,
     protected $handle;
     protected $title;
     protected $blueprints = [];
+    protected $routes = [];
     protected $sites = [];
     protected $collection;
     protected $defaultPublishState = true;
@@ -274,6 +275,7 @@ public function fileData()
             'title' => $this->title,
             'blueprints' => $this->blueprints,
             'preview_targets' => $this->previewTargetsForFile(),
+            'route' => $this->routes,
             'template' => $this->template,
             'term_template' => $this->termTemplate,
             'layout' => $this->layout,
@@ -344,7 +346,7 @@ public function uri()
 
         $prefix = $this->collection() ? $this->collection()->uri($site->handle()) : '/';
 
-        return URL::tidy($prefix.str_replace('_', '-', '/'.$this->handle));
+        return URL::tidy($prefix.$this->routes()->get($site->handle()));
     }
 
     public function collection($collection = null)
@@ -362,6 +364,25 @@ public function collections()
         })->values();
     }
 
+    public function routes($routes = null)
+    {
+        return $this
+            ->fluentlyGetOrSet('routes')
+            ->getter(function ($routes) {
+                return $this->sites()->mapWithKeys(function ($site) use ($routes) {
+                    $siteRoute = is_string($routes) ? $routes : ($routes[$site] ?? str_replace('_', '-', '/'.$this->handle));
+
+                    return [$site => $siteRoute];
+                });
+            })
+            ->args(func_get_args());
+    }
+
+    public function route($site)
+    {
+        return $this->routes()->get($site);
+    }
+
     public function toResponse($request)
     {
         if (! view()->exists($this->template())) {
diff --git a/tests/Data/Taxonomies/TaxonomyTest.php b/tests/Data/Taxonomies/TaxonomyTest.php
index 7c76a3c91c..32285fca56 100644
--- a/tests/Data/Taxonomies/TaxonomyTest.php
+++ b/tests/Data/Taxonomies/TaxonomyTest.php
@@ -390,6 +390,72 @@ public function if_saving_event_returns_false_the_taxonomy_doesnt_save()
         Event::assertNotDispatched(TaxonomySaved::class);
     }
 
+    #[Test]
+    public function it_gets_and_sets_the_routes()
+    {
+        $this->setSites([
+            'en' => ['url' => 'http://domain.com/'],
+            'fr' => ['url' => 'http://domain.com/fr/'],
+            'de' => ['url' => 'http://domain.com/de/'],
+        ]);
+
+        // A taxonomy with no sites uses the default site.
+        $taxonomy = new Taxonomy;
+        $this->assertInstanceOf(\Illuminate\Support\Collection::class, $taxonomy->routes());
+        $this->assertEquals(['en' => '/'], $taxonomy->routes()->all());
+
+        $return = $taxonomy->routes([
+            'en' => 'blog/',
+            'fr' => 'le-blog/',
+            'de' => 'das-blog/',
+        ]);
+
+        $this->assertEquals($taxonomy, $return);
+        $this->assertInstanceOf(\Illuminate\Support\Collection::class, $taxonomy->routes());
+
+        // Only routes corresponding to the collection's sites will be returned.
+        $this->assertEquals(['en' => 'blog/'], $taxonomy->routes()->all());
+        $this->assertEquals('blog/', $taxonomy->route('en'));
+        $this->assertNull($taxonomy->route('fr'));
+        $this->assertNull($taxonomy->route('de'));
+        $this->assertNull($taxonomy->route('unknown'));
+
+        $taxonomy->sites(['en', 'fr']);
+
+        $this->assertEquals([
+            'en' => 'blog/',
+            'fr' => 'le-blog/',
+        ], $taxonomy->routes()->all());
+        $this->assertEquals('blog/', $taxonomy->route('en'));
+        $this->assertEquals('le-blog/', $taxonomy->route('fr'));
+        $this->assertNull($taxonomy->route('de'));
+        $this->assertNull($taxonomy->route('unknown'));
+    }
+
+    #[Test]
+    public function it_sets_all_the_routes_identically()
+    {
+        $this->setSites([
+            'en' => ['url' => 'http://domain.com/'],
+            'fr' => ['url' => 'http://domain.com/fr/'],
+            'de' => ['url' => 'http://domain.com/de/'],
+        ]);
+
+        $taxonomy = (new Taxonomy)->sites(['en', 'fr']);
+
+        $return = $taxonomy->routes('{slug}');
+
+        $this->assertEquals($taxonomy, $return);
+        $this->assertEquals([
+            'en' => '{slug}',
+            'fr' => '{slug}',
+        ], $taxonomy->routes()->all());
+        $this->assertEquals('{slug}', $taxonomy->route('en'));
+        $this->assertEquals('{slug}', $taxonomy->route('fr'));
+        $this->assertNull($taxonomy->route('de'));
+        $this->assertNull($taxonomy->route('unknown'));
+    }
+
     #[Test]
     public function it_gets_and_sets_the_layout()
     {
diff --git a/tests/Data/Taxonomies/TermTest.php b/tests/Data/Taxonomies/TermTest.php
index b2352c3bb7..2541defbf2 100644
--- a/tests/Data/Taxonomies/TermTest.php
+++ b/tests/Data/Taxonomies/TermTest.php
@@ -317,6 +317,40 @@ public function it_gets_preview_targets()
         ], $termDe->previewTargets()->all());
     }
 
+    #[Test]
+    public function it_gets_routes()
+    {
+        $this->setSites([
+            'en' => ['url' => 'http://domain.com/'],
+            'fr' => ['url' => 'http://domain.com/fr/'],
+            'de' => ['url' => 'http://domain.de/'],
+        ]);
+
+        $taxonomy = tap(Taxonomy::make('tags')->sites(['en', 'fr', 'de'])->routes('tags'))->save();
+
+        $term = (new Term)->taxonomy('tags');
+
+        $termEn = $term->in('en')->slug('foo');
+        $termFr = $term->in('fr')->slug('le-foo');
+        $termDe = $term->in('de')->slug('das-foo');
+
+        $this->assertEquals('/tags/{slug}', $termEn->route());
+        $this->assertEquals('/tags/{slug}', $termFr->route());
+        $this->assertEquals('/tags/{slug}', $termDe->route());
+
+        $taxonomy->routes([
+            'en' => 'blog',
+            'fr' => 'le-blog',
+            'de' => 'das-blog',
+        ]);
+
+        $taxonomy->save();
+
+        $this->assertEquals('/blog/{slug}', $termEn->route());
+        $this->assertEquals('/le-blog/{slug}', $termFr->route());
+        $this->assertEquals('/das-blog/{slug}', $termDe->route());
+    }
+
     #[Test]
     public function it_has_a_dirty_state()
     {