diff --git a/third_party/blink/renderer/modules/ai/ai_mojo_client.h b/third_party/blink/renderer/modules/ai/ai_mojo_client.h
index 111b8b4318b42..886f3fab16154 100644
--- a/third_party/blink/renderer/modules/ai/ai_mojo_client.h
+++ b/third_party/blink/renderer/modules/ai/ai_mojo_client.h
@@ -52,6 +52,7 @@ class AIMojoClient : public ContextLifecycleObserver {
   ~AIMojoClient() override = default;
 
  protected:
+  ScriptState* GetScriptState() { return script_state_; }
   ScriptPromiseResolver<V8SessionObjectType>* GetResolver() {
     return resolver_;
   }
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.cc b/third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.cc
index 557c84caff5e5..96c7ea3066e71 100644
--- a/third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.cc
+++ b/third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.cc
@@ -7,10 +7,13 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ai_create_monitor_callback.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/modules/ai/ai.h"
 #include "third_party/blink/renderer/modules/ai/ai_create_monitor.h"
 #include "third_party/blink/renderer/modules/ai/ai_mojo_client.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 namespace {
@@ -18,20 +21,37 @@ namespace {
 const char kExceptionMessageUnableToCreateTranslator[] =
     "Unable to create translator for the given source and target language.";
 
+bool RequiresUserActivation(mojom::blink::CanCreateTranslatorResult result) {
+  switch (result) {
+    case mojom::blink::CanCreateTranslatorResult::kAfterDownloadLibraryNotReady:
+    case mojom::blink::CanCreateTranslatorResult::
+        kAfterDownloadLanguagePackNotReady:
+    case mojom::blink::CanCreateTranslatorResult::
+        kAfterDownloadLibraryAndLanguagePackNotReady:
+      return true;
+    case mojom::blink::CanCreateTranslatorResult::kReadily:
+    case mojom::blink::CanCreateTranslatorResult::kNoNotSupportedLanguage:
+    case mojom::blink::CanCreateTranslatorResult::kNoAcceptLanguagesCheckFailed:
+    case mojom::blink::CanCreateTranslatorResult::
+        kNoExceedsLanguagePackCountLimitation:
+    case mojom::blink::CanCreateTranslatorResult::kNoServiceCrashed:
+    case mojom::blink::CanCreateTranslatorResult::kNoDisallowedByPolicy:
+    case mojom::blink::CanCreateTranslatorResult::
+        kNoExceedsServiceCountLimitation:
+      return false;
+  }
+}
+
 class CreateTranslatorClient
     : public GarbageCollected<CreateTranslatorClient>,
       public mojom::blink::TranslationManagerCreateTranslatorClient,
       public AIMojoClient<AITranslator> {
  public:
-  CreateTranslatorClient(
-      ScriptState* script_state,
-      AITranslatorFactory* translation,
-      AITranslatorCreateOptions* options,
-      scoped_refptr<base::SequencedTaskRunner> task_runner,
-      ScriptPromiseResolver<AITranslator>* resolver,
-      mojo::PendingReceiver<
-          mojom::blink::TranslationManagerCreateTranslatorClient>
-          pending_receiver)
+  CreateTranslatorClient(ScriptState* script_state,
+                         AITranslatorFactory* translation,
+                         AITranslatorCreateOptions* options,
+                         scoped_refptr<base::SequencedTaskRunner> task_runner,
+                         ScriptPromiseResolver<AITranslator>* resolver)
       : AIMojoClient(script_state,
                      translation,
                      resolver,
@@ -41,7 +61,6 @@ class CreateTranslatorClient
         target_language_(options->targetLanguage()),
         receiver_(this, translation_->GetExecutionContext()),
         task_runner_(task_runner) {
-    receiver_.Bind(std::move(pending_receiver), task_runner);
     if (options->hasMonitor()) {
       monitor_ = MakeGarbageCollected<AICreateMonitor>(
           translation_->GetExecutionContext(), task_runner);
@@ -86,6 +105,30 @@ class CreateTranslatorClient
     Cleanup();
   }
 
+  void OnGotAvailability(mojom::blink::CanCreateTranslatorResult result) {
+    LocalDOMWindow* const window = LocalDOMWindow::From(GetScriptState());
+
+    if (RuntimeEnabledFeatures::TranslationAPIV1Enabled() &&
+        RequiresUserActivation(result) &&
+        !LocalFrame::ConsumeTransientUserActivation(window->GetFrame())) {
+      GetResolver()->RejectWithDOMException(
+          DOMExceptionCode::kNotAllowedError,
+          "Requires handling a user gesture when availability is "
+          "\"after-download\".");
+      return;
+    }
+    mojo::PendingRemote<mojom::blink::TranslationManagerCreateTranslatorClient>
+        client;
+
+    receiver_.Bind(client.InitWithNewPipeAndPassReceiver(), task_runner_);
+
+    translation_->GetTranslationManagerRemote()->CreateTranslator(
+        std::move(client),
+        mojom::blink::TranslatorCreateOptions::New(
+            mojom::blink::TranslatorLanguageCode::New(source_language_),
+            mojom::blink::TranslatorLanguageCode::New(target_language_)));
+  }
+
   void ResetReceiver() override { receiver_.reset(); }
 
  private:
@@ -111,14 +154,15 @@ ScriptPromise<AITranslator> AITranslatorFactory::create(
     ScriptState* script_state,
     AITranslatorCreateOptions* options,
     ExceptionState& exception_state) {
+  // If `sourceLanguage` and `targetLanguage` are not passed, A TypeError should
+  // be thrown before we get here.
+  CHECK(options && options->sourceLanguage() && options->targetLanguage());
+
   if (!script_state->ContextIsValid()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       "The execution context is not valid.");
     return EmptyPromise();
   }
-  // If `sourceLanguage` and `targetLanguage` are not passed, A TypeError should
-  // be thrown before we get here.
-  CHECK(options && options->sourceLanguage() && options->targetLanguage());
 
   AbortSignal* signal = options->getSignalOr(nullptr);
   if (HandleAbortSignal(signal, script_state, exception_state)) {
@@ -128,17 +172,15 @@ ScriptPromise<AITranslator> AITranslatorFactory::create(
   auto* resolver =
       MakeGarbageCollected<ScriptPromiseResolver<AITranslator>>(script_state);
 
-  mojo::PendingRemote<mojom::blink::TranslationManagerCreateTranslatorClient>
-      client;
-  MakeGarbageCollected<CreateTranslatorClient>(
-      script_state, this, options, task_runner_, resolver,
-      client.InitWithNewPipeAndPassReceiver());
-  GetTranslationManagerRemote()->CreateTranslator(
-      std::move(client),
-      mojom::blink::TranslatorCreateOptions::New(
-          mojom::blink::TranslatorLanguageCode::New(options->sourceLanguage()),
-          mojom::blink::TranslatorLanguageCode::New(
-              options->targetLanguage())));
+  CreateTranslatorClient* create_translator_client =
+      MakeGarbageCollected<CreateTranslatorClient>(script_state, this, options,
+                                                   task_runner_, resolver);
+
+  GetTranslationManagerRemote()->CanCreateTranslator(
+      mojom::blink::TranslatorLanguageCode::New(options->sourceLanguage()),
+      mojom::blink::TranslatorLanguageCode::New(options->targetLanguage()),
+      WTF::BindOnce(&CreateTranslatorClient::OnGotAvailability,
+                    WrapPersistent(create_translator_client)));
 
   return resolver->Promise();
 }
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.h b/third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.h
index a45250ede093c..0dc723c3b4ae0 100644
--- a/third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.h
+++ b/third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.h
@@ -34,12 +34,12 @@ class AITranslatorFactory final : public ScriptWrappable,
       ScriptState* script_state,
       ExceptionState& exception_state);
 
-  void Trace(Visitor* visitor) const override;
-
- private:
   HeapMojoRemote<mojom::blink::TranslationManager>&
   GetTranslationManagerRemote();
 
+  void Trace(Visitor* visitor) const override;
+
+ private:
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
   HeapMojoRemote<mojom::blink::TranslationManager> translation_manager_remote_{
       nullptr};
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 6c727b2c6a2d2..ca9d96369af5a 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -4498,6 +4498,7 @@
       origin_trial_feature_name: "TranslationAPI",
       base_feature_status: "enabled",
       copied_from_base_feature_if: "overridden",
+      implied_by: ["TranslationAPIV1"],
     },
     {
       name: "TranslationAPIEntryPoint",
@@ -4505,6 +4506,11 @@
       origin_trial_feature_name: "TranslationAPIEntryPoint",
       implied_by: ["TranslationAPI", "LanguageDetectionAPI"],
     },
+    {
+      name: "TranslationAPIV1",
+      status: "test",
+      copied_from_base_feature_if: "overridden",
+    },
     {
       name: "TrustedTypeBeforePolicyCreationEvent",
       status: "experimental",
diff --git a/third_party/blink/web_tests/external/wpt/ai/translator/ai_translator_translate.tentative.https.any.js b/third_party/blink/web_tests/external/wpt/ai/translator/ai_translator_translate.tentative.https.any.js
index 2041ad82b5aad..5cd8cb86b22b8 100644
--- a/third_party/blink/web_tests/external/wpt/ai/translator/ai_translator_translate.tentative.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/ai/translator/ai_translator_translate.tentative.https.any.js
@@ -1,24 +1,54 @@
 // META: title=Translate from English to Japanese
-// META: global=window,worker
+// META: global=window
 // META: timeout=long
 // META: script=../resources/util.js
+// META: script=../resources/language_codes.js
+// META: script=/resources/testdriver.js
 //
 // Setting `timeout=long` as this test may require downloading the translation
 // library and the language models.
 
 'use strict';
 
+async function createTranslator(options) {
+  return await test_driver.bless('Create translator', async () => {
+    return await ai.translator.create(options);
+  });
+}
+
 promise_test(async t => {
-  const translatorFactory = ai.translator;
-  assert_not_equals(translatorFactory, null);
-  const translator = await translatorFactory.create(
-      {sourceLanguage: 'en', targetLanguage: 'ja'});
+  const languagePair = {sourceLanguage: 'en', targetLanguage: 'ja'};
+
+  // Creating the translator without user activation rejects with
+  // NotAllowedError.
+  const createPromise = ai.translator.create(languagePair);
+  await promise_rejects_dom(t, 'NotAllowedError', createPromise);
+
+  // Creating the translator with user activation succeeds.
+  await createTranslator(languagePair);
+
+  // TODO(crbug.com/390459310): Replace with availability.
+  //
+  // Creating it should have switched it to readily.
+  const capabilities = await ai.translator.capabilities();
+  const {sourceLanguage, targetLanguage} = languagePair;
+  assert_equals(
+      capabilities.languagePairAvailable(sourceLanguage, targetLanguage),
+      'readily');
+
+  // Now that it is readily, we should no longer need user activation.
+  await ai.translator.create(languagePair);
+}, 'AITranslator.create() requires user activation when availability is "after-download".');
+
+promise_test(async t => {
+  const translator =
+      await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'});
   assert_equals(await translator.translate('hello'), 'こんにちは');
 }, 'Simple AITranslator.translate() call');
 
 promise_test(async () => {
   const translator =
-      await ai.translator.create({sourceLanguage: 'en', targetLanguage: 'ja'});
+      await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'});
   const streamingResponse = translator.translateStreaming('hello');
   assert_equals(
       Object.prototype.toString.call(streamingResponse),
@@ -32,14 +62,14 @@ promise_test(async () => {
 
 promise_test(async t => {
   const translator =
-      await ai.translator.create({sourceLanguage: 'en', targetLanguage: 'ja'});
+      await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'});
   assert_equals(translator.sourceLanguage, 'en');
   assert_equals(translator.targetLanguage, 'ja');
 }, 'AITranslator: sourceLanguage and targetLanguage are equal to their respective option passed in to AITranslatorFactory.create.')
 
 promise_test(async (t) => {
   const translator =
-      await ai.translator.create({sourceLanguage: 'en', targetLanguage: 'ja'});
+      await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'});
   translator.destroy();
   await promise_rejects_dom(
       t, 'InvalidStateError', translator.translate('hello'));
@@ -49,7 +79,7 @@ promise_test(async t => {
   const controller = new AbortController();
   controller.abort();
 
-  const createPromise = ai.translator.create(
+  const createPromise = createTranslator(
       {signal: controller.signal, sourceLanguage: 'en', targetLanguage: 'ja'});
 
   await promise_rejects_dom(t, 'AbortError', createPromise);
@@ -57,7 +87,7 @@ promise_test(async t => {
 
 promise_test(async t => {
   await testAbortPromise(t, signal => {
-    return ai.translator.create(
+    return createTranslator(
         {signal, sourceLanguage: 'en', targetLanguage: 'ja'});
   });
 }, 'Aborting AITranslatorFactory.create().');
@@ -67,7 +97,7 @@ promise_test(async t => {
   controller.abort();
 
   const translator =
-      await ai.translator.create({sourceLanguage: 'en', targetLanguage: 'ja'});
+      await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'});
   const translatePromise =
       translator.translate('hello', {signal: controller.signal});
 
@@ -76,7 +106,7 @@ promise_test(async t => {
 
 promise_test(async t => {
   const translator =
-      await ai.translator.create({sourceLanguage: 'en', targetLanguage: 'ja'});
+      await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja'});
   await testAbortPromise(t, signal => {
     return translator.translate('hello', {signal});
   });
@@ -93,8 +123,7 @@ promise_test(async t => {
     });
   }
 
-  await ai.translator.create(
-      {sourceLanguage: 'en', targetLanguage: 'ja', monitor});
+  await createTranslator({sourceLanguage: 'en', targetLanguage: 'ja', monitor});
 
   // Monitor callback must be called.
   assert_true(monitorCalled);