diff --git a/api/controllers/console/explore/recommended_app.py b/api/controllers/console/explore/recommended_app.py
index 1125c0bc580..1fdf61f049d 100644
--- a/api/controllers/console/explore/recommended_app.py
+++ b/api/controllers/console/explore/recommended_app.py
@@ -7,17 +7,17 @@ from pydantic import BaseModel, Field, computed_field, field_validator
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
from controllers.console import console_ns
from controllers.console.explore.error import RecommendedAppNotFoundError
-from controllers.console.wraps import account_initialization_required, model_validate, with_current_user
+from controllers.console.flask_admission import console_account_admission
+from controllers.console.wraps import model_validate
from extensions.ext_application_services import application_services
from fields.base import ResponseModel
from libs.helper import build_icon_url, dump_response
-from libs.login import login_required
-from models import Account
+from machinery.context import RequestContext
from services.recommended_app_query_service import RecommendedAppNotFoundError as RecommendedAppQueryNotFoundError
class RecommendedAppsQuery(BaseModel):
- language: str | None = Field(default=None, description="Language code for recommended app localization")
+ language: str = Field(default="en-US", description="Language code for recommended app localization")
class RecommendedAppInfoResponse(ResponseModel):
@@ -97,16 +97,13 @@ register_response_schema_models(
class RecommendedAppListApi(Resource):
@console_ns.doc(params=query_params_from_model(RecommendedAppsQuery))
@console_ns.response(200, "Success", console_ns.models[RecommendedAppListResponse.__name__])
- @login_required
- @account_initialization_required
- @with_current_user
+ @console_account_admission()
@model_validate(RecommendedAppsQuery)
- def get(self, req_data: RecommendedAppsQuery, current_user: Account):
+ def get(self, req_data: RecommendedAppsQuery, _request_context: RequestContext):
return dump_response(
RecommendedAppListResponse,
application_services().recommended_app_queries.list_recommended(
- requested_language=req_data.language,
- interface_language=current_user.interface_language,
+ language=req_data.language,
),
)
@@ -115,16 +112,13 @@ class RecommendedAppListApi(Resource):
class LearnDifyAppListApi(Resource):
@console_ns.doc(params=query_params_from_model(RecommendedAppsQuery))
@console_ns.response(200, "Success", console_ns.models[LearnDifyAppListResponse.__name__])
- @login_required
- @account_initialization_required
- @with_current_user
+ @console_account_admission()
@model_validate(RecommendedAppsQuery)
- def get(self, req_data: RecommendedAppsQuery, current_user: Account):
+ def get(self, req_data: RecommendedAppsQuery, _request_context: RequestContext):
return dump_response(
LearnDifyAppListResponse,
application_services().recommended_app_queries.list_learn_dify(
- requested_language=req_data.language,
- interface_language=current_user.interface_language,
+ language=req_data.language,
),
)
@@ -133,9 +127,8 @@ class LearnDifyAppListApi(Resource):
class RecommendedAppApi(Resource):
@console_ns.response(200, "Success", console_ns.models[RecommendedAppDetailResponse.__name__])
@console_ns.response(404, "Recommended app not found")
- @login_required
- @account_initialization_required
- def get(self, app_id: UUID):
+ @console_account_admission()
+ def get(self, _request_context: RequestContext, app_id: UUID):
try:
result = application_services().recommended_app_queries.get_detail(str(app_id))
except RecommendedAppQueryNotFoundError:
diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md
index 70ddbe73455..0fa45b01005 100644
--- a/api/openapi/markdown/console-openapi.md
+++ b/api/openapi/markdown/console-openapi.md
@@ -6507,7 +6507,7 @@ Check if dataset is in use
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
-| language | query | Language code for recommended app localization | No | string |
+| language | query | Language code for recommended app localization | No | string,
**Default:** en-US |
#### Responses
@@ -6520,7 +6520,7 @@ Check if dataset is in use
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
-| language | query | Language code for recommended app localization | No | string |
+| language | query | Language code for recommended app localization | No | string,
**Default:** en-US |
#### Responses
@@ -21331,7 +21331,7 @@ Model class for provider quota configuration.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
-| language | string | Language code for recommended app localization | No |
+| language | string,
**Default:** en-US | Language code for recommended app localization | No |
#### RedirectResponse
diff --git a/api/services/explore_banner_query_service.py b/api/services/explore_banner_query_service.py
index 8ef4f3827f4..4eb204f7d85 100644
--- a/api/services/explore_banner_query_service.py
+++ b/api/services/explore_banner_query_service.py
@@ -7,6 +7,8 @@ from collections.abc import Sequence
from datetime import datetime
from typing import Any, NamedTuple, Protocol
+from constants.languages import languages
+
_DEFAULT_LANGUAGE = "en-US"
@@ -37,6 +39,7 @@ class ExploreBannerQueryService:
if not self._enabled:
return ()
+ language = language if language in languages else _DEFAULT_LANGUAGE
banners = tuple(self._banners.list_enabled(language))
if banners or language == _DEFAULT_LANGUAGE:
return banners
diff --git a/api/services/recommended_app_query_service.py b/api/services/recommended_app_query_service.py
index 5b79f40d505..f0da2259797 100644
--- a/api/services/recommended_app_query_service.py
+++ b/api/services/recommended_app_query_service.py
@@ -5,6 +5,8 @@ from typing import NamedTuple, Protocol
from constants.languages import languages
+_DEFAULT_LANGUAGE = "en-US"
+
class RecommendedAppInfoRecord(NamedTuple):
id: str
@@ -116,10 +118,9 @@ class RecommendedAppQueryService:
def list_recommended(
self,
*,
- requested_language: str | None,
- interface_language: str | None,
+ language: str,
) -> RecommendedAppListResult:
- language = self._resolve_language(requested_language, interface_language)
+ language = language if language in languages else _DEFAULT_LANGUAGE
page = self._catalog.list_recommended(language)
return RecommendedAppListResult(
@@ -130,10 +131,9 @@ class RecommendedAppQueryService:
def list_learn_dify(
self,
*,
- requested_language: str | None,
- interface_language: str | None,
+ language: str,
) -> LearnDifyAppListResult:
- language = self._resolve_language(requested_language, interface_language)
+ language = language if language in languages else _DEFAULT_LANGUAGE
page = self._catalog.list_learn_dify(language)
return LearnDifyAppListResult(recommended_apps=self._with_trial_status(page.recommended_apps))
@@ -176,11 +176,3 @@ class RecommendedAppQueryService:
)
for app in apps
)
-
- @staticmethod
- def _resolve_language(requested_language: str | None, interface_language: str | None) -> str:
- if requested_language and requested_language in languages:
- return requested_language
- if interface_language:
- return interface_language
- return languages[0]
diff --git a/api/tests/unit_tests/controllers/console/explore/test_banner.py b/api/tests/unit_tests/controllers/console/explore/test_banner.py
index 5c44ea4b8f5..9ade4ba82ad 100644
--- a/api/tests/unit_tests/controllers/console/explore/test_banner.py
+++ b/api/tests/unit_tests/controllers/console/explore/test_banner.py
@@ -110,7 +110,7 @@ class TestExploreBannerQueryService:
assert service.list_for_language("fr-FR") == (record,)
assert banners.requested_languages == ["fr-FR"]
- def test_falls_back_to_en_us(self) -> None:
+ def test_falls_back_to_en_us_when_requested_translation_is_missing(self) -> None:
record = _record(title="fallback")
banners = FakeExploreBannerQuery({"en-US": (record,)})
service = ExploreBannerQueryService(banners=banners, enabled=True)
@@ -118,6 +118,14 @@ class TestExploreBannerQueryService:
assert service.list_for_language("es-ES") == (record,)
assert banners.requested_languages == ["es-ES", "en-US"]
+ def test_invalid_language_uses_en_us(self) -> None:
+ record = _record(title="fallback")
+ banners = FakeExploreBannerQuery({"en-US": (record,)})
+ service = ExploreBannerQueryService(banners=banners, enabled=True)
+
+ assert service.list_for_language("invalid") == (record,)
+ assert banners.requested_languages == ["en-US"]
+
def test_does_not_repeat_default_language_query(self) -> None:
banners = FakeExploreBannerQuery()
service = ExploreBannerQueryService(banners=banners, enabled=True)
diff --git a/api/tests/unit_tests/controllers/console/explore/test_recommended_app.py b/api/tests/unit_tests/controllers/console/explore/test_recommended_app.py
index fccbba38d6d..e281a22caac 100644
--- a/api/tests/unit_tests/controllers/console/explore/test_recommended_app.py
+++ b/api/tests/unit_tests/controllers/console/explore/test_recommended_app.py
@@ -7,7 +7,7 @@ from flask import Flask
from pydantic import ValidationError
import controllers.console.explore.recommended_app as module
-from models import Account
+from machinery.context import RequestContext
from models.model import AppMode, IconType
from services.recommended_app_query_service import (
LearnDifyAppListResult,
@@ -21,11 +21,13 @@ from services.recommended_app_query_service import (
)
-def make_account(interface_language: str | None) -> Account:
- account = Account(name="Test User", email="user@example.com")
- account.id = "account-1"
- account.interface_language = interface_language
- return account
+def _request_context() -> RequestContext:
+ return RequestContext(
+ request_id="request-1",
+ trace_id="trace-1",
+ account_id="account-1",
+ active_workspace_id="workspace-1",
+ )
class TestRecommendedAppListApi:
@@ -44,17 +46,16 @@ class TestRecommendedAppListApi:
return_value=SimpleNamespace(recommended_app_queries=queries),
),
):
- result = method(api, module.RecommendedAppsQuery(language="en-US"), make_account("fr-FR"))
+ result = method(api, module.RecommendedAppsQuery(language="en-US"), _request_context())
queries.list_recommended.assert_called_once_with(
- requested_language="en-US",
- interface_language="fr-FR",
+ language="en-US",
)
assert result == {"recommended_apps": [], "categories": []}
class TestLearnDifyAppListApi:
- def test_get_with_language_param(self, app: Flask) -> None:
+ def test_get_uses_default_language(self, app: Flask) -> None:
api = module.LearnDifyAppListApi()
method = unwrap(api.get)
@@ -62,18 +63,17 @@ class TestLearnDifyAppListApi:
queries.list_learn_dify.return_value = LearnDifyAppListResult(recommended_apps=())
with (
- app.test_request_context("/", query_string={"language": "en-US"}),
+ app.test_request_context("/"),
patch.object(
module,
"application_services",
return_value=SimpleNamespace(recommended_app_queries=queries),
),
):
- result = method(api, module.RecommendedAppsQuery(language="en-US"), make_account("fr-FR"))
+ result = method(api, module.RecommendedAppsQuery(), _request_context())
queries.list_learn_dify.assert_called_once_with(
- requested_language="en-US",
- interface_language="fr-FR",
+ language="en-US",
)
assert result == {"recommended_apps": []}
@@ -102,7 +102,7 @@ class TestRecommendedAppApi:
return_value=SimpleNamespace(recommended_app_queries=queries),
),
):
- result = method(api, "11111111-1111-1111-1111-111111111111")
+ result = method(api, _request_context(), "11111111-1111-1111-1111-111111111111")
queries.get_detail.assert_called_once_with("11111111-1111-1111-1111-111111111111")
assert result == {
@@ -130,7 +130,7 @@ class TestRecommendedAppApi:
),
):
with pytest.raises(module.RecommendedAppNotFoundError) as exc_info:
- method(api, "11111111-1111-1111-1111-111111111111")
+ method(api, _request_context(), "11111111-1111-1111-1111-111111111111")
assert exc_info.value.data == {
"code": "recommended_app_not_found",
diff --git a/api/tests/unit_tests/extensions/test_ext_application_services.py b/api/tests/unit_tests/extensions/test_ext_application_services.py
index 649958ca5bf..39f8f2c10f6 100644
--- a/api/tests/unit_tests/extensions/test_ext_application_services.py
+++ b/api/tests/unit_tests/extensions/test_ext_application_services.py
@@ -721,14 +721,12 @@ def test_build_application_services_wires_dynamic_recommended_catalog(
)
with patch.object(recommended_app_catalog_gateway.Path, "read_text", return_value=builtin_payload):
result = services.recommended_app_queries.list_recommended(
- requested_language="en-US",
- interface_language=None,
+ language="en-US",
)
assert result.recommended_apps
apply_config_overrides(monkeypatch, HOSTED_FETCH_APP_TEMPLATES_MODE="invalid")
with pytest.raises(ValueError, match="invalid fetch recommended apps mode: invalid"):
services.recommended_app_queries.list_recommended(
- requested_language="en-US",
- interface_language=None,
+ language="en-US",
)
diff --git a/api/tests/unit_tests/services/test_recommended_app_query_service.py b/api/tests/unit_tests/services/test_recommended_app_query_service.py
index 09d661b6754..3e762b12167 100644
--- a/api/tests/unit_tests/services/test_recommended_app_query_service.py
+++ b/api/tests/unit_tests/services/test_recommended_app_query_service.py
@@ -2,7 +2,6 @@ from unittest.mock import MagicMock
import pytest
-from constants.languages import languages
from services.recommended_app_query_service import (
RecommendedAppCatalogPage,
RecommendedAppDetailRecord,
@@ -83,17 +82,14 @@ def test_is_previewable_falls_back_to_catalog(expected: bool) -> None:
@pytest.mark.parametrize(
- ("requested_language", "interface_language", "expected"),
+ ("language", "expected"),
[
- ("en-US", "fr-FR", "en-US"),
- ("invalid", "fr-FR", "fr-FR"),
- (None, "custom-language", "custom-language"),
- (None, None, languages[0]),
+ ("fr-FR", "fr-FR"),
+ ("invalid", "en-US"),
],
)
def test_list_recommended_resolves_language(
- requested_language: str | None,
- interface_language: str | None,
+ language: str,
expected: str,
) -> None:
catalog = MagicMock()
@@ -101,8 +97,7 @@ def test_list_recommended_resolves_language(
service, _ = _service(catalog=catalog)
service.list_recommended(
- requested_language=requested_language,
- interface_language=interface_language,
+ language=language,
)
catalog.list_recommended.assert_called_once_with(expected)
@@ -113,7 +108,7 @@ def test_list_recommended_disables_upstream_trial_without_querying_trial_apps()
catalog.list_recommended.return_value = _page("app-1")
service, trial_apps = _service(catalog=catalog)
- result = service.list_recommended(requested_language="en-US", interface_language=None)
+ result = service.list_recommended(language="en-US")
assert result.recommended_apps[0].can_trial is False
trial_apps.existing_ids.assert_not_called()
@@ -130,7 +125,7 @@ def test_list_recommended_enriches_trial_status_in_one_bulk_query() -> None:
trial_enabled=True,
)
- result = service.list_recommended(requested_language="en-US", interface_language=None)
+ result = service.list_recommended(language="en-US")
assert [app.can_trial for app in result.recommended_apps] == [True, False]
trial_apps.existing_ids.assert_called_once_with(["app-1", "app-2"])
@@ -141,9 +136,9 @@ def test_list_learn_dify_does_not_return_categories() -> None:
catalog.list_learn_dify.return_value = _page(categories=("ignored",))
service, _ = _service(catalog=catalog)
- result = service.list_learn_dify(requested_language="invalid", interface_language="fr-FR")
+ result = service.list_learn_dify(language="invalid")
- catalog.list_learn_dify.assert_called_once_with("fr-FR")
+ catalog.list_learn_dify.assert_called_once_with("en-US")
assert result.recommended_apps == ()
assert not hasattr(result, "categories")
diff --git a/packages/contracts/generated/api/console/explore/zod.gen.ts b/packages/contracts/generated/api/console/explore/zod.gen.ts
index 272b2884146..995a5b98a58 100644
--- a/packages/contracts/generated/api/console/explore/zod.gen.ts
+++ b/packages/contracts/generated/api/console/explore/zod.gen.ts
@@ -137,7 +137,7 @@ export const zLearnDifyAppListResponseWritable = z.object({
})
export const zGetExploreAppsQuery = z.object({
- language: z.string().optional(),
+ language: z.string().optional().default('en-US'),
})
/**
@@ -146,7 +146,7 @@ export const zGetExploreAppsQuery = z.object({
export const zGetExploreAppsResponse = zRecommendedAppListResponse
export const zGetExploreAppsLearnDifyQuery = z.object({
- language: z.string().optional(),
+ language: z.string().optional().default('en-US'),
})
/**