diff --git a/docs/guides/en/extensions/store.md b/docs/guides/en/extensions/store.md index edfd14e..a49ea28 100644 --- a/docs/guides/en/extensions/store.md +++ b/docs/guides/en/extensions/store.md @@ -9,9 +9,16 @@ provider commerce operations and must not contain credentials. persists the profile during registration or synchronization rather than calling it for every list or detail view. -Return `relatedAppIds` and a strict `i18nMap` containing `ko`, `ja`, and `en`. Each locale contains -media-key images with optional alt text, an intro (`helpsWith`, `recommendedFor`), and FAQs. Return -the persisted metadata directly without a `profile` wrapper. +Return `relatedAppIds`, optional `root` content, and an `i18nMap` keyed by any non-empty Desk locale code. Root is not a locale. Add only locales +that contain authored content; `ko`, `ja`, and `en` are not pre-created or required. Each locale +contains media-key images with optional alt text, an intro (`helpsWith`, `recommendedFor`), and +FAQs. Return the persisted metadata directly without a `profile` wrapper. + +AppStore treats images, intro fields, and related apps as extension-first fallbacks: a non-empty +extension value is shown read-only in the Developer GUI. FAQs are additive instead. Extension FAQs +are shown first and remain read-only, while Developer GUI FAQs can be appended and edited. +For each field, locale fallback is `ko → root → en` for Korean, `en → root` for English, and +`locale → en → root` for every other locale. ## TypeScript @@ -32,7 +39,7 @@ Use the `extension/store` DTOs and keep stable IDs separate from localized label - Registration uses an app token; profile reads do not need provider credentials. - Sanitize externally sourced text and URLs before returning them. - Increment or re-register when presentation metadata changes according to the release process. -- Test all locales, missing optional assets, invalid URLs, schema discovery, and a real +- Test every locale you author, arbitrary locale codes, missing optional assets, invalid URLs, schema discovery, and a real registration/sync readback. See the [Go Extension reference](../../../reference/go/EXTENSIONS.md). diff --git a/docs/guides/ja/extensions/store.md b/docs/guides/ja/extensions/store.md index 17818ee..6b6f9ca 100644 --- a/docs/guides/ja/extensions/store.md +++ b/docs/guides/ja/extensions/store.md @@ -8,9 +8,16 @@ operation を実装する Extension ではなく、credential を含めてはい `extension.store.metadata.getStoreProfile` が必須です。`store:v1` を登録すると AppStore は registration/sync 時に profile を読み、list/detail request ごとには app Function を呼びません。 -`relatedAppIds` と `ko`、`ja`、`en` をすべて含む strict `i18nMap` を返します。各 locale は media -key image と optional alt text、intro(`helpsWith`、`recommendedFor`)、FAQ を含みます。 -`profile` wrapper なしで persisted metadata を直接返します。 +`relatedAppIds`、optional `root` content、Desk の non-empty locale code を key にする `i18nMap` を返します。root は locale ではありません。実際に +作成した locale だけを追加し、`ko`、`ja`、`en` を事前に作成したり、すべて埋めたりする必要は +ありません。各 locale は media key image と optional alt text、intro(`helpsWith`、 +`recommendedFor`)、FAQ を含みます。`profile` wrapper なしで persisted metadata を直接返します。 + +AppStore は image、intro field、related app を extension-first fallback として扱います。 +extension value が空でなければ Developer GUI では read-only です。FAQ は例外として加算され、 +extension FAQ を先に read-only で表示した後、Developer GUI FAQ を追加・編集できます。 +Field ごとの locale fallback は、韓国語では `ko → root → en`、英語では `en → root`、その他の +locale では `locale → en → root` の順です。 ## TypeScript @@ -31,7 +38,7 @@ err := app.Use(store.Extension().GetStoreProfile(handler.GetStoreProfile)) - Registration は app token を使い、profile read に provider credential は不要です。 - 外部由来の文言と URL は返す前に sanitize します。 - Release 手順に従って metadata 変更時の version または re-registration を処理します。 -- 3 locale、optional asset 不足、invalid URL、schema discovery、実際の registration/sync readback +- 作成したすべての locale、任意の locale code、optional asset 不足、invalid URL、schema discovery、実際の registration/sync readback を test します。 [Go Extension reference](../../../reference/go/EXTENSIONS.md) も参照してください。 diff --git a/docs/guides/ko/extensions/store.md b/docs/guides/ko/extensions/store.md index 05d27a5..18088b7 100644 --- a/docs/guides/ko/extensions/store.md +++ b/docs/guides/ko/extensions/store.md @@ -8,9 +8,16 @@ operation을 구현하는 Extension이 아니며 credential을 포함하면 안 `extension.store.metadata.getStoreProfile`이 필수입니다. `store:v1`을 등록하면 AppStore가 등록 또는 sync 시 profile을 읽어 저장하며, list/detail 요청마다 app Function을 호출하지 않습니다. -`relatedAppIds`와 `ko`, `ja`, `en`을 모두 포함하는 strict `i18nMap`을 반환합니다. 각 locale은 media -key image와 optional alt text, intro(`helpsWith`, `recommendedFor`), FAQ를 포함합니다. `profile` -wrapper 없이 persisted metadata를 직접 반환합니다. +`relatedAppIds`, optional `root` content, Desk의 non-empty locale code를 key로 사용하는 `i18nMap`을 반환합니다. root는 locale이 아닙니다. 실제로 +작성한 locale만 추가하며 `ko`, `ja`, `en`을 미리 만들거나 모두 채울 필요는 없습니다. 각 locale은 +media key image와 optional alt text, intro(`helpsWith`, `recommendedFor`), FAQ를 포함합니다. +`profile` wrapper 없이 persisted metadata를 직접 반환합니다. + +AppStore는 이미지, intro 필드, 관련 앱을 extension 우선 fallback으로 처리합니다. extension 값이 +비어 있지 않으면 Developer GUI에 read-only로 표시됩니다. FAQ는 예외로 합산합니다. extension +FAQ를 먼저 read-only로 표시하고, 그 뒤에 Developer GUI FAQ를 추가·수정할 수 있습니다. +필드별 locale fallback은 한국어에서 `ko → root → en`, 영어에서 `en → root`, 그 외 locale에서 +`locale → en → root` 순서입니다. ## TypeScript @@ -31,7 +38,7 @@ err := app.Use(store.Extension().GetStoreProfile(handler.GetStoreProfile)) - 등록은 app token을 사용하며 profile 조회에는 provider credential이 필요하지 않습니다. - 외부에서 가져온 문구와 URL을 반환하기 전에 sanitize합니다. - Release 절차에 따라 metadata 변경 시 version 또는 re-registration을 처리합니다. -- 세 locale, optional asset 누락, invalid URL, schema discovery, 실제 registration/sync readback을 +- 작성한 모든 locale, 임의 locale code, optional asset 누락, invalid URL, schema discovery, 실제 registration/sync readback을 테스트합니다. [Go Extension 레퍼런스](../../../reference/go/EXTENSIONS.md)도 확인하세요. diff --git a/docs/reference/typescript/extensions/store.md b/docs/reference/typescript/extensions/store.md index ad3c55e..f031abe 100644 --- a/docs/reference/typescript/extensions/store.md +++ b/docs/reference/typescript/extensions/store.md @@ -2,7 +2,7 @@ Use the Store Extension to provide localized App Store introduction metadata without hardcoding app-specific copy in App Store. -App Store calls the metadata function during registration or re-registration, validates the result, and persists it. App Store reads the stored effective profile for list and detail queries; it does not call the app function for every customer-facing read. +App Store calls the metadata function during registration or re-registration, validates the result, and persists it as the `extension` source. App Store combines it with any Developer GUI fallback when serving list and detail queries; it does not call the app function for every customer-facing read. ## Required Function @@ -17,11 +17,8 @@ The function returns the persisted metadata directly. Do not wrap it in a `profi ```typescript type StoreProfileMetadata = { relatedAppIds: string[]; - i18nMap: { - ko: StoreProfileLocalizedContent; - ja: StoreProfileLocalizedContent; - en: StoreProfileLocalizedContent; - }; + root?: StoreProfileLocalizedContent; + i18nMap: Record; }; type StoreProfileLocalizedContent = { @@ -35,10 +32,15 @@ type StoreProfileLocalizedContent = { ``` - `relatedAppIds` lists apps that work with this app. +- `root` is optional default content. It is not a locale and is never copied into `i18nMap`. +- `i18nMap` accepts every non-empty locale code exposed by Desk. Add only locales that have authored content; `ko`, `ja`, and `en` are not pre-created or required. - `images[].key` is the opaque relative key returned by the App Store media upload API. Do not assume a storage prefix or pass an external URL. - `images[].alt` is limited to 120 characters. - Intro and FAQ answers support the limited Markdown syntax validated by App Store. -- Use empty arrays or empty strings when App Store Developer GUI should provide a fallback value. +- The SDK output always represents the `extension` source. It never contains Developer GUI values or source wrappers. +- Use empty image/related-app arrays or whitespace-only/empty intro strings when App Store Developer GUI should provide a fallback value. App Store evaluates those fields per locale/field. +- FAQs are additive rather than fallback-only: App Store shows extension FAQs first and appends Developer GUI FAQs. Extension FAQs remain read-only in the Developer GUI, while developers can add, edit, or remove GUI FAQs. +- App Store resolves each field as `ko → root → en` for Korean, `en → root` for English, and `locale → en → root` for every other locale. ## TypeScript Implementation @@ -70,9 +72,8 @@ export class MyStoreExtension implements StoreExtensionInterface { return { relatedAppIds: [], + root: localizedContent, i18nMap: { - ko: localizedContent, - ja: localizedContent, en: localizedContent, }, }; diff --git a/go/extension/schemaregistry/extension_function_schemas.json b/go/extension/schemaregistry/extension_function_schemas.json index b95a365..8d4218b 100644 --- a/go/extension/schemaregistry/extension_function_schemas.json +++ b/go/extension/schemaregistry/extension_function_schemas.json @@ -22326,213 +22326,138 @@ }, "i18nMap": { "type": "object", - "properties": { - "ko": { - "type": "object", - "properties": { - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "key": { - "type": "string", - "minLength": 1 - }, - "alt": { - "type": "string", - "maxLength": 120 - } - }, - "required": [ - "key" - ], - "additionalProperties": false - } - }, - "intro": { + "additionalProperties": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { "type": "object", "properties": { - "helpsWith": { - "type": "string" + "key": { + "type": "string", + "minLength": 1 }, - "recommendedFor": { - "type": "string" + "alt": { + "type": "string", + "maxLength": 120 } }, "required": [ - "helpsWith", - "recommendedFor" + "key" ], "additionalProperties": false - }, - "faqs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "question": { - "type": "string", - "minLength": 1 - }, - "answer": { - "type": "string" - } - }, - "required": [ - "question", - "answer" - ], - "additionalProperties": false - } } }, - "required": [ - "images", - "intro", - "faqs" - ], - "additionalProperties": false - }, - "ja": { - "type": "object", - "properties": { - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "key": { - "type": "string", - "minLength": 1 - }, - "alt": { - "type": "string", - "maxLength": 120 - } - }, - "required": [ - "key" - ], - "additionalProperties": false + "intro": { + "type": "object", + "properties": { + "helpsWith": { + "type": "string" + }, + "recommendedFor": { + "type": "string" } }, - "intro": { + "required": [ + "helpsWith", + "recommendedFor" + ], + "additionalProperties": false + }, + "faqs": { + "type": "array", + "items": { "type": "object", "properties": { - "helpsWith": { - "type": "string" + "question": { + "type": "string", + "minLength": 1 }, - "recommendedFor": { + "answer": { "type": "string" } }, "required": [ - "helpsWith", - "recommendedFor" + "question", + "answer" ], "additionalProperties": false - }, - "faqs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "question": { - "type": "string", - "minLength": 1 - }, - "answer": { - "type": "string" - } - }, - "required": [ - "question", - "answer" - ], - "additionalProperties": false - } } - }, - "required": [ - "images", - "intro", - "faqs" - ], - "additionalProperties": false + } }, - "en": { - "type": "object", - "properties": { - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "key": { - "type": "string", - "minLength": 1 - }, - "alt": { - "type": "string", - "maxLength": 120 - } - }, - "required": [ - "key" - ], - "additionalProperties": false + "required": [ + "images", + "intro", + "faqs" + ], + "additionalProperties": false + } + }, + "root": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "minLength": 1 + }, + "alt": { + "type": "string", + "maxLength": 120 } }, - "intro": { - "type": "object", - "properties": { - "helpsWith": { - "type": "string" - }, - "recommendedFor": { - "type": "string" - } - }, - "required": [ - "helpsWith", - "recommendedFor" - ], - "additionalProperties": false + "required": [ + "key" + ], + "additionalProperties": false + } + }, + "intro": { + "type": "object", + "properties": { + "helpsWith": { + "type": "string" }, - "faqs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "question": { - "type": "string", - "minLength": 1 - }, - "answer": { - "type": "string" - } - }, - "required": [ - "question", - "answer" - ], - "additionalProperties": false - } + "recommendedFor": { + "type": "string" } }, "required": [ - "images", - "intro", - "faqs" + "helpsWith", + "recommendedFor" ], "additionalProperties": false + }, + "faqs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "question": { + "type": "string", + "minLength": 1 + }, + "answer": { + "type": "string" + } + }, + "required": [ + "question", + "answer" + ], + "additionalProperties": false + } } }, "required": [ - "ko", - "ja", - "en" + "images", + "intro", + "faqs" ], "additionalProperties": false } diff --git a/go/extension/store/extension.go b/go/extension/store/extension.go index ca797cb..aa7c567 100644 --- a/go/extension/store/extension.go +++ b/go/extension/store/extension.go @@ -43,6 +43,9 @@ func (b *ExtensionBuilder) Register(app *appsdk.App) error { return b.base.Register(app) } +// StaticProfile returns metadata for the extension source. Root is optional and distinct from +// I18NMap locales. Empty localized arrays and intro strings intentionally leave those fields +// available for an App Store Developer GUI fallback. func StaticProfile(profile *Profile) appsdk.TypedHandlerFunc[GetStoreProfileRequest, GetStoreProfileResponse] { return func(context.Context, appsdk.Context, *GetStoreProfileRequest) (*GetStoreProfileResponse, error) { return profile, nil diff --git a/go/extension/store/extension_test.go b/go/extension/store/extension_test.go new file mode 100644 index 0000000..9199f5a --- /dev/null +++ b/go/extension/store/extension_test.go @@ -0,0 +1,47 @@ +package store + +import ( + "context" + "testing" + + "github.com/channel-io/app-sdk/go/appsdk" +) + +func TestStaticProfilePreservesEmptyExtensionValuesForGUIFallback(t *testing.T) { + empty := &LocalizedContent{ + Images: []*Image{}, + Intro: &Intro{ + HelpsWith: "", + RecommendedFor: " ", + }, + Faqs: []*FAQ{}, + } + profile := &Profile{ + RelatedAppIds: []string{}, + Root: &LocalizedContent{ + Images: []*Image{}, + Intro: &Intro{ + HelpsWith: "Root help", + RecommendedFor: "", + }, + Faqs: []*FAQ{}, + }, + I18NMap: map[string]*LocalizedContent{ + "fr": empty, + }, + } + + response, err := StaticProfile(profile)(context.Background(), appsdk.Context{}, &GetStoreProfileRequest{}) + if err != nil { + t.Fatalf("StaticProfile returned an error: %v", err) + } + if response != profile { + t.Fatal("StaticProfile did not preserve the extension profile") + } + if got := response.GetI18NMap()["fr"].GetIntro().GetRecommendedFor(); got != " " { + t.Fatalf("expected whitespace-only fallback marker to be preserved, got %q", got) + } + if got := response.GetRoot().GetIntro().GetHelpsWith(); got != "Root help" { + t.Fatalf("expected root content to be preserved, got %q", got) + } +} diff --git a/go/internal/gen/channel/app/sdk/v1/extension.pb.go b/go/internal/gen/channel/app/sdk/v1/extension.pb.go index a23b7fc..66d0433 100644 --- a/go/internal/gen/channel/app/sdk/v1/extension.pb.go +++ b/go/internal/gen/channel/app/sdk/v1/extension.pb.go @@ -7362,6 +7362,7 @@ type StoreGetProfileOutput struct { state protoimpl.MessageState `protogen:"open.v1"` RelatedAppIds []string `protobuf:"bytes,1,rep,name=related_app_ids,json=relatedAppIds,proto3" json:"related_app_ids,omitempty"` I18NMap map[string]*StoreProfileLocalizedContent `protobuf:"bytes,2,rep,name=i18n_map,json=i18nMap,proto3" json:"i18n_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Root *StoreProfileLocalizedContent `protobuf:"bytes,3,opt,name=root,proto3" json:"root,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -7410,6 +7411,13 @@ func (x *StoreGetProfileOutput) GetI18NMap() map[string]*StoreProfileLocalizedCo return nil } +func (x *StoreGetProfileOutput) GetRoot() *StoreProfileLocalizedContent { + if x != nil { + return x.Root + } + return nil +} + type NotebookCell struct { state protoimpl.MessageState `protogen:"open.v1"` CellKey string `protobuf:"bytes,1,opt,name=cell_key,json=cellKey,proto3" json:"cell_key,omitempty"` @@ -16092,10 +16100,11 @@ const file_channel_app_sdk_v1_extension_proto_rawDesc = "" + "\x06images\x18\x01 \x03(\v2%.channel.app.sdk.v1.StoreProfileImageR\x06images\x12;\n" + "\x05intro\x18\x02 \x01(\v2%.channel.app.sdk.v1.StoreProfileIntroR\x05intro\x120\n" + "\x04faqs\x18\x03 \x03(\v2\x1c.channel.app.sdk.v1.StoreFaqR\x04faqs\"\x16\n" + - "\x14StoreGetProfileInput\"\x80\x02\n" + + "\x14StoreGetProfileInput\"\xc6\x02\n" + "\x15StoreGetProfileOutput\x12&\n" + "\x0frelated_app_ids\x18\x01 \x03(\tR\rrelatedAppIds\x12Q\n" + - "\bi18n_map\x18\x02 \x03(\v26.channel.app.sdk.v1.StoreGetProfileOutput.I18nMapEntryR\ai18nMap\x1al\n" + + "\bi18n_map\x18\x02 \x03(\v26.channel.app.sdk.v1.StoreGetProfileOutput.I18nMapEntryR\ai18nMap\x12D\n" + + "\x04root\x18\x03 \x01(\v20.channel.app.sdk.v1.StoreProfileLocalizedContentR\x04root\x1al\n" + "\fI18nMapEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12F\n" + "\x05value\x18\x02 \x01(\v20.channel.app.sdk.v1.StoreProfileLocalizedContentR\x05value:\x028\x01\"\xc7\x01\n" + @@ -17143,175 +17152,176 @@ var file_channel_app_sdk_v1_extension_proto_depIdxs = []int32{ 98, // 100: channel.app.sdk.v1.StoreProfileLocalizedContent.intro:type_name -> channel.app.sdk.v1.StoreProfileIntro 99, // 101: channel.app.sdk.v1.StoreProfileLocalizedContent.faqs:type_name -> channel.app.sdk.v1.StoreFaq 247, // 102: channel.app.sdk.v1.StoreGetProfileOutput.i18n_map:type_name -> channel.app.sdk.v1.StoreGetProfileOutput.I18nMapEntry - 250, // 103: channel.app.sdk.v1.NotebookCell.definition:type_name -> google.protobuf.Struct - 250, // 104: channel.app.sdk.v1.NotebookCell.presentation:type_name -> google.protobuf.Struct - 104, // 105: channel.app.sdk.v1.NotebookLayoutRow.columns:type_name -> channel.app.sdk.v1.NotebookLayoutColumn - 105, // 106: channel.app.sdk.v1.NotebookTab.layout:type_name -> channel.app.sdk.v1.NotebookLayoutRow - 103, // 107: channel.app.sdk.v1.NotebookPayload.cells:type_name -> channel.app.sdk.v1.NotebookCell - 106, // 108: channel.app.sdk.v1.NotebookPayload.tabs:type_name -> channel.app.sdk.v1.NotebookTab - 107, // 109: channel.app.sdk.v1.AppNotebook.notebook:type_name -> channel.app.sdk.v1.NotebookPayload - 108, // 110: channel.app.sdk.v1.NotebookGetNotebooksOutput.notebooks:type_name -> channel.app.sdk.v1.AppNotebook - 112, // 111: channel.app.sdk.v1.DataSourceTableDefinition.table:type_name -> channel.app.sdk.v1.DataSourceTable - 113, // 112: channel.app.sdk.v1.DataSourceTableDefinition.columns:type_name -> channel.app.sdk.v1.DataSourceColumn - 112, // 113: channel.app.sdk.v1.DataSourceTableListing.table:type_name -> channel.app.sdk.v1.DataSourceTable - 111, // 114: channel.app.sdk.v1.DataSourceListCatalogsOutput.catalogs:type_name -> channel.app.sdk.v1.DataSourceCatalog - 115, // 115: channel.app.sdk.v1.DataSourceListTablesOutput.tables:type_name -> channel.app.sdk.v1.DataSourceTableListing - 114, // 116: channel.app.sdk.v1.DataSourceDescribeTableOutput.definition:type_name -> channel.app.sdk.v1.DataSourceTableDefinition - 250, // 117: channel.app.sdk.v1.DataSourceDescribeTableOutput.sample:type_name -> google.protobuf.Struct - 126, // 118: channel.app.sdk.v1.OrderItem.claimability:type_name -> channel.app.sdk.v1.OrderClaimability - 128, // 119: channel.app.sdk.v1.Order.items:type_name -> channel.app.sdk.v1.OrderItem - 129, // 120: channel.app.sdk.v1.Order.payment:type_name -> channel.app.sdk.v1.OrderPayment - 130, // 121: channel.app.sdk.v1.Order.fulfillments:type_name -> channel.app.sdk.v1.OrderFulfillment - 122, // 122: channel.app.sdk.v1.Order.shipping_address:type_name -> channel.app.sdk.v1.OrderAddress - 127, // 123: channel.app.sdk.v1.Order.claims:type_name -> channel.app.sdk.v1.OrderClaim - 132, // 124: channel.app.sdk.v1.OrderFieldConfig.allowed_values:type_name -> channel.app.sdk.v1.OrderAllowedValue - 248, // 125: channel.app.sdk.v1.OrderOperationOptions.field_configs:type_name -> channel.app.sdk.v1.OrderOperationOptions.FieldConfigsEntry - 134, // 126: channel.app.sdk.v1.OrderAppCapabilities.get_orders_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 127: channel.app.sdk.v1.OrderAppCapabilities.cancel_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 128: channel.app.sdk.v1.OrderAppCapabilities.return_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 129: channel.app.sdk.v1.OrderAppCapabilities.exchange_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 130: channel.app.sdk.v1.OrderAppCapabilities.change_address_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 131, // 131: channel.app.sdk.v1.OrderGetOrdersOutput.orders:type_name -> channel.app.sdk.v1.Order - 135, // 132: channel.app.sdk.v1.OrderGetAppConfigsOutput.app_capabilities:type_name -> channel.app.sdk.v1.OrderAppCapabilities - 140, // 133: channel.app.sdk.v1.OrderCancelOrderInput.cancel_items:type_name -> channel.app.sdk.v1.OrderClaimItem - 125, // 134: channel.app.sdk.v1.OrderCancelOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason - 123, // 135: channel.app.sdk.v1.OrderCancelOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount - 140, // 136: channel.app.sdk.v1.OrderReturnOrderInput.return_items:type_name -> channel.app.sdk.v1.OrderClaimItem - 125, // 137: channel.app.sdk.v1.OrderReturnOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason - 122, // 138: channel.app.sdk.v1.OrderReturnOrderInput.pickup_address:type_name -> channel.app.sdk.v1.OrderAddress - 123, // 139: channel.app.sdk.v1.OrderReturnOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount - 124, // 140: channel.app.sdk.v1.OrderReturnOrderInput.defect_info:type_name -> channel.app.sdk.v1.OrderDefectInfo - 140, // 141: channel.app.sdk.v1.OrderExchangeOrderInput.before_exchange_items:type_name -> channel.app.sdk.v1.OrderClaimItem - 143, // 142: channel.app.sdk.v1.OrderExchangeOrderInput.after_exchange_items:type_name -> channel.app.sdk.v1.OrderExchangeItem - 125, // 143: channel.app.sdk.v1.OrderExchangeOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason - 122, // 144: channel.app.sdk.v1.OrderExchangeOrderInput.pickup_address:type_name -> channel.app.sdk.v1.OrderAddress - 123, // 145: channel.app.sdk.v1.OrderExchangeOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount - 124, // 146: channel.app.sdk.v1.OrderExchangeOrderInput.defect_info:type_name -> channel.app.sdk.v1.OrderDefectInfo - 140, // 147: channel.app.sdk.v1.OrderGetExchangeableItemsInput.items:type_name -> channel.app.sdk.v1.OrderClaimItem - 128, // 148: channel.app.sdk.v1.OrderGetExchangeableItemsOutput.items:type_name -> channel.app.sdk.v1.OrderItem - 122, // 149: channel.app.sdk.v1.OrderChangeShippingAddressInput.new_address:type_name -> channel.app.sdk.v1.OrderAddress - 126, // 150: channel.app.sdk.v1.CommerceOrderItem.claimability:type_name -> channel.app.sdk.v1.OrderClaimability - 182, // 151: channel.app.sdk.v1.CommerceOrder.buyer:type_name -> channel.app.sdk.v1.Buyer - 150, // 152: channel.app.sdk.v1.CommerceOrder.items:type_name -> channel.app.sdk.v1.CommerceOrderItem - 129, // 153: channel.app.sdk.v1.CommerceOrder.payment:type_name -> channel.app.sdk.v1.OrderPayment - 130, // 154: channel.app.sdk.v1.CommerceOrder.fulfillments:type_name -> channel.app.sdk.v1.OrderFulfillment - 122, // 155: channel.app.sdk.v1.CommerceOrder.shipping_address:type_name -> channel.app.sdk.v1.OrderAddress - 127, // 156: channel.app.sdk.v1.CommerceOrder.claims:type_name -> channel.app.sdk.v1.OrderClaim - 149, // 157: channel.app.sdk.v1.CommerceGetOrdersInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier - 251, // 158: channel.app.sdk.v1.CommerceGetOrdersInput.search_filter:type_name -> google.protobuf.Value - 151, // 159: channel.app.sdk.v1.CommerceGetOrdersOutput.orders:type_name -> channel.app.sdk.v1.CommerceOrder - 134, // 160: channel.app.sdk.v1.CommerceAppCapabilities.get_orders_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 161: channel.app.sdk.v1.CommerceAppCapabilities.request_cancel_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 162: channel.app.sdk.v1.CommerceAppCapabilities.request_return_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 163: channel.app.sdk.v1.CommerceAppCapabilities.accept_return_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 164: channel.app.sdk.v1.CommerceAppCapabilities.request_exchange_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 134, // 165: channel.app.sdk.v1.CommerceAppCapabilities.change_shipping_address_options:type_name -> channel.app.sdk.v1.OrderOperationOptions - 154, // 166: channel.app.sdk.v1.CommerceGetAppConfigsOutput.app_capabilities:type_name -> channel.app.sdk.v1.CommerceAppCapabilities - 157, // 167: channel.app.sdk.v1.CommerceActionResult.result:type_name -> channel.app.sdk.v1.CommerceResultBody - 149, // 168: channel.app.sdk.v1.CommerceCancelOrderInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier - 140, // 169: channel.app.sdk.v1.CommerceCancelOrderInput.cancel_items:type_name -> channel.app.sdk.v1.OrderClaimItem - 125, // 170: channel.app.sdk.v1.CommerceCancelOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason - 123, // 171: channel.app.sdk.v1.CommerceCancelOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount - 149, // 172: channel.app.sdk.v1.CommerceReturnOrderInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier - 140, // 173: channel.app.sdk.v1.CommerceReturnOrderInput.return_items:type_name -> channel.app.sdk.v1.OrderClaimItem - 125, // 174: channel.app.sdk.v1.CommerceReturnOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason - 122, // 175: channel.app.sdk.v1.CommerceReturnOrderInput.pickup_address:type_name -> channel.app.sdk.v1.OrderAddress - 123, // 176: channel.app.sdk.v1.CommerceReturnOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount - 124, // 177: channel.app.sdk.v1.CommerceReturnOrderInput.defect_info:type_name -> channel.app.sdk.v1.OrderDefectInfo - 149, // 178: channel.app.sdk.v1.CommerceAcceptReturnOrderInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier - 140, // 179: channel.app.sdk.v1.CommerceAcceptReturnOrderInput.return_items:type_name -> channel.app.sdk.v1.OrderClaimItem - 125, // 180: channel.app.sdk.v1.CommerceAcceptReturnOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason - 123, // 181: channel.app.sdk.v1.CommerceAcceptReturnOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount - 149, // 182: channel.app.sdk.v1.CommerceExchangeOrderInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier - 140, // 183: channel.app.sdk.v1.CommerceExchangeOrderInput.before_exchange_items:type_name -> channel.app.sdk.v1.OrderClaimItem - 143, // 184: channel.app.sdk.v1.CommerceExchangeOrderInput.after_exchange_items:type_name -> channel.app.sdk.v1.OrderExchangeItem - 125, // 185: channel.app.sdk.v1.CommerceExchangeOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason - 122, // 186: channel.app.sdk.v1.CommerceExchangeOrderInput.pickup_address:type_name -> channel.app.sdk.v1.OrderAddress - 123, // 187: channel.app.sdk.v1.CommerceExchangeOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount - 124, // 188: channel.app.sdk.v1.CommerceExchangeOrderInput.defect_info:type_name -> channel.app.sdk.v1.OrderDefectInfo - 149, // 189: channel.app.sdk.v1.CommerceGetExchangeableItemsInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier - 140, // 190: channel.app.sdk.v1.CommerceGetExchangeableItemsInput.items:type_name -> channel.app.sdk.v1.OrderClaimItem - 150, // 191: channel.app.sdk.v1.CommerceGetExchangeableItemsOutput.items:type_name -> channel.app.sdk.v1.CommerceOrderItem - 149, // 192: channel.app.sdk.v1.CommerceChangeShippingAddressInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier - 122, // 193: channel.app.sdk.v1.CommerceChangeShippingAddressInput.new_address:type_name -> channel.app.sdk.v1.OrderAddress - 166, // 194: channel.app.sdk.v1.WmsOrderItem.shipping_info:type_name -> channel.app.sdk.v1.WmsShippingInfo - 168, // 195: channel.app.sdk.v1.WmsOrder.items:type_name -> channel.app.sdk.v1.WmsOrderItem - 167, // 196: channel.app.sdk.v1.WmsOrder.deliveries:type_name -> channel.app.sdk.v1.WmsDelivery - 169, // 197: channel.app.sdk.v1.WmsGetOrdersResult.orders:type_name -> channel.app.sdk.v1.WmsOrder - 169, // 198: channel.app.sdk.v1.WmsGetOrderResult.order:type_name -> channel.app.sdk.v1.WmsOrder - 251, // 199: channel.app.sdk.v1.WmsGetShopIDResult.shop_id:type_name -> google.protobuf.Value - 182, // 200: channel.app.sdk.v1.WmsOrderV2.buyer:type_name -> channel.app.sdk.v1.Buyer - 184, // 201: channel.app.sdk.v1.WmsOrderV2.items:type_name -> channel.app.sdk.v1.WmsOrderItemV2 - 183, // 202: channel.app.sdk.v1.WmsOrderV2.deliveries:type_name -> channel.app.sdk.v1.WmsDeliveryV2 - 186, // 203: channel.app.sdk.v1.WmsOrderGetOrdersRequest.identifier:type_name -> channel.app.sdk.v1.WmsIdentifier - 251, // 204: channel.app.sdk.v1.WmsOrderGetOrdersRequest.search_filter:type_name -> google.protobuf.Value - 185, // 205: channel.app.sdk.v1.WmsOrderGetOrdersResult.orders:type_name -> channel.app.sdk.v1.WmsOrderV2 - 186, // 206: channel.app.sdk.v1.WmsOrderActionRequest.identifier:type_name -> channel.app.sdk.v1.WmsIdentifier - 186, // 207: channel.app.sdk.v1.WmsOrderChangeShippingAddressInput.identifier:type_name -> channel.app.sdk.v1.WmsIdentifier - 122, // 208: channel.app.sdk.v1.WmsOrderChangeShippingAddressInput.new_address:type_name -> channel.app.sdk.v1.OrderAddress - 191, // 209: channel.app.sdk.v1.WmsOrderActionResult.result:type_name -> channel.app.sdk.v1.WmsOrderResultBody - 132, // 210: channel.app.sdk.v1.WmsFieldConfig.allowed_values:type_name -> channel.app.sdk.v1.OrderAllowedValue - 249, // 211: channel.app.sdk.v1.WmsOperationOptions.field_configs:type_name -> channel.app.sdk.v1.WmsOperationOptions.FieldConfigsEntry - 194, // 212: channel.app.sdk.v1.WmsAppCapabilities.get_orders_options:type_name -> channel.app.sdk.v1.WmsOperationOptions - 194, // 213: channel.app.sdk.v1.WmsAppCapabilities.request_cancel_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions - 194, // 214: channel.app.sdk.v1.WmsAppCapabilities.restore_canceled_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions - 194, // 215: channel.app.sdk.v1.WmsAppCapabilities.request_return_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions - 194, // 216: channel.app.sdk.v1.WmsAppCapabilities.restore_returned_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions - 194, // 217: channel.app.sdk.v1.WmsAppCapabilities.request_exchange_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions - 194, // 218: channel.app.sdk.v1.WmsAppCapabilities.restore_exchanged_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions - 194, // 219: channel.app.sdk.v1.WmsAppCapabilities.change_shipping_address_options:type_name -> channel.app.sdk.v1.WmsOperationOptions - 195, // 220: channel.app.sdk.v1.WmsGetAppConfigsOutput.app_capabilities:type_name -> channel.app.sdk.v1.WmsAppCapabilities - 252, // 221: channel.app.sdk.v1.MessagingOnMediumMessageCreatedInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat - 253, // 222: channel.app.sdk.v1.MessagingOnMediumMessageCreatedInput.message:type_name -> channel.app.sdk.v1.ChannelMessage - 199, // 223: channel.app.sdk.v1.MessagingOnMediumMessageCreatedOutput.send_result:type_name -> channel.app.sdk.v1.MessagingSendResult - 252, // 224: channel.app.sdk.v1.MessagingInboxOnMediumUserChatClosedInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat - 252, // 225: channel.app.sdk.v1.MessagingInboxGetWritingTypesInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat - 254, // 226: channel.app.sdk.v1.MessagingInboxGetWritingTypesOutput.writing_type_map:type_name -> channel.app.sdk.v1.WritingTypeMap - 255, // 227: channel.app.sdk.v1.MessagingInboxGetCustomEditorWamInput.user:type_name -> channel.app.sdk.v1.ChannelUser - 252, // 228: channel.app.sdk.v1.MessagingInboxGetCustomEditorWamInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat - 253, // 229: channel.app.sdk.v1.MessagingInboxGetCustomEditorWamInput.message:type_name -> channel.app.sdk.v1.ChannelMessage - 255, // 230: channel.app.sdk.v1.MessagingInboxGetMediumTopicSelectorWamInput.user:type_name -> channel.app.sdk.v1.ChannelUser - 252, // 231: channel.app.sdk.v1.MessagingInboxGetMediumMessageErrorReasonInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat - 253, // 232: channel.app.sdk.v1.MessagingInboxGetMediumMessageErrorReasonInput.message:type_name -> channel.app.sdk.v1.ChannelMessage - 254, // 233: channel.app.sdk.v1.MessagingPrebuiltGetWritingTypesOutput.writing_type_map:type_name -> channel.app.sdk.v1.WritingTypeMap - 256, // 234: channel.app.sdk.v1.MessagingPrebuiltValidateEntityInput.message:type_name -> channel.app.sdk.v1.PrebuiltMessage - 257, // 235: channel.app.sdk.v1.MessagingPrebuiltValidateEntityOutput.reasons:type_name -> channel.app.sdk.v1.UnavailableReason - 256, // 236: channel.app.sdk.v1.MessagingPrebuiltGetCustomEditorWamInput.message:type_name -> channel.app.sdk.v1.PrebuiltMessage - 250, // 237: channel.app.sdk.v1.MessagingPrebuiltGetCustomEditorWamInput.trigger_event_name_i18n_map:type_name -> google.protobuf.Struct - 255, // 238: channel.app.sdk.v1.MessagingPrebuiltBuildMediumTopicsInput.user:type_name -> channel.app.sdk.v1.ChannelUser - 258, // 239: channel.app.sdk.v1.MessagingPrebuiltBuildMediumTopicsOutput.medium_profile:type_name -> channel.app.sdk.v1.MediumProfile - 250, // 240: channel.app.sdk.v1.MessagingDefaultOptions.campaign_user_query:type_name -> google.protobuf.Struct - 250, // 241: channel.app.sdk.v1.MessagingDefaultOptions.one_time_msg_user_query:type_name -> google.protobuf.Struct - 218, // 242: channel.app.sdk.v1.MessagingPrebuiltGetDefaultOptionsOutput.default_options:type_name -> channel.app.sdk.v1.MessagingDefaultOptions - 250, // 243: channel.app.sdk.v1.AlfTaskWorkflowNode.config:type_name -> google.protobuf.Struct - 220, // 244: channel.app.sdk.v1.AlfTaskPredefinedTask.memory_schema:type_name -> channel.app.sdk.v1.AlfTaskMemoryDefinition - 221, // 245: channel.app.sdk.v1.AlfTaskPredefinedTask.nodes:type_name -> channel.app.sdk.v1.AlfTaskWorkflowNode - 222, // 246: channel.app.sdk.v1.AlfTaskGetTasksOutput.predefined_tasks:type_name -> channel.app.sdk.v1.AlfTaskPredefinedTask - 10, // 247: channel.app.sdk.v1.ConfigChoice.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 248: channel.app.sdk.v1.ConfigInlineLink.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 249: channel.app.sdk.v1.ConfigValidationNotice.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 250: channel.app.sdk.v1.ConfigOverview.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 251: channel.app.sdk.v1.ConfigDefaultSelector.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 252: channel.app.sdk.v1.ConfigSettings.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 253: channel.app.sdk.v1.ConfigField.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 254: channel.app.sdk.v1.ConfigBlock.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 255: channel.app.sdk.v1.ConfigGetConfigSchemaOutput.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 10, // 256: channel.app.sdk.v1.ConfigValidationError.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText - 35, // 257: channel.app.sdk.v1.OAuthProvider.I18nMapEntry.value:type_name -> channel.app.sdk.v1.OAuthProviderLocalizedText - 57, // 258: channel.app.sdk.v1.CommandChoice.NameDescI18nMapEntry.value:type_name -> channel.app.sdk.v1.CommandNameDescI18n - 58, // 259: channel.app.sdk.v1.CommandParamDefinition.NameDescI18nMapEntry.value:type_name -> channel.app.sdk.v1.CommandParamDefI18n - 56, // 260: channel.app.sdk.v1.CommandConfig.ButtonNameI18nMapEntry.value:type_name -> channel.app.sdk.v1.CommandNameI18n - 57, // 261: channel.app.sdk.v1.CommandConfig.NameDescI18nMapEntry.value:type_name -> channel.app.sdk.v1.CommandNameDescI18n - 70, // 262: channel.app.sdk.v1.WidgetConfig.DefaultNameDescI18nMapEntry.value:type_name -> channel.app.sdk.v1.WidgetNameDescI18n - 76, // 263: channel.app.sdk.v1.CustomTabConfig.NameI18nMapEntry.value:type_name -> channel.app.sdk.v1.CustomTabNameI18n - 100, // 264: channel.app.sdk.v1.StoreGetProfileOutput.I18nMapEntry.value:type_name -> channel.app.sdk.v1.StoreProfileLocalizedContent - 133, // 265: channel.app.sdk.v1.OrderOperationOptions.FieldConfigsEntry.value:type_name -> channel.app.sdk.v1.OrderFieldConfig - 193, // 266: channel.app.sdk.v1.WmsOperationOptions.FieldConfigsEntry.value:type_name -> channel.app.sdk.v1.WmsFieldConfig - 267, // [267:267] is the sub-list for method output_type - 267, // [267:267] is the sub-list for method input_type - 267, // [267:267] is the sub-list for extension type_name - 267, // [267:267] is the sub-list for extension extendee - 0, // [0:267] is the sub-list for field type_name + 100, // 103: channel.app.sdk.v1.StoreGetProfileOutput.root:type_name -> channel.app.sdk.v1.StoreProfileLocalizedContent + 250, // 104: channel.app.sdk.v1.NotebookCell.definition:type_name -> google.protobuf.Struct + 250, // 105: channel.app.sdk.v1.NotebookCell.presentation:type_name -> google.protobuf.Struct + 104, // 106: channel.app.sdk.v1.NotebookLayoutRow.columns:type_name -> channel.app.sdk.v1.NotebookLayoutColumn + 105, // 107: channel.app.sdk.v1.NotebookTab.layout:type_name -> channel.app.sdk.v1.NotebookLayoutRow + 103, // 108: channel.app.sdk.v1.NotebookPayload.cells:type_name -> channel.app.sdk.v1.NotebookCell + 106, // 109: channel.app.sdk.v1.NotebookPayload.tabs:type_name -> channel.app.sdk.v1.NotebookTab + 107, // 110: channel.app.sdk.v1.AppNotebook.notebook:type_name -> channel.app.sdk.v1.NotebookPayload + 108, // 111: channel.app.sdk.v1.NotebookGetNotebooksOutput.notebooks:type_name -> channel.app.sdk.v1.AppNotebook + 112, // 112: channel.app.sdk.v1.DataSourceTableDefinition.table:type_name -> channel.app.sdk.v1.DataSourceTable + 113, // 113: channel.app.sdk.v1.DataSourceTableDefinition.columns:type_name -> channel.app.sdk.v1.DataSourceColumn + 112, // 114: channel.app.sdk.v1.DataSourceTableListing.table:type_name -> channel.app.sdk.v1.DataSourceTable + 111, // 115: channel.app.sdk.v1.DataSourceListCatalogsOutput.catalogs:type_name -> channel.app.sdk.v1.DataSourceCatalog + 115, // 116: channel.app.sdk.v1.DataSourceListTablesOutput.tables:type_name -> channel.app.sdk.v1.DataSourceTableListing + 114, // 117: channel.app.sdk.v1.DataSourceDescribeTableOutput.definition:type_name -> channel.app.sdk.v1.DataSourceTableDefinition + 250, // 118: channel.app.sdk.v1.DataSourceDescribeTableOutput.sample:type_name -> google.protobuf.Struct + 126, // 119: channel.app.sdk.v1.OrderItem.claimability:type_name -> channel.app.sdk.v1.OrderClaimability + 128, // 120: channel.app.sdk.v1.Order.items:type_name -> channel.app.sdk.v1.OrderItem + 129, // 121: channel.app.sdk.v1.Order.payment:type_name -> channel.app.sdk.v1.OrderPayment + 130, // 122: channel.app.sdk.v1.Order.fulfillments:type_name -> channel.app.sdk.v1.OrderFulfillment + 122, // 123: channel.app.sdk.v1.Order.shipping_address:type_name -> channel.app.sdk.v1.OrderAddress + 127, // 124: channel.app.sdk.v1.Order.claims:type_name -> channel.app.sdk.v1.OrderClaim + 132, // 125: channel.app.sdk.v1.OrderFieldConfig.allowed_values:type_name -> channel.app.sdk.v1.OrderAllowedValue + 248, // 126: channel.app.sdk.v1.OrderOperationOptions.field_configs:type_name -> channel.app.sdk.v1.OrderOperationOptions.FieldConfigsEntry + 134, // 127: channel.app.sdk.v1.OrderAppCapabilities.get_orders_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 128: channel.app.sdk.v1.OrderAppCapabilities.cancel_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 129: channel.app.sdk.v1.OrderAppCapabilities.return_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 130: channel.app.sdk.v1.OrderAppCapabilities.exchange_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 131: channel.app.sdk.v1.OrderAppCapabilities.change_address_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 131, // 132: channel.app.sdk.v1.OrderGetOrdersOutput.orders:type_name -> channel.app.sdk.v1.Order + 135, // 133: channel.app.sdk.v1.OrderGetAppConfigsOutput.app_capabilities:type_name -> channel.app.sdk.v1.OrderAppCapabilities + 140, // 134: channel.app.sdk.v1.OrderCancelOrderInput.cancel_items:type_name -> channel.app.sdk.v1.OrderClaimItem + 125, // 135: channel.app.sdk.v1.OrderCancelOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason + 123, // 136: channel.app.sdk.v1.OrderCancelOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount + 140, // 137: channel.app.sdk.v1.OrderReturnOrderInput.return_items:type_name -> channel.app.sdk.v1.OrderClaimItem + 125, // 138: channel.app.sdk.v1.OrderReturnOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason + 122, // 139: channel.app.sdk.v1.OrderReturnOrderInput.pickup_address:type_name -> channel.app.sdk.v1.OrderAddress + 123, // 140: channel.app.sdk.v1.OrderReturnOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount + 124, // 141: channel.app.sdk.v1.OrderReturnOrderInput.defect_info:type_name -> channel.app.sdk.v1.OrderDefectInfo + 140, // 142: channel.app.sdk.v1.OrderExchangeOrderInput.before_exchange_items:type_name -> channel.app.sdk.v1.OrderClaimItem + 143, // 143: channel.app.sdk.v1.OrderExchangeOrderInput.after_exchange_items:type_name -> channel.app.sdk.v1.OrderExchangeItem + 125, // 144: channel.app.sdk.v1.OrderExchangeOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason + 122, // 145: channel.app.sdk.v1.OrderExchangeOrderInput.pickup_address:type_name -> channel.app.sdk.v1.OrderAddress + 123, // 146: channel.app.sdk.v1.OrderExchangeOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount + 124, // 147: channel.app.sdk.v1.OrderExchangeOrderInput.defect_info:type_name -> channel.app.sdk.v1.OrderDefectInfo + 140, // 148: channel.app.sdk.v1.OrderGetExchangeableItemsInput.items:type_name -> channel.app.sdk.v1.OrderClaimItem + 128, // 149: channel.app.sdk.v1.OrderGetExchangeableItemsOutput.items:type_name -> channel.app.sdk.v1.OrderItem + 122, // 150: channel.app.sdk.v1.OrderChangeShippingAddressInput.new_address:type_name -> channel.app.sdk.v1.OrderAddress + 126, // 151: channel.app.sdk.v1.CommerceOrderItem.claimability:type_name -> channel.app.sdk.v1.OrderClaimability + 182, // 152: channel.app.sdk.v1.CommerceOrder.buyer:type_name -> channel.app.sdk.v1.Buyer + 150, // 153: channel.app.sdk.v1.CommerceOrder.items:type_name -> channel.app.sdk.v1.CommerceOrderItem + 129, // 154: channel.app.sdk.v1.CommerceOrder.payment:type_name -> channel.app.sdk.v1.OrderPayment + 130, // 155: channel.app.sdk.v1.CommerceOrder.fulfillments:type_name -> channel.app.sdk.v1.OrderFulfillment + 122, // 156: channel.app.sdk.v1.CommerceOrder.shipping_address:type_name -> channel.app.sdk.v1.OrderAddress + 127, // 157: channel.app.sdk.v1.CommerceOrder.claims:type_name -> channel.app.sdk.v1.OrderClaim + 149, // 158: channel.app.sdk.v1.CommerceGetOrdersInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier + 251, // 159: channel.app.sdk.v1.CommerceGetOrdersInput.search_filter:type_name -> google.protobuf.Value + 151, // 160: channel.app.sdk.v1.CommerceGetOrdersOutput.orders:type_name -> channel.app.sdk.v1.CommerceOrder + 134, // 161: channel.app.sdk.v1.CommerceAppCapabilities.get_orders_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 162: channel.app.sdk.v1.CommerceAppCapabilities.request_cancel_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 163: channel.app.sdk.v1.CommerceAppCapabilities.request_return_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 164: channel.app.sdk.v1.CommerceAppCapabilities.accept_return_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 165: channel.app.sdk.v1.CommerceAppCapabilities.request_exchange_order_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 134, // 166: channel.app.sdk.v1.CommerceAppCapabilities.change_shipping_address_options:type_name -> channel.app.sdk.v1.OrderOperationOptions + 154, // 167: channel.app.sdk.v1.CommerceGetAppConfigsOutput.app_capabilities:type_name -> channel.app.sdk.v1.CommerceAppCapabilities + 157, // 168: channel.app.sdk.v1.CommerceActionResult.result:type_name -> channel.app.sdk.v1.CommerceResultBody + 149, // 169: channel.app.sdk.v1.CommerceCancelOrderInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier + 140, // 170: channel.app.sdk.v1.CommerceCancelOrderInput.cancel_items:type_name -> channel.app.sdk.v1.OrderClaimItem + 125, // 171: channel.app.sdk.v1.CommerceCancelOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason + 123, // 172: channel.app.sdk.v1.CommerceCancelOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount + 149, // 173: channel.app.sdk.v1.CommerceReturnOrderInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier + 140, // 174: channel.app.sdk.v1.CommerceReturnOrderInput.return_items:type_name -> channel.app.sdk.v1.OrderClaimItem + 125, // 175: channel.app.sdk.v1.CommerceReturnOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason + 122, // 176: channel.app.sdk.v1.CommerceReturnOrderInput.pickup_address:type_name -> channel.app.sdk.v1.OrderAddress + 123, // 177: channel.app.sdk.v1.CommerceReturnOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount + 124, // 178: channel.app.sdk.v1.CommerceReturnOrderInput.defect_info:type_name -> channel.app.sdk.v1.OrderDefectInfo + 149, // 179: channel.app.sdk.v1.CommerceAcceptReturnOrderInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier + 140, // 180: channel.app.sdk.v1.CommerceAcceptReturnOrderInput.return_items:type_name -> channel.app.sdk.v1.OrderClaimItem + 125, // 181: channel.app.sdk.v1.CommerceAcceptReturnOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason + 123, // 182: channel.app.sdk.v1.CommerceAcceptReturnOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount + 149, // 183: channel.app.sdk.v1.CommerceExchangeOrderInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier + 140, // 184: channel.app.sdk.v1.CommerceExchangeOrderInput.before_exchange_items:type_name -> channel.app.sdk.v1.OrderClaimItem + 143, // 185: channel.app.sdk.v1.CommerceExchangeOrderInput.after_exchange_items:type_name -> channel.app.sdk.v1.OrderExchangeItem + 125, // 186: channel.app.sdk.v1.CommerceExchangeOrderInput.reason:type_name -> channel.app.sdk.v1.OrderClaimReason + 122, // 187: channel.app.sdk.v1.CommerceExchangeOrderInput.pickup_address:type_name -> channel.app.sdk.v1.OrderAddress + 123, // 188: channel.app.sdk.v1.CommerceExchangeOrderInput.refund_bank_account:type_name -> channel.app.sdk.v1.OrderBankAccount + 124, // 189: channel.app.sdk.v1.CommerceExchangeOrderInput.defect_info:type_name -> channel.app.sdk.v1.OrderDefectInfo + 149, // 190: channel.app.sdk.v1.CommerceGetExchangeableItemsInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier + 140, // 191: channel.app.sdk.v1.CommerceGetExchangeableItemsInput.items:type_name -> channel.app.sdk.v1.OrderClaimItem + 150, // 192: channel.app.sdk.v1.CommerceGetExchangeableItemsOutput.items:type_name -> channel.app.sdk.v1.CommerceOrderItem + 149, // 193: channel.app.sdk.v1.CommerceChangeShippingAddressInput.identifier:type_name -> channel.app.sdk.v1.CommerceIdentifier + 122, // 194: channel.app.sdk.v1.CommerceChangeShippingAddressInput.new_address:type_name -> channel.app.sdk.v1.OrderAddress + 166, // 195: channel.app.sdk.v1.WmsOrderItem.shipping_info:type_name -> channel.app.sdk.v1.WmsShippingInfo + 168, // 196: channel.app.sdk.v1.WmsOrder.items:type_name -> channel.app.sdk.v1.WmsOrderItem + 167, // 197: channel.app.sdk.v1.WmsOrder.deliveries:type_name -> channel.app.sdk.v1.WmsDelivery + 169, // 198: channel.app.sdk.v1.WmsGetOrdersResult.orders:type_name -> channel.app.sdk.v1.WmsOrder + 169, // 199: channel.app.sdk.v1.WmsGetOrderResult.order:type_name -> channel.app.sdk.v1.WmsOrder + 251, // 200: channel.app.sdk.v1.WmsGetShopIDResult.shop_id:type_name -> google.protobuf.Value + 182, // 201: channel.app.sdk.v1.WmsOrderV2.buyer:type_name -> channel.app.sdk.v1.Buyer + 184, // 202: channel.app.sdk.v1.WmsOrderV2.items:type_name -> channel.app.sdk.v1.WmsOrderItemV2 + 183, // 203: channel.app.sdk.v1.WmsOrderV2.deliveries:type_name -> channel.app.sdk.v1.WmsDeliveryV2 + 186, // 204: channel.app.sdk.v1.WmsOrderGetOrdersRequest.identifier:type_name -> channel.app.sdk.v1.WmsIdentifier + 251, // 205: channel.app.sdk.v1.WmsOrderGetOrdersRequest.search_filter:type_name -> google.protobuf.Value + 185, // 206: channel.app.sdk.v1.WmsOrderGetOrdersResult.orders:type_name -> channel.app.sdk.v1.WmsOrderV2 + 186, // 207: channel.app.sdk.v1.WmsOrderActionRequest.identifier:type_name -> channel.app.sdk.v1.WmsIdentifier + 186, // 208: channel.app.sdk.v1.WmsOrderChangeShippingAddressInput.identifier:type_name -> channel.app.sdk.v1.WmsIdentifier + 122, // 209: channel.app.sdk.v1.WmsOrderChangeShippingAddressInput.new_address:type_name -> channel.app.sdk.v1.OrderAddress + 191, // 210: channel.app.sdk.v1.WmsOrderActionResult.result:type_name -> channel.app.sdk.v1.WmsOrderResultBody + 132, // 211: channel.app.sdk.v1.WmsFieldConfig.allowed_values:type_name -> channel.app.sdk.v1.OrderAllowedValue + 249, // 212: channel.app.sdk.v1.WmsOperationOptions.field_configs:type_name -> channel.app.sdk.v1.WmsOperationOptions.FieldConfigsEntry + 194, // 213: channel.app.sdk.v1.WmsAppCapabilities.get_orders_options:type_name -> channel.app.sdk.v1.WmsOperationOptions + 194, // 214: channel.app.sdk.v1.WmsAppCapabilities.request_cancel_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions + 194, // 215: channel.app.sdk.v1.WmsAppCapabilities.restore_canceled_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions + 194, // 216: channel.app.sdk.v1.WmsAppCapabilities.request_return_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions + 194, // 217: channel.app.sdk.v1.WmsAppCapabilities.restore_returned_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions + 194, // 218: channel.app.sdk.v1.WmsAppCapabilities.request_exchange_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions + 194, // 219: channel.app.sdk.v1.WmsAppCapabilities.restore_exchanged_order_options:type_name -> channel.app.sdk.v1.WmsOperationOptions + 194, // 220: channel.app.sdk.v1.WmsAppCapabilities.change_shipping_address_options:type_name -> channel.app.sdk.v1.WmsOperationOptions + 195, // 221: channel.app.sdk.v1.WmsGetAppConfigsOutput.app_capabilities:type_name -> channel.app.sdk.v1.WmsAppCapabilities + 252, // 222: channel.app.sdk.v1.MessagingOnMediumMessageCreatedInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat + 253, // 223: channel.app.sdk.v1.MessagingOnMediumMessageCreatedInput.message:type_name -> channel.app.sdk.v1.ChannelMessage + 199, // 224: channel.app.sdk.v1.MessagingOnMediumMessageCreatedOutput.send_result:type_name -> channel.app.sdk.v1.MessagingSendResult + 252, // 225: channel.app.sdk.v1.MessagingInboxOnMediumUserChatClosedInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat + 252, // 226: channel.app.sdk.v1.MessagingInboxGetWritingTypesInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat + 254, // 227: channel.app.sdk.v1.MessagingInboxGetWritingTypesOutput.writing_type_map:type_name -> channel.app.sdk.v1.WritingTypeMap + 255, // 228: channel.app.sdk.v1.MessagingInboxGetCustomEditorWamInput.user:type_name -> channel.app.sdk.v1.ChannelUser + 252, // 229: channel.app.sdk.v1.MessagingInboxGetCustomEditorWamInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat + 253, // 230: channel.app.sdk.v1.MessagingInboxGetCustomEditorWamInput.message:type_name -> channel.app.sdk.v1.ChannelMessage + 255, // 231: channel.app.sdk.v1.MessagingInboxGetMediumTopicSelectorWamInput.user:type_name -> channel.app.sdk.v1.ChannelUser + 252, // 232: channel.app.sdk.v1.MessagingInboxGetMediumMessageErrorReasonInput.user_chat:type_name -> channel.app.sdk.v1.ChannelUserChat + 253, // 233: channel.app.sdk.v1.MessagingInboxGetMediumMessageErrorReasonInput.message:type_name -> channel.app.sdk.v1.ChannelMessage + 254, // 234: channel.app.sdk.v1.MessagingPrebuiltGetWritingTypesOutput.writing_type_map:type_name -> channel.app.sdk.v1.WritingTypeMap + 256, // 235: channel.app.sdk.v1.MessagingPrebuiltValidateEntityInput.message:type_name -> channel.app.sdk.v1.PrebuiltMessage + 257, // 236: channel.app.sdk.v1.MessagingPrebuiltValidateEntityOutput.reasons:type_name -> channel.app.sdk.v1.UnavailableReason + 256, // 237: channel.app.sdk.v1.MessagingPrebuiltGetCustomEditorWamInput.message:type_name -> channel.app.sdk.v1.PrebuiltMessage + 250, // 238: channel.app.sdk.v1.MessagingPrebuiltGetCustomEditorWamInput.trigger_event_name_i18n_map:type_name -> google.protobuf.Struct + 255, // 239: channel.app.sdk.v1.MessagingPrebuiltBuildMediumTopicsInput.user:type_name -> channel.app.sdk.v1.ChannelUser + 258, // 240: channel.app.sdk.v1.MessagingPrebuiltBuildMediumTopicsOutput.medium_profile:type_name -> channel.app.sdk.v1.MediumProfile + 250, // 241: channel.app.sdk.v1.MessagingDefaultOptions.campaign_user_query:type_name -> google.protobuf.Struct + 250, // 242: channel.app.sdk.v1.MessagingDefaultOptions.one_time_msg_user_query:type_name -> google.protobuf.Struct + 218, // 243: channel.app.sdk.v1.MessagingPrebuiltGetDefaultOptionsOutput.default_options:type_name -> channel.app.sdk.v1.MessagingDefaultOptions + 250, // 244: channel.app.sdk.v1.AlfTaskWorkflowNode.config:type_name -> google.protobuf.Struct + 220, // 245: channel.app.sdk.v1.AlfTaskPredefinedTask.memory_schema:type_name -> channel.app.sdk.v1.AlfTaskMemoryDefinition + 221, // 246: channel.app.sdk.v1.AlfTaskPredefinedTask.nodes:type_name -> channel.app.sdk.v1.AlfTaskWorkflowNode + 222, // 247: channel.app.sdk.v1.AlfTaskGetTasksOutput.predefined_tasks:type_name -> channel.app.sdk.v1.AlfTaskPredefinedTask + 10, // 248: channel.app.sdk.v1.ConfigChoice.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 249: channel.app.sdk.v1.ConfigInlineLink.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 250: channel.app.sdk.v1.ConfigValidationNotice.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 251: channel.app.sdk.v1.ConfigOverview.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 252: channel.app.sdk.v1.ConfigDefaultSelector.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 253: channel.app.sdk.v1.ConfigSettings.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 254: channel.app.sdk.v1.ConfigField.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 255: channel.app.sdk.v1.ConfigBlock.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 256: channel.app.sdk.v1.ConfigGetConfigSchemaOutput.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 10, // 257: channel.app.sdk.v1.ConfigValidationError.I18nMapEntry.value:type_name -> channel.app.sdk.v1.ConfigLocalizedText + 35, // 258: channel.app.sdk.v1.OAuthProvider.I18nMapEntry.value:type_name -> channel.app.sdk.v1.OAuthProviderLocalizedText + 57, // 259: channel.app.sdk.v1.CommandChoice.NameDescI18nMapEntry.value:type_name -> channel.app.sdk.v1.CommandNameDescI18n + 58, // 260: channel.app.sdk.v1.CommandParamDefinition.NameDescI18nMapEntry.value:type_name -> channel.app.sdk.v1.CommandParamDefI18n + 56, // 261: channel.app.sdk.v1.CommandConfig.ButtonNameI18nMapEntry.value:type_name -> channel.app.sdk.v1.CommandNameI18n + 57, // 262: channel.app.sdk.v1.CommandConfig.NameDescI18nMapEntry.value:type_name -> channel.app.sdk.v1.CommandNameDescI18n + 70, // 263: channel.app.sdk.v1.WidgetConfig.DefaultNameDescI18nMapEntry.value:type_name -> channel.app.sdk.v1.WidgetNameDescI18n + 76, // 264: channel.app.sdk.v1.CustomTabConfig.NameI18nMapEntry.value:type_name -> channel.app.sdk.v1.CustomTabNameI18n + 100, // 265: channel.app.sdk.v1.StoreGetProfileOutput.I18nMapEntry.value:type_name -> channel.app.sdk.v1.StoreProfileLocalizedContent + 133, // 266: channel.app.sdk.v1.OrderOperationOptions.FieldConfigsEntry.value:type_name -> channel.app.sdk.v1.OrderFieldConfig + 193, // 267: channel.app.sdk.v1.WmsOperationOptions.FieldConfigsEntry.value:type_name -> channel.app.sdk.v1.WmsFieldConfig + 268, // [268:268] is the sub-list for method output_type + 268, // [268:268] is the sub-list for method input_type + 268, // [268:268] is the sub-list for extension type_name + 268, // [268:268] is the sub-list for extension extendee + 0, // [0:268] is the sub-list for field type_name } func init() { file_channel_app_sdk_v1_extension_proto_init() } diff --git a/proto/channel/app/sdk/v1/extension.proto b/proto/channel/app/sdk/v1/extension.proto index 4c36593..21476c5 100644 --- a/proto/channel/app/sdk/v1/extension.proto +++ b/proto/channel/app/sdk/v1/extension.proto @@ -766,6 +766,7 @@ message StoreGetProfileInput {} message StoreGetProfileOutput { repeated string related_app_ids = 1; map i18n_map = 2; + StoreProfileLocalizedContent root = 3; } message NotebookCell { diff --git a/ts/.changeset/store-profile-arbitrary-locales.md b/ts/.changeset/store-profile-arbitrary-locales.md new file mode 100644 index 0000000..59c3e72 --- /dev/null +++ b/ts/.changeset/store-profile-arbitrary-locales.md @@ -0,0 +1,6 @@ +--- +"@channel.io/app-sdk-core": patch +"@channel.io/app-sdk-server": patch +--- + +Allow Store Profile metadata to include only authored locales and accept arbitrary non-empty Desk locale codes. diff --git a/ts/.changeset/store-profile-root.md b/ts/.changeset/store-profile-root.md new file mode 100644 index 0000000..b32b40a --- /dev/null +++ b/ts/.changeset/store-profile-root.md @@ -0,0 +1,6 @@ +--- +"@channel.io/app-sdk-core": patch +"@channel.io/app-sdk-server": patch +--- + +Allow Store Profile metadata to define optional root content separately from locale translations. diff --git a/ts/packages/core/src/__tests__/extensions/store.test.ts b/ts/packages/core/src/__tests__/extensions/store.test.ts index 420f964..7192e9c 100644 --- a/ts/packages/core/src/__tests__/extensions/store.test.ts +++ b/ts/packages/core/src/__tests__/extensions/store.test.ts @@ -33,6 +33,18 @@ describe("store extension schemas", () => { expect(parsed.relatedAppIds).toEqual(["appCafe24"]); expect(parsed.i18nMap.en.intro.helpsWith).toContain("shipping"); + expect(parsed.root).toBeUndefined(); + }); + + it("accepts optional root content without treating it as a locale", () => { + const parsed = StoreProfileMetadataSchema.parse({ + relatedAppIds: [], + root: localizedContent, + i18nMap: { en: localizedContent }, + }); + + expect(parsed.root?.intro.helpsWith).toContain("shipping"); + expect(Object.keys(parsed.i18nMap)).toEqual(["en"]); }); it("rejects the legacy runtime profile shapes", () => { @@ -45,13 +57,45 @@ describe("store extension schemas", () => { ).toThrow(); }); - it("requires all supported locales", () => { - expect(() => - StoreProfileMetadataSchema.parse({ - relatedAppIds: [], - i18nMap: { ko: localizedContent, en: localizedContent }, - }) - ).toThrow(); + it("accepts only the locales supplied by the app, including arbitrary Desk locales", () => { + const parsed = StoreProfileMetadataSchema.parse({ + relatedAppIds: [], + i18nMap: { fr: localizedContent, "zh-CN": localizedContent }, + }); + + expect(Object.keys(parsed.i18nMap)).toEqual(["fr", "zh-CN"]); + expect(parsed.i18nMap.fr.intro.helpsWith).toContain("shipping"); + }); + + it("rejects blank or whitespace-padded locale keys", () => { + for (const locale of ["", " fr", "fr "]) { + expect(() => + StoreProfileMetadataSchema.parse({ + relatedAppIds: [], + i18nMap: { [locale]: localizedContent }, + }) + ).toThrow(); + } + }); + + it("accepts empty extension values that allow Developer GUI fallbacks", () => { + const emptyLocalizedContent = { + images: [], + intro: { helpsWith: "", recommendedFor: " " }, + faqs: [], + }; + + const parsed = StoreProfileMetadataSchema.parse({ + relatedAppIds: [], + i18nMap: { + fr: emptyLocalizedContent, + }, + }); + + expect(parsed.relatedAppIds).toEqual([]); + expect(parsed.i18nMap.fr.images).toEqual([]); + expect(parsed.i18nMap.fr.intro.helpsWith).toBe(""); + expect(parsed.i18nMap.fr.intro.recommendedFor).toBe(" "); }); it("treats uploaded relative media keys as opaque values", () => { diff --git a/ts/packages/core/src/extensions/store.ts b/ts/packages/core/src/extensions/store.ts index d125640..5b8e2b2 100644 --- a/ts/packages/core/src/extensions/store.ts +++ b/ts/packages/core/src/extensions/store.ts @@ -14,6 +14,12 @@ type ProtoBacked = T & Proto; const StoreNonEmptyStringSchema = z.string().trim().min(1); const StoreMediaKeySchema = StoreNonEmptyStringSchema; +const StoreProfileLocaleSchema = z + .string() + .min(1) + .refine((locale) => locale.trim() === locale, { + message: "Store profile locale keys must not contain surrounding whitespace.", + }); export const StoreProfileImageSchema = z .object({ @@ -57,16 +63,16 @@ export type StoreProfileLocalizedContent = ProtoBacked< ProtoStoreProfileLocalizedContent >; +/** + * Metadata supplied by the extension source. Empty localized arrays and intro strings intentionally + * leave those fields available for an App Store Developer GUI fallback. Root content is optional + * and remains distinct from every authored locale. + */ export const StoreProfileMetadataSchema = z .object({ relatedAppIds: z.array(StoreNonEmptyStringSchema), - i18nMap: z - .object({ - ko: StoreProfileLocalizedContentSchema, - ja: StoreProfileLocalizedContentSchema, - en: StoreProfileLocalizedContentSchema, - }) - .strict(), + i18nMap: z.record(StoreProfileLocaleSchema, StoreProfileLocalizedContentSchema), + root: StoreProfileLocalizedContentSchema.optional(), }) .strict(); export type StoreProfileMetadata = ProtoBacked< diff --git a/ts/packages/core/src/gen/channel/app/sdk/v1/extension.ts b/ts/packages/core/src/gen/channel/app/sdk/v1/extension.ts index 67827c6..1599f43 100644 --- a/ts/packages/core/src/gen/channel/app/sdk/v1/extension.ts +++ b/ts/packages/core/src/gen/channel/app/sdk/v1/extension.ts @@ -884,6 +884,7 @@ export interface StoreGetProfileInput { export interface StoreGetProfileOutput { relatedAppIds?: string[] | undefined; i18nMap?: { [key: string]: StoreProfileLocalizedContent } | undefined; + root?: StoreProfileLocalizedContent | undefined; } export interface StoreGetProfileOutput_I18nMapEntry { diff --git a/ts/packages/core/src/gen/channel/app/sdk/v1/extension.zod.ts b/ts/packages/core/src/gen/channel/app/sdk/v1/extension.zod.ts index 85b931b..09d3584 100644 --- a/ts/packages/core/src/gen/channel/app/sdk/v1/extension.zod.ts +++ b/ts/packages/core/src/gen/channel/app/sdk/v1/extension.zod.ts @@ -861,6 +861,7 @@ export type StoreGetProfileInputProto = z.infer StoreProfileLocalizedContentProtoSchema)).optional(), + root: z.lazy(() => StoreProfileLocalizedContentProtoSchema).optional(), }) satisfies z.ZodType; export type StoreGetProfileOutputProto = z.infer;