web-launch-kit 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ko.md CHANGED
@@ -34,7 +34,7 @@ npm install web-launch-kit
34
34
  | `LaunchKit.filepicker(options?)` | `Promise<File[]>` | 파일 또는 디렉터리 선택 (File System Access API, input 폴백) |
35
35
  | `LaunchKit.setting(type?)` | `Promise<void>` | 지원되는 환경에서 시스템 설정 화면 열기 |
36
36
  | `LaunchKit.escapeWebview(options?)` | `Promise<void>` | URL(기본값은 현재 페이지)을 외부 브라우저에서 다시 열어 인앱 웹뷰에서 탈출 |
37
- | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` / `canEscapeWebview` getter와 비동기 `getTrackId` / `getProductId` |
37
+ | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` / `canEscapeWebview` getter와 비동기 `getTrackId(bundleId)` / `getProductId(packageFamilyName, language?, country?)` |
38
38
 
39
39
  `AppOpenedBy`는 `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"` 중 하나입니다.
40
40
 
@@ -147,6 +147,18 @@ flowchart TD
147
147
  M2 --> M3["Priority list:<br/>scheme ⭢ fallback<br/>⭢ app store ⭢ web store (by trackId)"]
148
148
  end
149
149
 
150
+ subgraph LOC["web store · createWebStoreURL"]
151
+ L1{"country given?"}
152
+ L1 -->|yes| L3["Play · MS Store: hl / gl parameters<br/>App Store: /country/ path + l parameter"]
153
+ L1 -->|no| L2["Derive from language:<br/>region subtag ⭢ Intl.Locale.maximize()<br/>⭢ omit (store geolocation)"]
154
+ L2 --> L3
155
+ end
156
+
157
+ A5 -.->|"web store"| L1
158
+ I3 -.->|"web store"| L1
159
+ W3 -.->|"web store"| L1
160
+ M3 -.->|"web store"| L1
161
+
150
162
  AND --> P
151
163
  IOS --> P
152
164
  WIN --> P
@@ -191,6 +203,8 @@ const openedBy = await LaunchKit.app({
191
203
  packageName: 'com.example.myapp',
192
204
  allowAppStore: true,
193
205
  allowWebStore: true,
206
+ language: 'ko', // 웹스토어 UI 언어
207
+ country: 'KR', // 생략하면 language 에서 유도됩니다 (region 서브태그 → Intl.Locale.maximize())
194
208
  },
195
209
  ios: {
196
210
  universal: 'https://example.com/profile/42',
@@ -206,7 +220,17 @@ console.log(openedBy) // "universal" | "scheme" | "intent" | "fallback" | "store
206
220
  플랫폼별 필드: 안드로이드는 `intent` / `scheme` / `packageName` / `fallback` /
207
221
  `allowIntent`(scheme ⇄ intent는 서로에게서 유도됩니다), iOS는 `universal` / `scheme` / `bundleId` / `trackId`, Windows는
208
222
  `scheme` / `packageFamilyName` / `productId`, macOS는 `scheme` / `bundleId` / `trackId`를
209
- 받습니다. 모두 `fallback`, `timeout`, `allowAppStore`, `allowWebStore`를 함께 받습니다.
223
+ 받습니다. 모두 `fallback`, `timeout`, `allowAppStore`, `allowWebStore`, `language`,
224
+ `country`를 함께 받습니다.
225
+
226
+ `language` / `country`는 **웹스토어** 후보에만 적용됩니다 — Google Play와 Microsoft
227
+ Store에서는 `hl` / `gl` 파라미터, App Store에서는 국가 경로 세그먼트와 `l` 파라미터로
228
+ 반영됩니다. `country`를 생략하면 `language`에서 유도합니다. region 서브태그가 우선하고
229
+ (`ko-KR` → `KR`), 없으면 런타임이 지원하는 환경에서 `Intl.Locale.maximize()`를
230
+ 사용합니다. 둘 다 실패하면 국가를 붙이지 않고 스토어의 자체 위치 추정에 맡깁니다 —
231
+ 잘못된 국가는 "이 지역에서 사용할 수 없음" 페이지로 이어질 수 있어 생략이 안전한
232
+ 기본값입니다. 네이티브 앱스토어 스킴(`market://`, `itms-apps://` 등)은 건드리지
233
+ 않습니다. OS가 기기 로케일로 알아서 렌더링합니다.
210
234
 
211
235
  `onAttempt`는 각 후보 시도 직전에 동기로 호출됩니다. 대기 UI를 여기서 그리세요 — 그러지
212
236
  않으면 타임아웃 동안 화면에 아무 변화가 없습니다.
@@ -353,7 +377,12 @@ Meta 계열 웹뷰(Facebook, Instagram) 안에서는 차단됩니다. http(s) UR
353
377
  - **`utils.getTrackId` / `getProductId`는 비동기**이고 1시간 캐시됩니다. 페이지 초기화 때
354
378
  한 번 호출해두면 `app()`이 id를 인라인으로 조회하지 않습니다. `app()`은 사용자 제스처 안에서
355
379
  실행돼야 하므로, id가 없으면 `await`으로 user activation을 잃는 대신 동기로 조회합니다.
356
- `trackId` / `productId`를 직접 주면 조회 자체가 생략됩니다.
380
+ `trackId` / `productId`를 직접 주면 조회 자체가 생략됩니다. `getProductId`는 선택적
381
+ `language` / `country`를 추가로 받아 Microsoft 카탈로그의 언어와 마켓을 고정합니다 —
382
+ 조회 성공 여부가 마켓에 따라 달라질 수 있으니 `app()`에 넘길 값과 동일하게 주어야
383
+ 프리워밍된 캐시가 같은 마켓을 커버합니다. 생략하면 웹스토어 country와 같은 방식으로
384
+ 유도됩니다: 명시 region 서브태그 → 런타임이 지원하면 `Intl.Locale.maximize()` →
385
+ `en-US` / `US`. `getTrackId`는 로케일이 필요 없습니다 — iTunes lookup은 마켓과 무관합니다.
357
386
  - **스토어 id 조회는 외부 API에 의존합니다**(iTunes Lookup, Microsoft display catalog).
358
387
  1시간 캐시되며 실패는 예외가 아니라 `undefined`로 resolve됩니다.
359
388
  - **인앱 브라우저는 후보 단위로 걸러집니다.** 실행이 차단된 웹뷰(WeChat, QQ, Qzone, Baidu,
package/README.md CHANGED
@@ -35,7 +35,7 @@ The bundle is self-contained (OS/locale detection is inlined) — no peer script
35
35
  | `LaunchKit.filepicker(options?)` | `Promise<File[]>` | Pick files or a directory (File System Access API, with input fallback) |
36
36
  | `LaunchKit.setting(type?)` | `Promise<void>` | Open a system-settings pane where supported |
37
37
  | `LaunchKit.escapeWebview(options?)` | `Promise<void>` | Reopen a URL (the current page by default) in the external browser, escaping an in-app webview |
38
- | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` / `canEscapeWebview` getters, plus async `getTrackId` / `getProductId` |
38
+ | `LaunchKit.utils` | object | `canOpenIntent` / `canOpenUniversal` / `canOpenSetting` / `canEscapeWebview` getters, plus async `getTrackId(bundleId)` / `getProductId(packageFamilyName, language?, country?)` |
39
39
 
40
40
  `AppOpenedBy` is one of: `"scheme"`, `"universal"`, `"intent"`, `"fallback"`, `"store"`.
41
41
 
@@ -149,6 +149,18 @@ flowchart TD
149
149
  M2 --> M3["Priority list:<br/>scheme ⭢ fallback<br/>⭢ app store ⭢ web store (by trackId)"]
150
150
  end
151
151
 
152
+ subgraph LOC["web store · createWebStoreURL"]
153
+ L1{"country given?"}
154
+ L1 -->|yes| L3["Play · MS Store: hl / gl parameters<br/>App Store: /country/ path + l parameter"]
155
+ L1 -->|no| L2["Derive from language:<br/>region subtag ⭢ Intl.Locale.maximize()<br/>⭢ omit (store geolocation)"]
156
+ L2 --> L3
157
+ end
158
+
159
+ A5 -.->|"web store"| L1
160
+ I3 -.->|"web store"| L1
161
+ W3 -.->|"web store"| L1
162
+ M3 -.->|"web store"| L1
163
+
152
164
  AND --> P
153
165
  IOS --> P
154
166
  WIN --> P
@@ -193,6 +205,8 @@ const openedBy = await LaunchKit.app({
193
205
  packageName: 'com.example.myapp',
194
206
  allowAppStore: true,
195
207
  allowWebStore: true,
208
+ language: 'ko', // web-store UI language
209
+ country: 'KR', // omit to derive from language (region subtag → Intl.Locale.maximize())
196
210
  },
197
211
  ios: {
198
212
  universal: 'https://example.com/profile/42',
@@ -209,7 +223,17 @@ Per-platform fields: Android accepts `intent` / `scheme` / `packageName` / `fall
209
223
  `allowIntent` (scheme ⇄ intent are derived from each other); iOS accepts `universal` / `scheme` /
210
224
  `bundleId` / `trackId`; Windows accepts `scheme` / `packageFamilyName` / `productId`;
211
225
  macOS accepts `scheme` / `bundleId` / `trackId`. All accept `fallback`, `timeout`,
212
- `allowAppStore`, `allowWebStore`.
226
+ `allowAppStore`, `allowWebStore`, `language`, `country`.
227
+
228
+ `language` / `country` localize the **web-store** candidate only — `hl` / `gl` on
229
+ Google Play and the Microsoft Store, a country path segment plus `l` on the App
230
+ Store. When `country` is omitted it is derived from `language`: a region subtag
231
+ wins (`ko-KR` → `KR`), then `Intl.Locale.maximize()` where the runtime supports
232
+ it. When neither yields a region, the country is simply dropped and the store
233
+ falls back to its own geolocation — a wrong country can land on an
234
+ "not available in your region" page, so omission is the safe default. Native
235
+ app-store schemes (`market://`, `itms-apps://`, …) are untouched; the OS renders
236
+ those in the device locale.
213
237
 
214
238
  `onAttempt` fires synchronously before each candidate, which is where you drive the waiting
215
239
  UI — there is otherwise nothing on screen for the length of the timeout.
@@ -360,6 +384,12 @@ the button on `utils.canEscapeWebview`.
360
384
  during page setup keeps `app()` from resolving the id inline. `app()` must run inside a
361
385
  user gesture, so when the id is missing it looks it up synchronously rather than awaiting
362
386
  and losing user activation. Passing `trackId` / `productId` directly skips it entirely.
387
+ `getProductId` also takes optional `language` / `country` to pin the Microsoft catalog
388
+ language and market — pass the same values you will give `app()`, since whether a lookup
389
+ succeeds can depend on the market and the pre-warmed cache should cover the same one.
390
+ When omitted, the market is derived the same way as the web-store country: explicit region
391
+ subtag first, then `Intl.Locale.maximize()` where the runtime supports it, then `en-US` / `US`.
392
+ `getTrackId` needs no locale — iTunes lookup is market-independent.
363
393
  - **Store-id lookups depend on remote APIs** (iTunes Lookup, Microsoft display catalog)
364
394
  and are cached for one hour; failures resolve to `undefined` rather than throwing.
365
395
  - **In-app browsers are gated per candidate.** Inside webviews that block launches
package/dist/index.d.ts CHANGED
@@ -32,6 +32,8 @@ declare interface AppInfo {
32
32
  timeout?: number;
33
33
  allowAppStore?: boolean;
34
34
  allowWebStore?: boolean;
35
+ language?: string;
36
+ country?: string;
35
37
  }
36
38
  declare interface AndroidAppInfo extends AppInfo {
37
39
  intent?: URLCandidate;
@@ -102,7 +104,7 @@ interface LaunchKitUtils {
102
104
  get canOpenSetting(): boolean;
103
105
  get canEscapeWebview(): boolean;
104
106
  getTrackId(bundleId: string): Promise<string | undefined>;
105
- getProductId(packageFamilyName: string): Promise<string | undefined>;
107
+ getProductId(packageFamilyName: string, language?: string, country?: string): Promise<string | undefined>;
106
108
  }
107
109
  interface LaunchKitInstance {
108
110
  readonly version: string;
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version$3 = "0.0.8";
5
+ var version$3 = "0.0.9";
6
6
  var packageJSON$3 = {
7
7
  version: version$3};
8
8
 
@@ -1284,7 +1284,7 @@ function createHiddenElement(tagName, focusable) {
1284
1284
  return element;
1285
1285
  }
1286
1286
 
1287
- var version = "0.0.5";
1287
+ var version = "0.0.6";
1288
1288
  var packageJSON = {
1289
1289
  version: version};
1290
1290
 
@@ -1413,6 +1413,27 @@ function expandTag(tag) {
1413
1413
  chain.push(segments.slice(0, i).join('-'));
1414
1414
  return freeze(chain);
1415
1415
  }
1416
+ function maximizeTag(tag) {
1417
+ var parsed = parseTag(tag);
1418
+ if (parsed === null)
1419
+ return null;
1420
+ if (parsed.script !== null && parsed.region !== null)
1421
+ return parsed;
1422
+ var IntlLocale = getIntlLocaleConstructor();
1423
+ if (typeof IntlLocale === 'undefined')
1424
+ return parsed;
1425
+ try {
1426
+ var intlLocale = new IntlLocale(parsed.baseName);
1427
+ if (typeof intlLocale.maximize === 'function') {
1428
+ var reparsed = parseTag(intlLocale.maximize().toString());
1429
+ if (reparsed !== null)
1430
+ return reparsed;
1431
+ }
1432
+ }
1433
+ catch (_) {
1434
+ }
1435
+ return parsed;
1436
+ }
1416
1437
  function toLookupKey(tag) {
1417
1438
  var parsed = parseTag(tag);
1418
1439
  return (parsed === null ? tag : parsed.baseName).toLowerCase();
@@ -1455,6 +1476,11 @@ function resolveDirection(primary) {
1455
1476
  catch (_) {
1456
1477
  }
1457
1478
  }
1479
+ if (primary.script === null) {
1480
+ var maximized = maximizeTag(primary.baseName);
1481
+ if (maximized !== null && maximized.script !== null)
1482
+ return contains(RTL_SCRIPTS, maximized.script);
1483
+ }
1458
1484
  return isRTL(primary);
1459
1485
  }
1460
1486
  var subtags = null;
@@ -1554,6 +1580,9 @@ var LocaleKit = {
1554
1580
  expand: function (tag) {
1555
1581
  return expandTag(tag);
1556
1582
  },
1583
+ maximize: function (tag) {
1584
+ return maximizeTag(tag);
1585
+ },
1557
1586
  lookup: function (supported, fallback) {
1558
1587
  return lookupTag(supported, languages, typeof fallback === 'undefined' ? null : fallback);
1559
1588
  },
@@ -1566,6 +1595,7 @@ var CACHE_PREFIX = 'web-launch-kit:';
1566
1595
  var TRACK_ID = 'track-id';
1567
1596
  var PRODUCT_ID = 'product-id';
1568
1597
  var TRACK_ID_CACHE_TTL = 1000 * 60 * 60;
1598
+ var ALPHA2_REGION_PATTERN$1 = /^[A-Z]{2}$/;
1569
1599
  function toCacheKey(mode, query) {
1570
1600
  return CACHE_PREFIX + mode + ':' + query;
1571
1601
  }
@@ -1698,27 +1728,34 @@ function requestGet(url, async) {
1698
1728
  }
1699
1729
  }
1700
1730
  }
1701
- function resolveGetProductIdURL(packageFamilyName) {
1702
- var locales = LocaleKit.languages;
1703
- var locale = 'en-US';
1704
- var country = 'US';
1705
- for (var i = 0; i < locales.length; i++) {
1706
- var candidate = locales[i];
1707
- if (candidate.indexOf('-') === -1)
1708
- continue;
1709
- var subtags = candidate.split('-');
1710
- for (var j = 1; j < subtags.length; j++) {
1711
- var subtag = subtags[j];
1712
- if (/^[A-Za-z]{2}$/.test(subtag) || /^\d{3}$/.test(subtag)) {
1713
- locale = candidate;
1714
- country = subtag;
1715
- break;
1716
- }
1717
- }
1718
- if (locale === candidate)
1719
- break;
1720
- }
1721
- return 'https://displaycatalog.md.mp.microsoft.com/v7.0/products/lookup?value=' + encodeURIComponent(packageFamilyName) + '&market=' + encodeURIComponent(country) + '&languages=' + encodeURIComponent(locale) + '&alternateId=PackageFamilyName';
1731
+ function resolveMarket(language, country) {
1732
+ var locale = language;
1733
+ var market;
1734
+ if (typeof country !== 'undefined' && ALPHA2_REGION_PATTERN$1.test(country.toUpperCase()))
1735
+ market = country.toUpperCase();
1736
+ if (typeof market === 'undefined') {
1737
+ var source = typeof locale !== 'undefined'
1738
+ ? locale
1739
+ : (LocaleKit.language === null ? undefined : LocaleKit.language);
1740
+ if (typeof source !== 'undefined') {
1741
+ var maximized = LocaleKit.maximize(source);
1742
+ if (maximized !== null && maximized.region !== null && ALPHA2_REGION_PATTERN$1.test(maximized.region))
1743
+ market = maximized.region;
1744
+ }
1745
+ }
1746
+ if (typeof locale === 'undefined' && LocaleKit.language !== null)
1747
+ locale = LocaleKit.language;
1748
+ if (typeof locale === 'undefined')
1749
+ locale = 'en-US';
1750
+ if (typeof market === 'undefined')
1751
+ market = 'US';
1752
+ return [locale, market];
1753
+ }
1754
+ function resolveGetProductIdURL(packageFamilyName, language, country) {
1755
+ var resolved = resolveMarket(language, country);
1756
+ var locale = resolved[0];
1757
+ var market = resolved[1];
1758
+ return 'https://displaycatalog.md.mp.microsoft.com/v7.0/products/lookup?value=' + encodeURIComponent(packageFamilyName) + '&market=' + encodeURIComponent(market) + '&languages=' + encodeURIComponent(locale) + '&alternateId=PackageFamilyName';
1722
1759
  }
1723
1760
  function resolveGetTrackIdURL(bundleId) {
1724
1761
  return 'https://itunes.apple.com/lookup?bundleId=' + encodeURIComponent(bundleId);
@@ -1746,12 +1783,12 @@ function parseGetTrackIdResponse(response) {
1746
1783
  return undefined;
1747
1784
  return '' + result.trackId;
1748
1785
  }
1749
- function getProductId(packageFamilyName) {
1786
+ function getProductId(packageFamilyName, language, country) {
1750
1787
  var cache = idCache.get(PRODUCT_ID, packageFamilyName);
1751
1788
  if (typeof cache !== 'undefined')
1752
1789
  return cache;
1753
1790
  try {
1754
- var productId = parseGetProductIdResponse(requestGet(resolveGetProductIdURL(packageFamilyName)));
1791
+ var productId = parseGetProductIdResponse(requestGet(resolveGetProductIdURL(packageFamilyName, language, country)));
1755
1792
  if (typeof productId === 'undefined')
1756
1793
  return undefined;
1757
1794
  idCache.set(PRODUCT_ID, packageFamilyName, productId);
@@ -1776,12 +1813,12 @@ function getTrackId(bundleId) {
1776
1813
  return undefined;
1777
1814
  }
1778
1815
  }
1779
- function getProductIdAsync(packageFamilyName) {
1816
+ function getProductIdAsync(packageFamilyName, language, country) {
1780
1817
  var cache = idCache.get(PRODUCT_ID, packageFamilyName);
1781
1818
  if (typeof cache !== 'undefined')
1782
1819
  return Promise.resolve(cache);
1783
1820
  try {
1784
- return requestGet(resolveGetProductIdURL(packageFamilyName), true)
1821
+ return requestGet(resolveGetProductIdURL(packageFamilyName, language, country), true)
1785
1822
  .then(parseGetProductIdResponse)
1786
1823
  .then(function (productId) {
1787
1824
  if (typeof productId === 'undefined')
@@ -1896,6 +1933,7 @@ var MACOS_DEFAULT_TIMEOUT = 750;
1896
1933
  var DEFAULT_TIMEOUT = 750;
1897
1934
  var GLOBAL = getGlobal$1();
1898
1935
  var NAVIGATOR = GLOBAL.navigator;
1936
+ var ALPHA2_REGION_PATTERN = /^[A-Z]{2}$/;
1899
1937
  var CLEANUP_INPUT_ELEMENT = null;
1900
1938
  var DEBUG = false;
1901
1939
  var APP_IN_FLIGHT = null;
@@ -2345,6 +2383,8 @@ function resolveOptions(option) {
2345
2383
  var allowScheme;
2346
2384
  var assumeAllowedInApp;
2347
2385
  var timeout;
2386
+ var language;
2387
+ var country;
2348
2388
  var intent;
2349
2389
  var packageName;
2350
2390
  var allowIntent = true;
@@ -2367,6 +2407,8 @@ function resolveOptions(option) {
2367
2407
  allowWebStore = option.android.allowWebStore;
2368
2408
  allowScheme = canOpenScheme(assumeAllowedInApp);
2369
2409
  timeout = option.android.timeout;
2410
+ language = option.android.language;
2411
+ country = option.android.country;
2370
2412
  if (typeof intent !== 'undefined' && (typeof scheme === 'undefined' || typeof packageName === 'undefined' || typeof fallback === 'undefined')) {
2371
2413
  var parsed = parseIntentURL(intent);
2372
2414
  if (typeof parsed.scheme !== 'undefined' && typeof scheme === 'undefined')
@@ -2390,7 +2432,7 @@ function resolveOptions(option) {
2390
2432
  if (allowAppStore && allowScheme)
2391
2433
  resolved.push(['store', createAppStoreURL(packageName, os)]);
2392
2434
  if (allowWebStore)
2393
- resolved.push(['store', createWebStoreURL(packageName, os)]);
2435
+ resolved.push(['store', createWebStoreURL(packageName, os, language, country)]);
2394
2436
  }
2395
2437
  return [resolved, timeout];
2396
2438
  case 'ios':
@@ -2406,6 +2448,8 @@ function resolveOptions(option) {
2406
2448
  allowWebStore = option.ios.allowWebStore;
2407
2449
  allowScheme = canOpenScheme(assumeAllowedInApp);
2408
2450
  timeout = option.ios.timeout;
2451
+ language = option.ios.language;
2452
+ country = option.ios.country;
2409
2453
  if (typeof bundleId !== 'undefined' && typeof trackId === 'undefined')
2410
2454
  trackId = getTrackId(bundleId);
2411
2455
  if (typeof timeout === 'undefined')
@@ -2420,7 +2464,7 @@ function resolveOptions(option) {
2420
2464
  if (allowAppStore && allowScheme)
2421
2465
  resolved.push(['store', createAppStoreURL(trackId, os)]);
2422
2466
  if (allowWebStore)
2423
- resolved.push(['store', createWebStoreURL(trackId, os)]);
2467
+ resolved.push(['store', createWebStoreURL(trackId, os, language, country)]);
2424
2468
  }
2425
2469
  return [resolved, timeout];
2426
2470
  case 'windows':
@@ -2433,8 +2477,10 @@ function resolveOptions(option) {
2433
2477
  allowAppStore = option.windows.allowAppStore;
2434
2478
  allowWebStore = option.windows.allowWebStore;
2435
2479
  timeout = option.windows.timeout;
2480
+ language = option.windows.language;
2481
+ country = option.windows.country;
2436
2482
  if (typeof packageFamilyName !== 'undefined' && typeof productId === 'undefined')
2437
- productId = getProductId(packageFamilyName);
2483
+ productId = getProductId(packageFamilyName, language, country);
2438
2484
  if (typeof timeout === 'undefined')
2439
2485
  timeout = WINDOWS_DEFAULT_TIMEOUT;
2440
2486
  if (typeof scheme !== 'undefined')
@@ -2445,7 +2491,7 @@ function resolveOptions(option) {
2445
2491
  if (allowAppStore)
2446
2492
  resolved.push(['store', createAppStoreURL(productId, os)]);
2447
2493
  if (allowWebStore)
2448
- resolved.push(['store', createWebStoreURL(productId, os)]);
2494
+ resolved.push(['store', createWebStoreURL(productId, os, language, country)]);
2449
2495
  }
2450
2496
  return [resolved, timeout];
2451
2497
  case 'macos':
@@ -2458,6 +2504,8 @@ function resolveOptions(option) {
2458
2504
  allowAppStore = option.macos.allowAppStore;
2459
2505
  allowWebStore = option.macos.allowWebStore;
2460
2506
  timeout = option.macos.timeout;
2507
+ language = option.macos.language;
2508
+ country = option.macos.country;
2461
2509
  if (typeof bundleId !== 'undefined' && typeof trackId === 'undefined')
2462
2510
  trackId = getTrackId(bundleId);
2463
2511
  if (typeof timeout === 'undefined')
@@ -2470,7 +2518,7 @@ function resolveOptions(option) {
2470
2518
  if (allowAppStore)
2471
2519
  resolved.push(['store', createAppStoreURL(trackId, os)]);
2472
2520
  if (allowWebStore)
2473
- resolved.push(['store', createWebStoreURL(trackId, os)]);
2521
+ resolved.push(['store', createWebStoreURL(trackId, os, language, country)]);
2474
2522
  }
2475
2523
  return [resolved, timeout];
2476
2524
  }
@@ -2637,19 +2685,69 @@ function createAppStoreURL(id, os) {
2637
2685
  return os;
2638
2686
  }
2639
2687
  }
2640
- function createWebStoreURL(id, os) {
2688
+ function createWebStoreURL(id, os, language, country) {
2689
+ var resolved = resolveCountry(language, country);
2690
+ var countryPath = typeof resolved !== 'undefined' ? resolved.toLowerCase() + '/' : '';
2691
+ var url;
2641
2692
  switch (os) {
2642
2693
  case 'android':
2643
- return 'https://play.google.com/store/apps/details?id=' + id;
2694
+ url = 'https://play.google.com/store/apps/details?id=' + id;
2695
+ if (typeof language !== 'undefined')
2696
+ url = appendURLParameter(url, 'hl=' + escapeURIComponentString(language));
2697
+ if (typeof resolved !== 'undefined')
2698
+ url = appendURLParameter(url, 'gl=' + resolved);
2699
+ break;
2644
2700
  case 'ios':
2645
- return 'https://itunes.apple.com/app/id' + id + '?mt=8';
2701
+ url = 'https://itunes.apple.com/' + countryPath + 'app/id' + id + '?mt=8';
2702
+ if (typeof language !== 'undefined')
2703
+ url = appendURLParameter(url, 'l=' + escapeURIComponentString(language));
2704
+ break;
2646
2705
  case 'windows':
2647
- return 'https://apps.microsoft.com/detail/' + id;
2706
+ url = 'https://apps.microsoft.com/detail/' + id;
2707
+ if (typeof language !== 'undefined')
2708
+ url = appendURLParameter(url, 'hl=' + escapeURIComponentString(language));
2709
+ if (typeof resolved !== 'undefined')
2710
+ url = appendURLParameter(url, 'gl=' + resolved);
2711
+ break;
2648
2712
  case 'macos':
2649
- return 'https://apps.apple.com/app/id' + id + '?mt=12';
2713
+ url = 'https://apps.apple.com/' + countryPath + 'app/id' + id + '?mt=12';
2714
+ if (typeof language !== 'undefined')
2715
+ url = appendURLParameter(url, 'l=' + escapeURIComponentString(language));
2716
+ break;
2650
2717
  default:
2651
2718
  return os;
2652
2719
  }
2720
+ return url;
2721
+ }
2722
+ function extractRegion(tag) {
2723
+ var parts = tag.replace(/_/g, '-').split('-');
2724
+ for (var i = 1; i < parts.length; i++) {
2725
+ if (/^[A-Za-z]{2}$/.test(parts[i]))
2726
+ return parts[i].toUpperCase();
2727
+ }
2728
+ return undefined;
2729
+ }
2730
+ function resolveCountry(language, country) {
2731
+ if (typeof country !== 'undefined' && ALPHA2_REGION_PATTERN.test(country.toUpperCase()))
2732
+ return country.toUpperCase();
2733
+ if (typeof language === 'undefined')
2734
+ return undefined;
2735
+ var region = extractRegion(language);
2736
+ if (typeof region !== 'undefined')
2737
+ return region;
2738
+ if (typeof Intl !== 'undefined' && typeof Intl.Locale === 'function') {
2739
+ try {
2740
+ var locale = new Intl.Locale(language);
2741
+ if (typeof locale.maximize === 'function') {
2742
+ var maximized = locale.maximize();
2743
+ if (typeof maximized !== 'undefined' && maximized !== null && typeof maximized.region === 'string' && ALPHA2_REGION_PATTERN.test(maximized.region.toUpperCase()))
2744
+ return maximized.region.toUpperCase();
2745
+ }
2746
+ }
2747
+ catch (_) {
2748
+ }
2749
+ }
2750
+ return undefined;
2653
2751
  }
2654
2752
  function appendURLParameter(url, parameter) {
2655
2753
  var index = url.indexOf('#');