diff --git a/lib/data/ducet_order.dart b/lib/data/ducet_order.dart index 755720f6..e4e8ca7c 100644 --- a/lib/data/ducet_order.dart +++ b/lib/data/ducet_order.dart @@ -1,10 +1,522 @@ -/// Sorted BMP codepoints per DUCET (Unicode 13.0) + CLDR 39 CJK radical-stroke. +/// Dense BMP ranks per DUCET (Unicode 13.0) + pinned CLDR CJK radical-stroke. +/// Each code unit stores rank + 1; zero means absent. /// Katakana sorts before hiragana (CLDR root tailoring). /// Generated by scripts/generate_ducet_ranks.dart โ€” do not edit. library; -const String _ducetOrder = - '\u0332\u0313\u0343\u0486\u2cf1\u0314\u0485\u2cf0\u0301\u0341\u0954\u0300\u0340\u0953\u0306\u0302\u030c\u030a\u0342\u0308\u030b\u0303\u0307\u0338\u0327\u0328\u0304\u030d\u030e\u0312\u0315\u031a\u033d\u033e\u033f\u0346\u034a\u034b\u034c\u0350\u0351\u0352\u0357\u035b\u035d\u035e\u0484\u0487\u0741\u0745\u0b55\u17cb\u17cc\u17cd\u17ce\u17cf\u17d0\u17d1\u17dd\u1ab0\u1ab1\u1ab2\u1ab3\u1ab4\u1abb\u1abc\u1dc0\u1dc1\u1dc3\u1dc4\u1dc5\u1dc6\u1dc7\u1dc8\u1dc9\u1dcb\u1dcc\u1dcd\u1dce\u1dd1\u1df5\u1df6\u1df7\u1df8\u1dfb\u1dfe\u20f0\u2cef\ua67c\ua67d\u0316\u0317\u0318\u0319\u031c\u031d\u031e\u031f\u0320\u0329\u032a\u032b\u032c\u032f\u0333\u033a\u033b\u033c\u0347\u0348\u0349\u034d\u034e\u0353\u0354\u0355\u0356\u0359\u035a\u035c\u035f\u0362\u0742\u0746\u07fd\u0859\u085a\u085b\u1ab5\u1ab6\u1ab7\u1ab8\u1ab9\u1aba\u1abd\u1dc2\u1dcf\u1dd0\u1df9\u1dfc\u1dfd\u1dff\u20ec\u20ed\u20ee\u20ef\ufe27\u0336\u0337\u20d8\u20d9\u20da\u20e5\u20ea\u20eb\u1abe\u20dd\u20de\u20df\u20e0\u20e2\u20e3\u20e4\u3099\uff9e\u309a\uff9f\u0335\u0305\u0309\u030f\u0310\u0311\u031b\u0321\u0322\u0323\u0324\u0325\u0326\u032d\u032e\u0330\u0331\u0334\u0339\u0345\u0358\u0360\ufe22\ufe29\u0361\ufe20\u0483\ufe2e\ua66f\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05c7\u05b9\u05ba\u05bb\u05c2\u05c1\u05bc\u05bf\ufb1e\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a\u082b\u082c\u0818\u0819\u082d\u064b\ufe71\ufe70\u08f0\u08e7\u064c\ufe72\u08f1\u08e8\u064d\ufe74\u08f2\u08e9\u064e\ufe77\ufe76\u08e4\u08f4\u08f5\u064f\ufe79\ufe78\u08e5\u08fe\u0650\ufe7b\ufe7a\u08e6\u08f6\u0651\u0afb\ufe7d\ufe7c\u0652\u0afa\ufe7f\ufe7e\u0653\u0afc\u0654\u0655\u065f\u0656\u0657\u0658\u08ff\u0659\u065a\u065b\u065c\u065d\u065e\u08e3\u08f7\u08f8\u08fd\u08fb\u08fc\u08f9\u08fa\u0670\u0711\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a\u073b\u073c\u073d\u073e\u073f\u07eb\u07ec\u07ed\u07ee\u07ef\u07f0\u07f1\u07f2\u07f3\u135f\u135e\u135d\ua6f0\ua6f1\u093c\u09bc\u0a3c\u0abc\u0afd\u0afe\u0aff\u0b3c\u0cbc\u1b34\u1be6\u1c37\ua9b3\u0900\u0901\u0981\u0a01\u0a81\u0b01\u0c00\u0c01\u0c81\u0d01\u0d81\u1b00\u1b01\ua8c5\ua980\u0902\u0982\u0a02\u0a82\u0b02\u0b82\u0c02\u0c04\u0c82\u0d00\u0d02\u0d82\u0f7e\u1036\u17c6\u1a74\u1b02\u1b80\u1ced\ua80b\ua880\ua981\u0903\u0983\u0a03\u0a83\u0b03\u0c03\u0c83\u0d03\u0d83\u0f7f\u1038\u17c7\u1b04\u1b82\u1cf2\u1cf3\ua881\ua983\u09fe\u0a70\u0a71\u1b03\ua982\u1b81\uabec\u0e4e\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\uaabf\uaac1\u0f39\ua92b\ua92c\ua92d\u1037\u17c8\u17c9\u17ca\u1a75\u1a76\u1a77\u1a78\u1a79\u1a7a\u1a7b\u1a7c\u1939\u193a\u193b\u302a\u302b\u302c\u302d\u302e\u302f\u20d0\u20d1\u20d2\u20d3\u20d4\u20d5\u20d6\u20d7\u20db\u20dc\u20e1\u20e6\u20e7\u20e8\u20e9\u0344\ufc5e\ufc5f\ufcf2\ufc60\ufcf3\ufc61\ufcf4\ufc62\ufc63\u0009\u000a\u000b\u000c\u000d\u0085\u2028\u2029\u0020\u3000\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2008\u2009\u200a\u205f\u00a0\u2007\u202f\u203e\ufe49\ufe4a\ufe4b\ufe4c\u005f\uff3f\ufe4d\ufe4e\ufe4f\ufe33\ufe34\u2017\u002d\uff0d\ufe63\u058a\u1400\u1b60\u1806\u1807\u2010\u2011\u2012\u2013\ufe32\u2014\ufe58\ufe31\u2015\u2e3a\u2e3b\u2053\u2e43\u2e17\u2e40\u301c\u3030\u30a0\u30fb\uff65\u002c\uff0c\ufe50\ufe10\u2e34\u2e32\u2e41\u2e4c\u2e4e\u2e4f\u055d\u060c\u060d\u066b\u066c\u07f8\u1802\u1808\ua4fe\ua60d\ua6f5\u3001\ufe51\uff64\ufe11\ufe45\ufe46\u003b\u037e\uff1b\ufe54\ufe14\u061b\u204f\u2e35\ua6f6\u2e49\u003a\uff1a\ufe55\ufe13\u2a74\u0589\u061e\u0703\u0704\u0705\u0706\u0707\u0708\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e\u1361\u1363\u1364\u1365\u1366\u1804\u1805\u0f14\u17d6\u1b5d\ua9c7\u16eb\u16ec\u16ed\ua6f4\u0021\uff01\ufe57\ufe15\u203c\u2049\u00a1\u055c\u07f9\u1944\u003f\uff1f\ufe56\ufe16\u2048\u2047\u00bf\u2e2e\u055e\u061f\u0709\u1367\u1945\u2cfa\u2cfb\ua60f\ua6f7\uaaf1\u203d\u2e18\u002e\uff0e\u2024\ufe52\u2025\ufe30\u2026\ufe19\u1801\u06d4\u0701\u0702\u1362\u1803\u1809\u166e\u1b5c\u2cf9\u2cfe\u2e30\u2e3c\ua4ff\ua60e\ua6f3\u3002\uff61\ufe12\u00b7\u0387\u2e31\u2e33\u0964\u0965\ua8ce\ua8cf\u1c3b\u1c3c\ua876\ua877\u1735\u1736\ua92f\u104a\u104b\u17d4\u17d5\u1aa8\u1aa9\u1aaa\u1aab\u1b5e\u1b5f\ua9c8\ua9c9\uaa5d\uaa5e\uaa5f\uaaf0\uabeb\u1c7e\u1c7f\u0700\u07f7\u10fb\u1360\u1368\u1a1e\u1a1f\u1b5a\u1b5b\ua9c1\ua9c2\ua9c3\ua9c4\ua9c5\ua9c6\ua9ca\ua9cb\ua9cc\ua9cd\ua6f2\ua95f\u2055\u2056\u2058\u2059\u205a\u205b\u205c\u205d\u205e\u2e2a\u2e2b\u2e2c\u2e2d\u2e3d\u2cfc\u2cff\u2e19\'\uff07\u2018\u2019\u201a\u201b\u2039\u203a\u0022\uff02\u201c\u201d\u201e\u201f\u2e42\u301d\u301e\u301f\u00ab\u00bb\u0028\uff08\ufe59\u207d\u208d\ufe35\u2474\u247d\u247e\u247f\u2480\u2481\u2482\u2483\u2484\u2485\u2486\u2475\u2487\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u249c\u249d\u249e\u249f\u24a0\u24a1\u24a2\u24a3\u24a4\u24a5\u24a6\u24a7\u24a8\u24a9\u24aa\u24ab\u24ac\u24ad\u24ae\u24af\u24b0\u24b1\u24b2\u24b3\u24b4\u24b5\u3200\u320e\u3201\u320f\u3202\u3210\u3203\u3211\u3204\u3212\u3205\u3213\u3206\u3214\u3207\u3215\u321d\u321e\u3208\u3216\u321c\u3209\u3217\u320a\u3218\u320b\u3219\u320c\u321a\u320d\u321b\u3220\u3226\u3222\u3228\u3221\u3224\u3239\u323d\u3241\u3227\u3225\u3238\u3229\u323f\u3234\u323a\u3223\u322f\u323b\u3230\u322a\u3232\u322d\u3231\u322c\u322b\u3235\u323c\u3233\u3237\u3240\u3242\u3243\u3236\u323e\u322e\u0029\uff09\ufe5a\u207e\u208e\ufe36\u005b\uff3b\ufe47\u005d\uff3d\ufe48\u007b\uff5b\ufe5b\ufe37\u007d\uff5d\ufe5c\ufe38\u0f3a\u0f3b\u0f3c\u0f3d\u169b\u169c\u2045\u2046\u2308\u2309\u230a\u230b\u29fc\u29fd\u2983\u2984\u2985\uff5f\u2986\uff60\u2987\u2988\u2989\u298a\u298b\u298c\u298d\u298e\u298f\u2990\u2991\u2992\u2993\u2994\u2995\u2996\u2997\u2998\u27c5\u27c6\u27e6\u27e7\u27e8\u27e9\u27ea\u27eb\u27ec\u27ed\u27ee\u27ef\u2768\u2769\u276a\u276b\u276c\u276d\u276e\u276f\u2770\u2771\u2772\u2773\u2774\u2775\u2e02\u2e03\u2e04\u2e05\u2e09\u2e0a\u2e0c\u2e0d\u2e1c\u2e1d\u2e20\u2e21\u2e22\u2e23\u2e24\u2e25\u2e26\u2e27\u2e28\u2e29\u2329\u3008\ufe3f\u232a\u3009\ufe40\u300a\ufe3d\u300b\ufe3e\u300c\uff62\ufe41\u300d\uff63\ufe42\u300e\ufe43\u300f\ufe44\u3010\ufe3b\u3011\ufe3c\u3014\ufe5d\ufe39\u3015\ufe5e\ufe3a\u3016\ufe17\u3017\ufe18\u3018\u3019\u301a\u301b\ufd3e\ufd3f\u2016\u2e3e\u29d8\u29d9\u29da\u29db\u00a7\u2e39\u00b6\u204b\u2e4d\u2e3f\u0040\uff20\ufe6b\u002a\uff0a\ufe61\u204e\u2051\u066d\ua673\u002f\uff0f\\\uff3c\ufe68\u2e4a\u0026\uff06\ufe60\u204a\u2e52\u0023\uff03\ufe5f\u0025\uff05\ufe6a\u066a\u2030\u0609\u2031\u060a\u2020\u2021\u2e36\u2e37\u2e38\u2e4b\u2022\u2023\u2027\u2043\u204c\u204d\u2032\u2033\u2034\u2057\u2035\u2036\u2037\u3003\u303d\u2038\u203b\u203f\u2054\u2040\u2050\u2041\u2042\u2e00\u2e01\u2e06\u2e07\u2e08\u2e0b\u2e0e\u2e0f\u2e10\u2e11\u2e12\u2e13\u2e14\u2e15\u2e16\u2e1a\u2e1b\u2e1e\u2e1f\u2e44\u2e45\u2e46\u2e47\u2e48\ua67e\u055a\u055b\u055f\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u070a\u070b\u070c\u070d\u085e\u1800\u0970\ua8f8\ua8f9\ua8fa\ua8fc\u09fd\u0a76\u0af0\u0c77\u0c84\u0df4\u0e4f\u0e5a\u0e5b\uaade\uaadf\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a\u0fd0\u0fd1\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f85\u0fd2\u0fd3\u0fd4\u0fd9\u0fda\u1c3d\u1c3e\u1c3f\u104c\u104d\u104e\u104f\u17d8\u17d9\u17da\u1aa0\u1aa1\u1aa2\u1aa3\u1aa4\u1aa5\u1aa6\u1aac\u1aad\u1cc0\u1cc1\u1cc2\u1cc3\u1cc4\u1cc5\u1cc6\u1cc7\u2d70\ua874\ua875\u1bfc\u1bfd\u1bfe\u1bff\ua92e\ua9de\ua9df\uaa5c\u0060\u1fef\uff40\u00b4\u0384\u1ffd\u02dc\u005e\uff3e\u00af\uffe3\u02d8\u02d9\u00a8\u0385\u1fee\u1fed\u1fc1\u02da\u02dd\u00b8\u02db\u1fbd\u1fbf\u1fce\u1fcd\u1fcf\u1ffe\u1fde\u1fdd\u1fdf\u1fc0\u309b\u309c\u02b9\u0374\u0375\u02ba\u02c2\u02c3\u02c4\u02c5\u02c6\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d2\u02d3\u02d4\u02d5\uab6a\uab6b\u02d6\u02d7\u02de\u02df\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u1390\u1391\u1392\u1393\u1394\u1395\u1396\u1397\u1398\u1399\ua700\ua701\ua702\ua703\ua704\ua705\ua706\ua707\ua708\ua709\ua70a\ua70b\ua70c\ua70d\ua70e\ua70f\ua710\ua711\ua712\ua713\ua714\ua715\ua716\ua717\ua718\ua719\ua71a\ua71b\ua71c\ua71d\ua71e\ua71f\ua720\ua721\ua788\ua789\ua78a\uab5b\u00b0\u2103\u2109\u0482\u058d\u058e\u0608\u060e\u060f\u06de\u06e9\ufdfd\ufbb2\ufbb3\ufbb4\ufbb5\ufbb6\ufbb7\ufbb8\ufbb9\ufbba\ufbbb\ufbbc\ufbbd\ufbbe\ufbbf\ufbc0\ufbc1\u07f6\u09fa\u0b70\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\u0c7f\u0d4f\u0d79\ua828\ua829\ua82a\ua82b\ua836\ua837\ua839\u0f01\u0f02\u0f03\u0f13\u0f15\u0f16\u0f17\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e\u0f1f\u0f34\u0f36\u0f38\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fce\u0fcf\u0fd5\u0fd6\u0fd7\u0fd8\u166d\u1940\u109e\u109f\uaa77\uaa78\uaa79\u19e0\u19e1\u19e2\u19e3\u19e4\u19e5\u19e6\u19e7\u19e8\u19e9\u19ea\u19eb\u19ec\u19ed\u19ee\u19ef\u19f0\u19f1\u19f2\u19f3\u19f4\u19f5\u19f6\u19f7\u19f8\u19f9\u19fa\u19fb\u19fc\u19fd\u19fe\u19ff\u1b61\u1b62\u1b63\u1b64\u1b65\u1b66\u1b67\u1b68\u1b69\u1b6a\u1b74\u1b75\u1b76\u1b77\u1b78\u1b79\u1b7a\u1b7b\u1b7c\u00a9\u00ae\u2104\u2108\u2114\u2117\u2118\u211e\u211f\u2123\u2125\u2127\u2129\u212e\u213a\u2141\u2142\u2143\u2144\u214a\u214c\u214f\u218a\u218b\u2190\uffe9\u219a\u2192\uffeb\u219b\u2191\uffea\u2193\uffec\u2194\u21ae\u2195\u2196\u2197\u2198\u2199\u219c\u219d\u219e\u219f\u21a0\u21a1\u21a2\u21a3\u21a4\u21a5\u21a6\u21a7\u21a8\u21a9\u21aa\u21ab\u21ac\u21ad\u21af\u21b0\u21b1\u21b2\u21b3\u21b4\u21b5\u21b6\u21b7\u21b8\u21b9\u21ba\u21bb\u21bc\u21bd\u21be\u21bf\u21c0\u21c1\u21c2\u21c3\u21c4\u21c5\u21c6\u21c7\u21c8\u21c9\u21ca\u21cb\u21cc\u21d0\u21cd\u21d1\u21d2\u21cf\u21d3\u21d4\u21ce\u21d5\u21d6\u21d7\u21d8\u21d9\u21da\u21db\u21dc\u21dd\u21de\u21df\u21e0\u21e1\u21e2\u21e3\u21e4\u21e5\u21e6\u21e7\u21e8\u21e9\u21ea\u21eb\u21ec\u21ed\u21ee\u21ef\u21f0\u21f1\u21f2\u21f3\u21f4\u21f5\u21f6\u21f7\u21f8\u21f9\u21fa\u21fb\u21fc\u21fd\u21fe\u21ff\u2200\u2201\u2202\u2203\u2204\u2205\u2206\u2207\u2208\u2209\u220a\u220b\u220c\u220d\u03f6\u220e\u220f\u2210\u2211\u2140\u002b\uff0b\ufb29\ufe62\u207a\u208a\u00b1\u00f7\u00d7\u003c\uff1c\ufe64\u226e\u003d\uff1d\ufe66\u207c\u208c\u2260\u2a75\u2a76\u003e\uff1e\ufe65\u226f\u00ac\uffe2\u007c\uff5c\u00a6\uffe4\u007e\uff5e\u2212\u207b\u208b\u2052\u2213\u2214\u2215\u2044\u2216\u2217\u2218\u2219\u221a\u221b\u0606\u221c\u0607\u221d\u221e\u221f\u2220\u2221\u2222\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222a\u222b\u222c\u222d\u2a0c\u222e\u222f\u2230\u2231\u2232\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223a\u223b\u223c\u2241\u223d\u223e\u223f\u2240\u2242\u2243\u2244\u2245\u2247\u2246\u2248\u2249\u224a\u224b\u224c\u224d\u226d\u224e\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u2261\u2262\u2263\u2264\u2270\u2265\u2271\u2266\u2267\u2268\u2269\u226a\u226b\u226c\u2272\u2274\u2273\u2275\u2276\u2278\u2277\u2279\u227a\u2280\u227b\u2281\u227c\u22e0\u227d\u22e1\u227e\u227f\u2282\u2284\u2283\u2285\u2286\u2288\u2287\u2289\u228a\u228b\u228c\u228d\u228e\u228f\u2290\u2291\u22e2\u2292\u22e3\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229a\u229b\u229c\u229d\u229e\u229f\u22a0\u22a1\u22a2\u22ac\u22a3\u22a4\u22a5\u22a6\u22a7\u22a8\u22ad\u22a9\u22ae\u22aa\u22ab\u22af\u22b0\u22b1\u22b2\u22ea\u22b3\u22eb\u22b4\u22ec\u22b5\u22ed\u22b6\u22b7\u22b8\u22b9\u22ba\u22bb\u22bc\u214b\u22bd\u22be\u22bf\u22c0\u22c1\u22c2\u22c3\u22c4\u22c5\u22c6\u22c7\u22c8\u22c9\u22ca\u22cb\u22cc\u22cd\u22ce\u22cf\u22d0\u22d1\u22d2\u22d3\u22d4\u22d5\u22d6\u22d7\u22d8\u22d9\u22da\u22db\u22dc\u22dd\u22de\u22df\u22e4\u22e5\u22e6\u22e7\u22e8\u22e9\u22ee\u22ef\u22f0\u22f1\u22f2\u22f3\u22f4\u22f5\u22f6\u22f7\u22f8\u22f9\u22fa\u22fb\u22fc\u22fd\u22fe\u22ff\u2300\u2301\u2302\u2303\u2304\u2305\u2306\u2307\u230c\u230d\u230e\u230f\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231a\u231b\u231c\u231d\u231e\u231f\u2320\u2321\u2322\u2323\u2324\u2325\u2326\u2327\u2328\u232b\u232c\u232d\u232e\u232f\u2330\u2331\u2332\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233a\u233b\u233c\u233d\u233e\u233f\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234a\u234b\u234c\u234d\u234e\u234f\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2358\u2359\u235a\u235b\u235c\u235d\u235e\u235f\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236a\u236b\u236c\u236d\u236e\u236f\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237a\u237b\u237c\u237d\u237e\u237f\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238a\u238b\u238c\u238d\u238e\u238f\u2390\u2391\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239a\u239b\u239c\u239d\u239e\u239f\u23a0\u23a1\u23a2\u23a3\u23a4\u23a5\u23a6\u23a7\u23a8\u23a9\u23aa\u23ab\u23ac\u23ad\u23ae\u23af\u23b0\u23b1\u23b2\u23b3\u23b4\u23b5\u23b6\u23b7\u23b8\u23b9\u23ba\u23bb\u23bc\u23bd\u23be\u23bf\u23c0\u23c1\u23c2\u23c3\u23c4\u23c5\u23c6\u23c7\u23c8\u23c9\u23ca\u23cb\u23cc\u23cd\u23ce\u23cf\u23d0\u23d1\u23d2\u23d3\u23d4\u23d5\u23d6\u23d7\u23d8\u23d9\u23da\u23db\u23dc\u23dd\u23de\u23df\u23e0\u23e1\u23e2\u23e3\u23e4\u23e5\u23e6\u23e7\u23e8\u23e9\u23ea\u23eb\u23ec\u23ed\u23ee\u23ef\u23f0\u23f1\u23f2\u23f3\u23f4\u23f5\u23f6\u23f7\u23f8\u23f9\u23fa\u23fb\u23fc\u23fd\u23fe\u23ff\u2400\u2401\u2402\u2403\u2404\u2405\u2406\u2407\u2408\u2409\u240a\u240b\u240c\u240d\u240e\u240f\u2410\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u2419\u241a\u241b\u241c\u241d\u241e\u241f\u2420\u2421\u2422\u2423\u2424\u2425\u2426\u2440\u2441\u2442\u2443\u2444\u2445\u2446\u2447\u2448\u2449\u244a\u2500\u2501\u2502\uffe8\u2503\u2504\u2505\u2506\u2507\u2508\u2509\u250a\u250b\u250c\u250d\u250e\u250f\u2510\u2511\u2512\u2513\u2514\u2515\u2516\u2517\u2518\u2519\u251a\u251b\u251c\u251d\u251e\u251f\u2520\u2521\u2522\u2523\u2524\u2525\u2526\u2527\u2528\u2529\u252a\u252b\u252c\u252d\u252e\u252f\u2530\u2531\u2532\u2533\u2534\u2535\u2536\u2537\u2538\u2539\u253a\u253b\u253c\u253d\u253e\u253f\u2540\u2541\u2542\u2543\u2544\u2545\u2546\u2547\u2548\u2549\u254a\u254b\u254c\u254d\u254e\u254f\u2550\u2551\u2552\u2553\u2554\u2555\u2556\u2557\u2558\u2559\u255a\u255b\u255c\u255d\u255e\u255f\u2560\u2561\u2562\u2563\u2564\u2565\u2566\u2567\u2568\u2569\u256a\u256b\u256c\u256d\u256e\u256f\u2570\u2571\u2572\u2573\u2574\u2575\u2576\u2577\u2578\u2579\u257a\u257b\u257c\u257d\u257e\u257f\u2580\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2589\u258a\u258b\u258c\u258d\u258e\u258f\u2590\u2591\u2592\u2593\u2594\u2595\u2596\u2597\u2598\u2599\u259a\u259b\u259c\u259d\u259e\u259f\u25a0\uffed\u25a1\u25a2\u25a3\u25a4\u25a5\u25a6\u25a7\u25a8\u25a9\u25aa\u25ab\u25ac\u25ad\u25ae\u25af\u25b0\u25b1\u25b2\u25b3\u25b4\u25b5\u25b6\u25b7\u25b8\u25b9\u25ba\u25bb\u25bc\u25bd\u25be\u25bf\u25c0\u25c1\u25c2\u25c3\u25c4\u25c5\u25c6\u25c7\u25c8\u25c9\u25ca\u25cb\uffee\u25cc\u25cd\u25ce\u25cf\u25d0\u25d1\u25d2\u25d3\u25d4\u25d5\u25d6\u25d7\u25d8\u25d9\u25da\u25db\u25dc\u25dd\u25de\u25df\u25e0\u25e1\u25e2\u25e3\u25e4\u25e5\u25e6\u25e7\u25e8\u25e9\u25ea\u25eb\u25ec\u25ed\u25ee\u25ef\u25f0\u25f1\u25f2\u25f3\u25f4\u25f5\u25f6\u25f7\u25f8\u25f9\u25fa\u25fb\u25fc\u25fd\u25fe\u25ff\u2600\u2601\u2602\u2603\u2604\u2605\u2606\u2607\u2608\u2609\u260a\u260b\u260c\u260d\u260e\u260f\u2610\u2611\u2612\u2613\u2614\u2615\u2616\u2617\u2618\u2619\u261a\u261b\u261c\u261d\u261e\u261f\u2620\u2621\u2622\u2623\u2624\u2625\u2626\u2627\u2628\u2629\u262a\u262b\u262c\u262d\u262e\u262f\u2638\u2639\u263a\u263b\u263c\u263d\u263e\u263f\u2640\u2641\u2642\u2643\u2644\u2645\u2646\u2647\u2648\u2649\u264a\u264b\u264c\u264d\u264e\u264f\u2650\u2651\u2652\u2653\u2654\u2655\u2656\u2657\u2658\u2659\u265a\u265b\u265c\u265d\u265e\u265f\u2660\u2661\u2662\u2663\u2664\u2665\u2666\u2667\u2668\u2669\u266a\u266b\u266c\u2670\u2671\u2672\u2673\u2674\u2675\u2676\u2677\u2678\u2679\u267a\u267b\u267c\u267d\u267e\u267f\u2680\u2681\u2682\u2683\u2684\u2685\u2686\u2687\u2688\u2689\u2690\u2691\u2692\u2693\u2694\u2695\u2696\u2697\u2698\u2699\u269a\u269b\u269c\u269d\u269e\u269f\u26a0\u26a1\u26a2\u26a3\u26a4\u26a5\u26a6\u26a7\u26a8\u26a9\u26aa\u26ab\u26ac\u26ad\u26ae\u26af\u26b0\u26b1\u26b2\u26b3\u26b4\u26b5\u26b6\u26b7\u26b8\u26b9\u26ba\u26bb\u26bc\u26bd\u26be\u26bf\u26c0\u26c1\u26c2\u26c3\u26c4\u26c5\u26c6\u26c7\u26c8\u26c9\u26ca\u26cb\u26cc\u26cd\u26ce\u26cf\u26d0\u26d1\u26d2\u26d3\u26d4\u26d5\u26d6\u26d7\u26d8\u26d9\u26da\u26db\u26dc\u26dd\u26de\u26df\u26e0\u26e1\u26e2\u26e3\u26e4\u26e5\u26e6\u26e7\u26e8\u26e9\u26ea\u26eb\u26ec\u26ed\u26ee\u26ef\u26f0\u26f1\u26f2\u26f3\u26f4\u26f5\u26f6\u26f7\u26f8\u26f9\u26fa\u26fb\u26fc\u26fd\u26fe\u26ff\u2700\u2701\u2702\u2703\u2704\u2705\u2706\u2707\u2708\u2709\u270a\u270b\u270c\u270d\u270e\u270f\u2710\u2711\u2712\u2713\u2714\u2715\u2716\u2717\u2718\u2719\u271a\u271b\u271c\u271d\u271e\u271f\u2720\u2e50\u2e51\u2721\u2722\u2723\u2724\u2725\u2726\u2727\u2728\u2729\u272a\u272b\u272c\u272d\u272e\u272f\u2730\u2731\u2732\u2733\u2734\u2735\u2736\u2737\u2738\u2739\u273a\u273b\u273c\u273d\u273e\u273f\u2740\u2741\u2742\u2743\u2744\u2745\u2746\u2747\u2748\u2749\u274a\u274b\u274c\u274d\u274e\u274f\u2750\u2751\u2752\u2753\u2754\u2755\u2756\u2757\u2758\u2759\u275a\u275b\u275c\u275d\u275e\u275f\u2760\u2761\u2762\u2763\u2764\u2765\u2766\u2767\u2794\u2795\u2796\u2797\u2798\u2799\u279a\u279b\u279c\u279d\u279e\u279f\u27a0\u27a1\u27a2\u27a3\u27a4\u27a5\u27a6\u27a7\u27a8\u27a9\u27aa\u27ab\u27ac\u27ad\u27ae\u27af\u27b0\u27b1\u27b2\u27b3\u27b4\u27b5\u27b6\u27b7\u27b8\u27b9\u27ba\u27bb\u27bc\u27bd\u27be\u27bf\u27c0\u27c1\u27c2\u27c3\u27c4\u27c7\u27c8\u27c9\u27ca\u27cb\u27cc\u27cd\u27ce\u27cf\u27d0\u27d1\u27d2\u27d3\u27d4\u27d5\u27d6\u27d7\u27d8\u27d9\u27da\u27db\u27dc\u27dd\u27de\u27df\u27e0\u27e1\u27e2\u27e3\u27e4\u27e5\u27f0\u27f1\u27f2\u27f3\u27f4\u27f5\u27f6\u27f7\u27f8\u27f9\u27fa\u27fb\u27fc\u27fd\u27fe\u27ff\u2900\u2901\u2902\u2903\u2904\u2905\u2906\u2907\u2908\u2909\u290a\u290b\u290c\u290d\u290e\u290f\u2910\u2911\u2912\u2913\u2914\u2915\u2916\u2917\u2918\u2919\u291a\u291b\u291c\u291d\u291e\u291f\u2920\u2921\u2922\u2923\u2924\u2925\u2926\u2927\u2928\u2929\u292a\u292b\u292c\u292d\u292e\u292f\u2930\u2931\u2932\u2933\u2934\u2935\u2936\u2937\u2938\u2939\u293a\u293b\u293c\u293d\u293e\u293f\u2940\u2941\u2942\u2943\u2944\u2945\u2946\u2947\u2948\u2949\u294a\u294b\u294c\u294d\u294e\u294f\u2950\u2951\u2952\u2953\u2954\u2955\u2956\u2957\u2958\u2959\u295a\u295b\u295c\u295d\u295e\u295f\u2960\u2961\u2962\u2963\u2964\u2965\u2966\u2967\u2968\u2969\u296a\u296b\u296c\u296d\u296e\u296f\u2970\u2971\u2972\u2973\u2974\u2975\u2976\u2977\u2978\u2979\u297a\u297b\u297c\u297d\u297e\u297f\u2980\u2981\u2982\u2999\u299a\u299b\u299c\u299d\u299e\u299f\u29a0\u29a1\u29a2\u29a3\u29a4\u29a5\u29a6\u29a7\u29a8\u29a9\u29aa\u29ab\u29ac\u29ad\u29ae\u29af\u29b0\u29b1\u29b2\u29b3\u29b4\u29b5\u29b6\u29b7\u29b8\u29b9\u29ba\u29bb\u29bc\u29bd\u29be\u29bf\u29c0\u29c1\u29c2\u29c3\u29c4\u29c5\u29c6\u29c7\u29c8\u29c9\u29ca\u29cb\u29cc\u29cd\u29ce\u29cf\u29d0\u29d1\u29d2\u29d3\u29d4\u29d5\u29d6\u29d7\u29dc\u29dd\u29de\u29df\u29e0\u29e1\u29e2\u29e3\u29e4\u29e5\u29e6\u29e7\u29e8\u29e9\u29ea\u29eb\u29ec\u29ed\u29ee\u29ef\u29f0\u29f1\u29f2\u29f3\u29f4\u29f5\u29f6\u29f7\u29f8\u29f9\u29fa\u29fb\u29fe\u29ff\u2a00\u2a01\u2a02\u2a03\u2a04\u2a05\u2a06\u2a07\u2a08\u2a09\u2a0a\u2a0b\u2a0d\u2a0e\u2a0f\u2a10\u2a11\u2a12\u2a13\u2a14\u2a15\u2a16\u2a17\u2a18\u2a19\u2a1a\u2a1b\u2a1c\u2a1d\u2a1e\u2a1f\u2a20\u2a21\u2a22\u2a23\u2a24\u2a25\u2a26\u2a27\u2a28\u2a29\u2a2a\u2a2b\u2a2c\u2a2d\u2a2e\u2a2f\u2a30\u2a31\u2a32\u2a33\u2a34\u2a35\u2a36\u2a37\u2a38\u2a39\u2a3a\u2a3b\u2a3c\u2a3d\u2a3e\u2a3f\u2a40\u2a41\u2a42\u2a43\u2a44\u2a45\u2a46\u2a47\u2a48\u2a49\u2a4a\u2a4b\u2a4c\u2a4d\u2a4e\u2a4f\u2a50\u2a51\u2a52\u2a53\u2a54\u2a55\u2a56\u2a57\u2a58\u2a59\u2a5a\u2a5b\u2a5c\u2a5d\u2a5e\u2a5f\u2a60\u2a61\u2a62\u2a63\u2a64\u2a65\u2a66\u2a67\u2a68\u2a69\u2a6a\u2a6b\u2a6c\u2a6d\u2a6e\u2a6f\u2a70\u2a71\u2a72\u2a73\u2a77\u2a78\u2a79\u2a7a\u2a7b\u2a7c\u2a7d\u2a7e\u2a7f\u2a80\u2a81\u2a82\u2a83\u2a84\u2a85\u2a86\u2a87\u2a88\u2a89\u2a8a\u2a8b\u2a8c\u2a8d\u2a8e\u2a8f\u2a90\u2a91\u2a92\u2a93\u2a94\u2a95\u2a96\u2a97\u2a98\u2a99\u2a9a\u2a9b\u2a9c\u2a9d\u2a9e\u2a9f\u2aa0\u2aa1\u2aa2\u2aa3\u2aa4\u2aa5\u2aa6\u2aa7\u2aa8\u2aa9\u2aaa\u2aab\u2aac\u2aad\u2aae\u2aaf\u2ab0\u2ab1\u2ab2\u2ab3\u2ab4\u2ab5\u2ab6\u2ab7\u2ab8\u2ab9\u2aba\u2abb\u2abc\u2abd\u2abe\u2abf\u2ac0\u2ac1\u2ac2\u2ac3\u2ac4\u2ac5\u2ac6\u2ac7\u2ac8\u2ac9\u2aca\u2acb\u2acc\u2acd\u2ace\u2acf\u2ad0\u2ad1\u2ad2\u2ad3\u2ad4\u2ad5\u2ad6\u2ad7\u2ad8\u2ad9\u2ada\u2adb\u2add\u2adc\u2ade\u2adf\u2ae0\u2ae1\u2ae2\u2ae3\u2ae4\u2ae5\u2ae6\u2ae7\u2ae8\u2ae9\u2aea\u2aeb\u2aec\u2aed\u2aee\u2aef\u2af0\u2af1\u2af2\u2af3\u2af4\u2af5\u2af6\u2af7\u2af8\u2af9\u2afa\u2afb\u2afc\u2afd\u2afe\u2aff\u2b00\u2b01\u2b02\u2b03\u2b04\u2b05\u2b06\u2b07\u2b08\u2b09\u2b0a\u2b0b\u2b0c\u2b0d\u2b0e\u2b0f\u2b10\u2b11\u2b12\u2b13\u2b14\u2b15\u2b16\u2b17\u2b18\u2b19\u2b1a\u2b1b\u2b1c\u2b1d\u2b1e\u2b1f\u2b20\u2b21\u2b22\u2b23\u2b24\u2b25\u2b26\u2b27\u2b28\u2b29\u2b2a\u2b2b\u2b2c\u2b2d\u2b2e\u2b2f\u2b30\u2b31\u2b32\u2b33\u2b34\u2b35\u2b36\u2b37\u2b38\u2b39\u2b3a\u2b3b\u2b3c\u2b3d\u2b3e\u2b3f\u2b40\u2b41\u2b42\u2b43\u2b44\u2b45\u2b46\u2b47\u2b48\u2b49\u2b4a\u2b4b\u2b4c\u2b4d\u2b4e\u2b4f\u2b50\u2b51\u2b52\u2b53\u2b54\u2b55\u2b56\u2b57\u2b58\u2b59\u2b5a\u2b5b\u2b5c\u2b5d\u2b5e\u2b5f\u2b60\u2b61\u2b62\u2b63\u2b64\u2b65\u2b66\u2b67\u2b68\u2b69\u2b6a\u2b6b\u2b6c\u2b6d\u2b6e\u2b6f\u2b70\u2b71\u2b72\u2b73\u2b76\u2b77\u2b78\u2b79\u2b7a\u2b7b\u2b7c\u2b7d\u2b7e\u2b7f\u2b80\u2b81\u2b82\u2b83\u2b84\u2b85\u2b86\u2b87\u2b88\u2b89\u2b8a\u2b8b\u2b8c\u2b8d\u2b8e\u2b8f\u2b90\u2b91\u2b92\u2b93\u2b94\u2b95\u2b97\u2b98\u2b99\u2b9a\u2b9b\u2b9c\u2b9d\u2b9e\u2b9f\u2ba0\u2ba1\u2ba2\u2ba3\u2ba4\u2ba5\u2ba6\u2ba7\u2ba8\u2ba9\u2baa\u2bab\u2bac\u2bad\u2bae\u2baf\u2bb0\u2bb1\u2bb2\u2bb3\u2bb4\u2bb5\u2bb6\u2bb7\u2bb8\u2bb9\u2bba\u2bbb\u2bbc\u2bbd\u2bbe\u2bbf\u2bc0\u2bc1\u2bc2\u2bc3\u2bc4\u2bc5\u2bc6\u2bc7\u2bc8\u2bc9\u2bca\u2bcb\u2bcc\u2bcd\u2bce\u2bcf\u2bd0\u2bd1\u2bd2\u2bd3\u2bd4\u2bd5\u2bd6\u2bd7\u2bd8\u2bd9\u2bda\u2bdb\u2bdc\u2bdd\u2bde\u2bdf\u2be0\u2be1\u2be2\u2be3\u2be4\u2be5\u2be6\u2be7\u2be8\u2be9\u2bea\u2beb\u2bec\u2bed\u2bee\u2bef\u2bf0\u2bf1\u2bf2\u2bf3\u2bf4\u2bf5\u2bf6\u2bf7\u2bf8\u2bf9\u2bfa\u2bfb\u2bfc\u2bfd\u2bfe\u2bff\u2ce5\u2ce6\u2ce7\u2ce8\u2ce9\u2cea\u2800\u2801\u2802\u2803\u2804\u2805\u2806\u2807\u2808\u2809\u280a\u280b\u280c\u280d\u280e\u280f\u2810\u2811\u2812\u2813\u2814\u2815\u2816\u2817\u2818\u2819\u281a\u281b\u281c\u281d\u281e\u281f\u2820\u2821\u2822\u2823\u2824\u2825\u2826\u2827\u2828\u2829\u282a\u282b\u282c\u282d\u282e\u282f\u2830\u2831\u2832\u2833\u2834\u2835\u2836\u2837\u2838\u2839\u283a\u283b\u283c\u283d\u283e\u283f\u2840\u2841\u2842\u2843\u2844\u2845\u2846\u2847\u2848\u2849\u284a\u284b\u284c\u284d\u284e\u284f\u2850\u2851\u2852\u2853\u2854\u2855\u2856\u2857\u2858\u2859\u285a\u285b\u285c\u285d\u285e\u285f\u2860\u2861\u2862\u2863\u2864\u2865\u2866\u2867\u2868\u2869\u286a\u286b\u286c\u286d\u286e\u286f\u2870\u2871\u2872\u2873\u2874\u2875\u2876\u2877\u2878\u2879\u287a\u287b\u287c\u287d\u287e\u287f\u2880\u2881\u2882\u2883\u2884\u2885\u2886\u2887\u2888\u2889\u288a\u288b\u288c\u288d\u288e\u288f\u2890\u2891\u2892\u2893\u2894\u2895\u2896\u2897\u2898\u2899\u289a\u289b\u289c\u289d\u289e\u289f\u28a0\u28a1\u28a2\u28a3\u28a4\u28a5\u28a6\u28a7\u28a8\u28a9\u28aa\u28ab\u28ac\u28ad\u28ae\u28af\u28b0\u28b1\u28b2\u28b3\u28b4\u28b5\u28b6\u28b7\u28b8\u28b9\u28ba\u28bb\u28bc\u28bd\u28be\u28bf\u28c0\u28c1\u28c2\u28c3\u28c4\u28c5\u28c6\u28c7\u28c8\u28c9\u28ca\u28cb\u28cc\u28cd\u28ce\u28cf\u28d0\u28d1\u28d2\u28d3\u28d4\u28d5\u28d6\u28d7\u28d8\u28d9\u28da\u28db\u28dc\u28dd\u28de\u28df\u28e0\u28e1\u28e2\u28e3\u28e4\u28e5\u28e6\u28e7\u28e8\u28e9\u28ea\u28eb\u28ec\u28ed\u28ee\u28ef\u28f0\u28f1\u28f2\u28f3\u28f4\u28f5\u28f6\u28f7\u28f8\u28f9\u28fa\u28fb\u28fc\u28fd\u28fe\u28ff\u268a\u268b\u268c\u268d\u268e\u268f\u2630\u2631\u2632\u2633\u2634\u2635\u2636\u2637\u4dc0\u4dc1\u4dc2\u4dc3\u4dc4\u4dc5\u4dc6\u4dc7\u4dc8\u4dc9\u4dca\u4dcb\u4dcc\u4dcd\u4dce\u4dcf\u4dd0\u4dd1\u4dd2\u4dd3\u4dd4\u4dd5\u4dd6\u4dd7\u4dd8\u4dd9\u4dda\u4ddb\u4ddc\u4ddd\u4dde\u4ddf\u4de0\u4de1\u4de2\u4de3\u4de4\u4de5\u4de6\u4de7\u4de8\u4de9\u4dea\u4deb\u4dec\u4ded\u4dee\u4def\u4df0\u4df1\u4df2\u4df3\u4df4\u4df5\u4df6\u4df7\u4df8\u4df9\u4dfa\u4dfb\u4dfc\u4dfd\u4dfe\u4dff\ua490\ua491\ua492\ua493\ua494\ua495\ua496\ua497\ua498\ua499\ua49a\ua49b\ua49c\ua49d\ua49e\ua49f\ua4a0\ua4a1\ua4a2\ua4a3\ua4a4\ua4a5\ua4a6\ua4a7\ua4a8\ua4a9\ua4aa\ua4ab\ua4ac\ua4ad\ua4ae\ua4af\ua4b0\ua4b1\ua4b2\ua4b3\ua4b4\ua4b5\ua4b6\ua4b7\ua4b8\ua4b9\ua4ba\ua4bb\ua4bc\ua4bd\ua4be\ua4bf\ua4c0\ua4c1\ua4c2\ua4c3\ua4c4\ua4c5\ua4c6\u266d\u266e\u266f\u2ff0\u2ff1\u2ff2\u2ff3\u2ff4\u2ff5\u2ff6\u2ff7\u2ff8\u2ff9\u2ffa\u2ffb\u31c0\u31c1\u31c2\u31c3\u31c4\u31c5\u31c6\u31c7\u31c8\u31c9\u31ca\u31cb\u31cc\u31cd\u31ce\u31cf\u31d0\u31d1\u31d2\u31d3\u31d4\u31d5\u31d6\u31d7\u31d8\u31d9\u31da\u31db\u31dc\u31dd\u31de\u31df\u31e0\u31e1\u31e2\u31e3\u3004\u3012\u3036\u3013\u3020\u3037\u303e\u303f\u3190\u3191\u327f\ufffc\u09f4\u09f5\u09f6\u09f7\u09f8\u09f9\u0b72\u0b73\u0b74\u0b75\u0b76\u0b77\ua830\ua831\ua832\ua833\ua834\ua835\u0bf0\u0bf1\u0bf2\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u1372\u1373\u1374\u1375\u1376\u1377\u1378\u1379\u137a\u137b\u137c\u2180\u2181\u2182\u2186\u2187\u2188\u2cfd\u02d0\u02d1\u0971\u0e46\u0ec6\u17d7\u1aa7\ua9cf\ua9e6\uaa70\uaadd\uaaf3\uaaf4\u3005\u303b\u3031\u3032\u3033\u3034\u3035\u309d\u309e\u30fc\uff70\u30fd\u30fe\u00a4\u00a2\uffe0\$\uff04\ufe69\u00a3\uffe1\u00a5\uffe5\u058f\u060b\u07fe\u07ff\u09f2\u09f3\u09fb\u0af1\ua838\u0bf9\u0e3f\u17db\u20a0\u20a1\u20a2\u20a3\u20a4\u20a5\u20a6\u20a7\u20a9\uffe6\u20aa\u20ab\u20ac\u20ad\u20ae\u20af\u20b0\u20b1\u20b2\u20b3\u20b4\u20b5\u20b6\u20b7\u20b8\u20b9\u20ba\u20bb\u20bc\u20bd\u20be\u20bf\u0030\u0660\u06f0\u07c0\u0966\u09e6\u0a66\u0ae6\u0b66\u0be6\u0c66\u0c78\u0ce6\u0d66\u0de6\u0e50\u0ed0\u0f20\u1040\u1090\u17e0\u17f0\u1810\u1946\u19d0\u1a80\u1a90\u1b50\u1bb0\u1c40\u1c50\u3007\ua620\ua8d0\ua900\ua9d0\ua9f0\uaa50\uabf0\uff10\u0f33\u24ea\u24ff\u2070\u2080\u2189\u3358\u0031\u0661\u06f1\u07c1\u0967\u09e7\u0a67\u0ae7\u0b67\u0be7\u0c67\u0c79\u0c7c\u0ce7\u0d67\u0de7\u0e51\u0ed1\u0f21\u1041\u1091\u1369\u17e1\u17f1\u1811\u1947\u19d1\u19da\u1a81\u1a91\u1b51\u1bb1\u1c41\u1c51\u3021\ua621\ua8d1\ua901\ua9d1\ua9f1\uaa51\uabf1\uff11\u0f2a\u2460\u24f5\u2776\u2780\u278a\u00b9\u2081\u2488\u215f\u2152\u00bd\u2153\u00bc\u2155\u2159\u2150\u215b\u2151\u2469\u24fe\u277f\u2789\u2793\u3248\u2491\u33e9\u32c9\u3362\u246a\u24eb\u2492\u33ea\u32ca\u3363\u246b\u24ec\u2493\u33eb\u32cb\u3364\u246c\u24ed\u2494\u33ec\u3365\u246d\u24ee\u2495\u33ed\u3366\u246e\u24ef\u2496\u33ee\u3367\u246f\u24f0\u2497\u33ef\u3368\u2470\u24f1\u2498\u33f0\u3369\u2471\u24f2\u2499\u33f1\u336a\u2472\u24f3\u249a\u33f2\u336b\u33e0\u32c0\u3359\u0032\u0662\u06f2\u07c2\u0968\u09e8\u0a68\u0ae8\u0b68\u0be8\u0c68\u0c7a\u0c7d\u0ce8\u0d68\u0de8\u0e52\u0ed2\u0f22\u1042\u1092\u136a\u17e2\u17f2\u1812\u1948\u19d2\u1a82\u1a92\u1b52\u1bb2\u1c42\u1c52\u3022\ua622\ua8d2\ua902\ua9d2\ua9f2\uaa52\uabf2\uff12\u0f2b\u2461\u24f6\u2777\u2781\u278b\u00b2\u2082\u2489\u2154\u2156\u2473\u24f4\u3249\u249b\u33f3\u336c\u3251\u33f4\u336d\u3252\u33f5\u336e\u3253\u33f6\u336f\u3254\u33f7\u3370\u3255\u33f8\u3256\u33f9\u3257\u33fa\u3258\u33fb\u3259\u33fc\u33e1\u32c1\u335a\u0033\u0663\u06f3\u07c3\u0969\u09e9\u0a69\u0ae9\u0b69\u0be9\u0c69\u0c7b\u0c7e\u0ce9\u0d69\u0de9\u0e53\u0ed3\u0f23\u1043\u1093\u136b\u17e3\u17f3\u1813\u1949\u19d3\u1a83\u1a93\u1b53\u1bb3\u1c43\u1c53\u3023\ua623\ua8d3\ua903\ua9d3\ua9f3\uaa53\uabf3\uff13\u0f2c\u2462\u24f7\u2778\u2782\u278c\u00b3\u2083\u248a\u00be\u2157\u215c\u324a\u325a\u33fd\u325b\u33fe\u325c\u325d\u325e\u325f\u32b1\u32b2\u32b3\u32b4\u33e2\u32c2\u335b\u0034\u0664\u06f4\u07c4\u096a\u09ea\u0a6a\u0aea\u0b6a\u0bea\u0c6a\u0cea\u0d6a\u0dea\u0e54\u0ed4\u0f24\u1044\u1094\u136c\u17e4\u17f4\u1814\u194a\u19d4\u1a84\u1a94\u1b54\u1bb4\u1c44\u1c54\u3024\ua624\ua8d4\ua904\ua9d4\ua9f4\uaa54\uabf4\uff14\u0f2d\u2463\u24f8\u2779\u2783\u278d\u2074\u2084\u248b\u2158\u324b\u32b5\u32b6\u32b7\u32b8\u32b9\u32ba\u32bb\u32bc\u32bd\u32be\u33e3\u32c3\u335c\u0035\u0665\u06f5\u07c5\u096b\u09eb\u0a6b\u0aeb\u0b6b\u0beb\u0c6b\u0ceb\u0d6b\u0deb\u0e55\u0ed5\u0f25\u1045\u1095\u136d\u17e5\u17f5\u1815\u194b\u19d5\u1a85\u1a95\u1b55\u1bb5\u1c45\u1c55\u3025\ua625\ua8d5\ua905\ua9d5\ua9f5\uaa55\uabf5\uff15\u0f2e\u2464\u24f9\u277a\u2784\u278e\u2075\u2085\u248c\u215a\u215d\u324c\u32bf\u33e4\u32c4\u335d\u0036\u0666\u06f6\u07c6\u096c\u09ec\u0a6c\u0aec\u0b6c\u0bec\u0c6c\u0cec\u0d6c\u0dec\u0e56\u0ed6\u0f26\u1046\u1096\u136e\u17e6\u17f6\u1816\u194c\u19d6\u1a86\u1a96\u1b56\u1bb6\u1c46\u1c56\u2185\u3026\ua626\ua8d6\ua906\ua9d6\ua9f6\uaa56\uabf6\uff16\u0f2f\u2465\u24fa\u277b\u2785\u278f\u2076\u2086\u248d\u324d\u33e5\u32c5\u335e\u0037\u0667\u06f7\u07c7\u096d\u09ed\u0a6d\u0aed\u0b6d\u0bed\u0c6d\u0ced\u0d6d\u0ded\u0e57\u0ed7\u0f27\u1047\u1097\u136f\u17e7\u17f7\u1817\u194d\u19d7\u1a87\u1a97\u1b57\u1bb7\u1c47\u1c57\u3027\ua627\ua8d7\ua907\ua9d7\ua9f7\uaa57\uabf7\uff17\u0f30\u2466\u24fb\u277c\u2786\u2790\u2077\u2087\u248e\u215e\u324e\u33e6\u32c6\u335f\u0038\u0668\u06f8\u07c8\u096e\u09ee\u0a6e\u0aee\u0b6e\u0bee\u0c6e\u0cee\u0d6e\u0dee\u0e58\u0ed8\u0f28\u1048\u1098\u1370\u17e8\u17f8\u1818\u194e\u19d8\u1a88\u1a98\u1b58\u1bb8\u1c48\u1c58\u3028\ua628\ua8d8\ua908\ua9d8\ua9f8\uaa58\uabf8\uff18\u0f31\u2467\u24fc\u277d\u2787\u2791\u2078\u2088\u248f\u324f\u33e7\u32c7\u3360\u0039\u0669\u06f9\u07c9\u096f\u09ef\u0a6f\u0aef\u0b6f\u0bef\u0c6f\u0cef\u0d6f\u0def\u0e59\u0ed9\u0f29\u1049\u1099\u1371\u17e9\u17f9\u1819\u194f\u19d9\u1a89\u1a99\u1b59\u1bb9\u1c49\u1c59\u3029\ua629\ua8d9\ua909\ua9d9\ua9f9\uaa59\uabf9\uff19\u0f32\u2468\u24fd\u277e\u2788\u2792\u2079\u2089\u2490\u33e8\u32c8\u3361\u0061\uff41\u0363\u24d0\u0041\uff21\u24b6\u00aa\u1d43\u2090\u1d2c\u00e1\u00c1\u00e0\u00c0\u0103\u0102\u00e2\u00c2\u01ce\u01cd\u00e5\u00c5\u212b\u00e4\u1df2\ua79b\u00c4\ua79a\u00e3\u00c3\u0227\u0226\u0105\u0104\u0101\u0100\u1ea3\u1ea2\u0201\u0200\u0203\u0202\u1ea1\u1ea0\u1e01\u1e00\u1dd3\u1eaf\u1eae\u1eb1\u1eb0\u1eb5\u1eb4\u1eb3\u1eb2\u1ea5\u1ea4\u1ea7\u1ea6\u1eab\u1eaa\u1ea9\u1ea8\u01fb\u01fa\u01df\u01de\u01e1\u01e0\u1eb7\u1eb6\u1ead\u1eac\u00e6\u1dd4\u00c6\u1d2d\u01fd\u01fc\u01e3\u01e2\ua73b\ua73a\u33c2\u2100\u2101\u214d\u33df\ua733\ua732\u1dd5\ua735\ua734\ua737\ua736\u3373\u1dd6\ua739\ua738\ua73d\ua73c\u1e9a\u1d00\u2c65\u023a\u1d8f\ua7bb\ua7ba\u1d01\u1d02\u1d46\uab31\u0250\u2c6f\u1d44\u0251\u1de7\u2c6d\u1d45\uab30\u1d90\u0252\u2c70\u1d9b\uab64\u0062\uff42\u1de8\u24d1\u0042\uff22\u212c\u24b7\u1d47\u1d2e\u1e03\u1e02\u1e05\u1e04\u1e07\u1e06\u3374\u33c3\u0299\u0180\u0243\u1d2f\u1d03\u1d6c\ua797\ua796\u1d80\u0253\u0181\u0183\u0182\ua7b5\u1de9\ua7b4\u0063\uff43\u0368\u217d\u24d2\u0043\uff23\u216d\u2102\u212d\u24b8\u1d9c\u0107\u0106\u0109\u0108\u010d\u010c\u010b\u010a\u00e7\u1dd7\u00c7\u1e09\u1e08\u2105\u2106\u33c6\u3388\u33c4\u33c5\u339d\u33a0\u33a4\u33c7\u1d04\u023c\u023b\ua793\ua792\ua794\ua7c4\u0188\u0187\u0255\u1d9d\u2184\u2183\ua73f\ua73e\u0064\uff44\u0369\u217e\u2146\u24d3\u0044\uff24\u216e\u2145\u24b9\u1d48\u1d30\u010f\u010e\u1e0b\u1e0a\u1e11\u1e10\u0111\u0110\u1e0d\u1e0c\u1e13\u1e12\u1e0f\u1e0e\u00f0\u1dd9\u00d0\u1d9e\u1dd8\ua77a\ua779\u3372\u0238\u33c8\u3397\u3377\u3378\u3379\u01f3\u02a3\u01f2\u01f1\u01c6\u01c5\u01c4\uab66\u02a5\u02a4\u1d05\u1d06\ua7c8\ua7c7\u1d6d\u1d81\u0256\u0189\u0257\u018a\u1d91\u018c\u018b\u0221\ua771\u1e9f\u0065\uff45\u0364\u212f\u2147\u24d4\u0045\uff25\u2130\u24ba\u1d49\u2091\u1d31\u00e9\u00c9\u00e8\u00c8\u0115\u0114\u00ea\u00ca\u011b\u011a\u00eb\u00cb\u1ebd\u1ebc\u0117\u0116\u0229\u0228\u0119\u0118\u0113\u0112\u1ebb\u1eba\u0205\u0204\u0207\u0206\u1eb9\u1eb8\u1e19\u1e18\u1e1b\u1e1a\u1ebf\u1ebe\u1ec1\u1ec0\u1ec5\u1ec4\u1ec3\u1ec2\u1e1d\u1e1c\u1e17\u1e16\u1e15\u1e14\u1ec7\u1ec6\u32cd\u32ce\u1d07\uab32\uab33\u0247\u0246\u1d92\uab34\u2c78\u01dd\u018e\u1d32\u2c7b\u0259\u1dea\u018f\u1d4a\u2094\u1d95\u025b\u0190\u2107\u1d4b\u1d93\u0258\u025a\u025c\ua7ab\u1d9f\u1d94\u1d08\u1d4c\u025d\u025e\u029a\u0264\u0066\uff46\u1deb\u24d5\u0046\uff26\u2131\u24bb\u1da0\u1e1f\u1e1e\ua77c\ua77b\u213b\ufb00\ufb03\ufb04\ufb01\ufb02\u3399\u02a9\ua730\uab35\ua799\ua798\u1d6e\u1d82\u0192\u0191\u214e\u2132\ua7fb\u0067\uff47\u1dda\u210a\u24d6\u0047\uff27\u24bc\u1d4d\u1d33\u01f5\u01f4\u011f\u011e\u011d\u011c\u01e7\u01e6\u0121\u0120\u0123\u0122\u1e21\u1e20\ua7a1\ua7a0\u1d79\ua77d\u33ff\u3387\u3393\u33ac\u33c9\u0261\ua7ac\u1da2\uab36\u0262\u1ddb\u01e5\u01e4\u1d83\u0260\u0193\u029b\u1d77\ua77f\ua77e\u0263\u0194\u02e0\u01a3\u01a2\u0068\uff48\u036a\u210e\u24d7\u0048\uff28\u210b\u210c\u210d\u24bd\u02b0\u2095\u1d34\u0125\u0124\u021f\u021e\u1e27\u1e26\u1e23\u1e22\u1e29\u1e28\u0127\u210f\u0126\ua7f8\u1e25\u1e24\u1e2b\u1e2a\u1e96\u33ca\u32cc\u33cb\u3371\u3390\u029c\u0195\u01f6\ua795\u0266\ua7aa\u02b1\u2c68\u2c67\u2c76\u2c75\ua7f6\ua7f5\ua727\ua726\uab5c\u0267\u02bb\u02bd\u0069\uff49\u0365\u2170\u2139\u2148\u24d8\u0049\uff29\u2160\u2110\u2111\u24be\u2071\u1d62\u1d35\u00ed\u00cd\u00ec\u00cc\u012d\u012c\u00ee\u00ce\u01d0\u01cf\u00ef\u00cf\u0129\u0128\u0130\u012f\u012e\u012b\u012a\u1ec9\u1ec8\u0209\u0208\u020b\u020a\u1ecb\u1eca\u1e2d\u1e2c\u1e2f\u1e2e\u2171\u2161\u2172\u2162\u0133\u0132\u33cc\u337a\u2173\u2163\u2178\u2168\u0131\u026a\ua7ae\u1da6\ua7fe\ua7f7\u1d09\u1d4e\u0268\u0197\u1da4\u1d7b\u1da7\u1d96\ua7bd\ua7bc\u0269\u0196\u1da5\u1d7c\u006a\uff4a\u2149\u24d9\u004a\uff2a\u24bf\u02b2\u2c7c\u1d36\u0135\u0134\u01f0\u0237\u1d0a\u0249\u0248\u029d\ua7b2\u1da8\u025f\u1da1\u0284\u006b\uff4b\u1ddc\u24da\u004b\u212a\uff2b\u24c0\u1d4f\u2096\u1d37\u1e31\u1e30\u01e9\u01e8\u0137\u0136\ua7a3\ua7a2\u1e33\u1e32\u1e35\u1e34\u3384\u3385\u3389\u338f\u3391\u33cd\u3398\u339e\u33ce\u33a2\u33a6\u33aa\u33cf\u33b8\u33be\u33c0\u1d0b\u1d84\u0199\u0198\u2c6a\u2c69\ua741\ua740\ua743\ua742\ua745\ua744\u029e\ua7b0\u006c\uff4c\u1ddd\u217c\u2113\u24db\u004c\uff2c\u216c\u2112\u24c1\u02e1\u2097\u1d38\u013a\u0139\u013e\u013d\u013c\u013b\u0142\u0141\u1e37\u1e36\u1e3d\u1e3c\u1e3b\u1e3a\u0140\u013f\u1e39\u1e38\u01c9\u01c8\u01c7\u1efb\u1efa\u33d0\u33d1\u33d2\u02aa\u32cf\u33d3\u02ab\u029f\u1dde\u1dab\ua747\ua746\u1d0c\ua749\ua748\u019a\u023d\u2c61\u2c60\u026b\u2c62\uab5e\uab38\u1dec\uab39\u026c\ua7ad\uab37\uab5d\u1d85\u1daa\u026d\u1da9\ua78e\u0234\ua772\u026e\ua781\ua780\u019b\u028e\u006d\uff4d\u036b\u217f\u24dc\u004d\uff2d\u216f\u2133\u24c2\u1d50\u2098\u1d39\u1e3f\u1e3e\u1e41\u1e40\u1e43\u1e42\u33a7\u33a8\u33a1\u33a5\u3383\u33d4\u3386\u338e\u3392\u33d5\u3396\u339c\u339f\u33a3\u33d6\u33ab\u33b3\u33b7\u33b9\u33bd\u33bf\u33c1\u1d0d\u1ddf\u1d6f\u1d86\u0271\u2c6e\u1dac\uab3a\ua7fd\ua7ff\ua773\u006e\uff4e\u1de0\u24dd\u004e\uff2e\u2115\u24c3\u207f\u2099\u1d3a\u0144\u0143\u01f9\u01f8\u0148\u0147\u00f1\u00d1\u1e45\u1e44\u0146\u0145\ua7a5\ua7a4\u1e47\u1e46\u1e4b\u1e4a\u1e49\u1e48\u3381\u338b\u01cc\u01cb\u01ca\u339a\u2116\u33b1\u33b5\u33bb\u0274\u1de1\u1db0\u1d3b\u1d0e\u1d70\u0272\u019d\u1dae\u019e\u0220\ua791\ua790\u1d87\u0273\u1daf\u0235\uab3b\ua774\u014b\u014a\u1d51\uab3c\u006f\uff4f\u0366\u2134\u24de\u004f\uff2f\u24c4\u00ba\u1d52\u2092\u1d3c\u00f3\u00d3\u00f2\u00d2\u014f\u014e\u00f4\u00d4\u01d2\u01d1\u00f6\u1df3\ua79d\u00d6\ua79c\u0151\u0150\u00f5\u00d5\u022f\u022e\u00f8\u00d8\u01eb\u01ea\u014d\u014c\u1ded\u1ecf\u1ece\u020d\u020c\u020f\u020e\u01a1\u01a0\u1ecd\u1ecc\u1ed1\u1ed0\u1ed3\u1ed2\u1ed7\u1ed6\u1ed5\u1ed4\u022b\u022a\u1e4d\u1e4c\u1e4f\u1e4e\u022d\u022c\u0231\u0230\u01ff\u01fe\u01ed\u01ec\u1e53\u1e52\u1e51\u1e50\u1edb\u1eda\u1edd\u1edc\u1ee1\u1ee0\u1edf\u1ede\u1ee3\u1ee2\u1ed9\u1ed8\u0153\u0152\ua7f9\ua74f\ua74e\u3375\u1d0f\u1d11\uab3d\u0276\u1d14\uab41\uab42\uab40\uab43\uab44\u1d13\uab3e\u0254\u0186\u1d53\u1d10\u1d12\uab3f\u1d97\uab62\ua74d\ua74c\u1d16\u1d54\u1d17\u1d55\u2c7a\u0275\u019f\u1db1\ua74b\ua74a\u0277\ua7b7\ua7b6\u0223\u0222\u1d3d\u1d15\u0070\uff50\u1dee\u24df\u0050\uff30\u2119\u24c5\u1d56\u209a\u1d3e\u1e55\u1e54\u1e57\u1e56\u33d8\u3380\u33a9\u3376\u338a\u33d7\u33d9\u33da\u33b0\u3250\u33b4\u33ba\u1d18\u1d7d\u2c63\ua751\ua750\u1d71\u1d88\u01a5\u01a4\ua753\ua752\ua755\ua754\ua7fc\u0278\u1db2\u2c77\u0071\uff51\u24e0\u0051\uff31\u211a\u24c6\u0239\ua7af\ua757\ua756\ua759\ua758\u02a0\u024b\u024a\u0138\u0072\uff52\u036c\u1dca\u24e1\u0052\uff32\u211b\u211c\u211d\u24c7\u02b3\u1d63\u1d3f\u0155\u0154\u0159\u0158\u1e59\u1e58\u0157\u0156\ua7a7\ua7a6\u0211\u0210\u0213\u0212\u1e5b\u1e5a\u1e5f\u1e5e\ua783\ua782\u1e5d\u1e5c\u33ad\u33ae\u33af\u20a8\uab45\u0280\u1de2\u01a6\uab46\ua75b\u1de3\ua75a\u1d19\u024d\u024c\u1d72\u0279\u02b4\u1d1a\u027a\u1d89\u027b\u02b5\u2c79\u027c\u027d\u2c64\uab49\u027e\u1d73\u027f\uab47\uab48\uab4a\uab4b\uab4c\u0281\u02b6\uab68\ua775\ua776\ua75d\ua75c\u0073\uff53\u1de4\u24e2\u0053\uff33\u24c8\u02e2\u209b\u015b\u015a\u015d\u015c\u0161\u0160\u1e61\u1e60\u015f\u015e\ua7a9\ua7a8\u1e63\u1e62\u0219\u0218\u017f\u1de5\ua785\ua784\u1e65\u1e64\u1e67\u1e66\u1e69\u1e68\u1e9b\u00df\u1e9e\ufb05\u2120\u33db\ufb06\u33dc\ua731\ua7ca\ua7c9\u1d74\u1d8a\u0282\ua7c5\u1db3\u023f\u2c7e\u1e9c\u1e9d\u0283\u1def\u01a9\u1db4\uab4d\u1d8b\u01aa\u0285\u1d98\u0286\u0074\uff54\u036d\u24e3\u0054\uff34\u24c9\u1d57\u209c\u1d40\u0165\u0164\u1e97\u1e6b\u1e6a\u0163\u0162\u1e6d\u1e6c\u021b\u021a\u1e71\u1e70\u1e6f\u1e6e\ua787\ua786\u1d7a\u02a8\u2121\u3394\u2122\u01be\u02a6\uab67\u02a7\ua729\ua728\u1d1b\u0167\u0166\u2c66\u023e\u1d75\u01ab\u1db5\u01ad\u01ac\u0288\u01ae\u0236\ua777\u0287\ua7b1\u0075\uff55\u0367\u24e4\u0055\uff35\u24ca\u1d58\u1d64\u1d41\u00fa\u00da\u00f9\u00d9\u016d\u016c\u00fb\u00db\u01d4\u01d3\u016f\u016e\u00fc\u1df4\ua79f\u00dc\ua79e\u0171\u0170\u0169\u0168\u0173\u0172\u016b\u016a\u1df0\u1ee7\u1ee6\u0215\u0214\u0217\u0216\u01b0\u01af\u1ee5\u1ee4\u1e73\u1e72\u1e77\u1e76\u1e75\u1e74\u01d8\u01d7\u01dc\u01db\u01da\u01d9\u01d6\u01d5\u1e79\u1e78\u1e7b\u1e7a\u1ee9\u1ee8\u1eeb\u1eea\u1eef\u1eee\u1eed\u1eec\u1ef1\u1ef0\u1d1c\u1db8\uab4e\u1d1d\u1d59\u1d1e\u1d6b\uab50\uab51\u0289\u0244\u1db6\uab4f\ua7b9\ua7b8\u1d7e\u1d99\uab52\uab5f\ua7bf\ua7be\u0265\ua78d\u1da3\u02ae\u02af\u026f\u019c\u1d5a\ua7fa\u1d1f\u0270\u1dad\u028a\u01b1\u1db7\u1d7f\u0076\uff56\u036e\u2174\u24e5\u0056\uff36\u2164\u24cb\u1d5b\u1d65\u2c7d\u1e7d\u1e7c\u1e7f\u1e7e\u33de\u2175\u2165\u2176\u2166\u2177\u2167\ua761\ua760\u1d20\ua75f\ua75e\u1d8c\u028b\u01b2\u1db9\u2c71\u2c74\u1efd\u1efc\u028c\u0245\u1dba\u0077\uff57\u1abf\u1df1\u24e6\u0057\uff37\u24cc\u02b7\u1d42\u1e83\u1e82\u1e81\u1e80\u0175\u0174\u1e98\u1e85\u1e84\u1e87\u1e86\u1e89\u1e88\u33dd\u1d21\ua7c3\ua7c2\u2c73\u2c72\u028d\u1ac0\uab69\u0078\uff58\u036f\u2179\u24e7\u0058\uff38\u2169\u24cd\u02e3\u2093\u1e8d\u1e8c\u1e8b\u1e8a\u217a\u216a\u217b\u216b\u1d8d\uab56\uab57\uab58\uab59\uab53\ua7b3\uab54\uab55\u0079\uff59\u24e8\u0059\uff39\u24ce\u02b8\u00fd\u00dd\u1ef3\u1ef2\u0177\u0176\u1e99\u00ff\u0178\u1ef9\u1ef8\u1e8f\u1e8e\u0233\u0232\u1ef7\u1ef6\u1ef5\u1ef4\u028f\u024f\u024e\u01b4\u01b3\u1eff\u1efe\uab5a\u021d\u021c\u007a\uff5a\u1de6\u24e9\u005a\uff3a\u2124\u2128\u24cf\u1dbb\u017a\u0179\u1e91\u1e90\u017e\u017d\u017c\u017b\u1e93\u1e92\u1e95\u1e94\u018d\u1d22\u01b6\u01b5\u1d76\u1d8e\ua7c6\u0225\u0224\u0290\u1dbc\u0291\u1dbd\u0240\u2c7f\u2c6c\u2c6b\ua763\ua762\u0292\u01b7\u1dbe\u01ef\u01ee\u1d23\u01b9\u01b8\u1d9a\u01ba\u0293\u00fe\u00de\ua765\ua764\ua767\ua766\u01bf\u01f7\ua769\ua768\uab60\uab61\uab63\ua76b\ua76a\ua76d\ua76c\ua76f\u1dd2\ua76e\ua770\ua778\u01bb\ua72b\ua72a\ua72d\ua72c\ua72f\ua72e\u01a8\u01a7\u01bd\u01bc\u0185\u0184\u0294\u0242\u0241\u02c0\u02bc\u0149\u02ee\u02be\ua723\ua722\ua78c\ua78b\ua78f\u0295\u02e4\u02bf\u02c1\u1d24\u1d25\u1d5c\ua725\ua724\u02a1\u02a2\u0296\u01c0\u01c1\u01c2\u01c3\u0297\u0298\u02ac\u02ad\u03b1\u0391\u1f00\u1f08\u1f01\u1f09\u03ac\u1f71\u0386\u1fbb\u1f70\u1fba\u1fb0\u1fb8\u1fb6\u1fb1\u1fb9\u1fb3\u1fbc\u1f04\u1f0c\u1f02\u1f0a\u1f06\u1f0e\u1f80\u1f88\u1f05\u1f0d\u1f03\u1f0b\u1f07\u1f0f\u1f81\u1f89\u1fb4\u1fb2\u1fb7\u1f84\u1f8c\u1f82\u1f8a\u1f86\u1f8e\u1f85\u1f8d\u1f83\u1f8b\u1f87\u1f8f\u03b2\u03d0\u0392\u1d5d\u1d66\u03b3\u213d\u0393\u213e\u1d5e\u1d67\u1d26\u03b4\u0394\u1d5f\u03b5\u03f5\u0395\u1f10\u1f18\u1f11\u1f19\u03ad\u1f73\u0388\u1fc9\u1f72\u1fc8\u1f14\u1f1c\u1f12\u1f1a\u1f15\u1f1d\u1f13\u1f1b\u03dd\u03dc\u0377\u0376\u03db\u03da\u03b6\u0396\u0371\u0370\u03b7\u0397\u1f20\u1f28\u1f21\u1f29\u03ae\u1f75\u0389\u1fcb\u1f74\u1fca\u1fc6\u1fc3\u1fcc\u1f24\u1f2c\u1f22\u1f2a\u1f26\u1f2e\u1f90\u1f98\u1f25\u1f2d\u1f23\u1f2b\u1f27\u1f2f\u1f91\u1f99\u1fc4\u1fc2\u1fc7\u1f94\u1f9c\u1f92\u1f9a\u1f96\u1f9e\u1f95\u1f9d\u1f93\u1f9b\u1f97\u1f9f\u03b8\u03d1\u0398\u03f4\u1dbf\u03b9\u1fbe\u037a\u0399\u1f30\u1f38\u1f31\u1f39\u03af\u1f77\u038a\u1fdb\u1f76\u1fda\u1fd0\u1fd8\u1fd6\u03ca\u03aa\u1fd1\u1fd9\u1f34\u1f3c\u1f32\u1f3a\u1f36\u1f3e\u1f35\u1f3d\u1f33\u1f3b\u1f37\u1f3f\u0390\u1fd3\u1fd2\u1fd7\u03f3\u037f\u03ba\u03f0\u039a\u03d7\u03cf\u03bb\u039b\u1d27\u03bc\u00b5\u039c\u3382\u338c\u338d\u3395\u339b\u33b2\u33b6\u33bc\u03bd\u039d\u03be\u039e\u03bf\u039f\u1f40\u1f48\u1f41\u1f49\u03cc\u1f79\u038c\u1ff9\u1f78\u1ff8\u1f44\u1f4c\u1f42\u1f4a\u1f45\u1f4d\u1f43\u1f4b\u03c0\u03d6\u213c\u03a0\u213f\u1d28\u03fb\u03fa\u03df\u03de\u03d9\u03d8\u03c1\u03f1\u03a1\u1d68\u1fe4\u1fe5\u1fec\u1d29\u03fc\u03c3\u03f2\u03a3\u03f9\u03c2\u037c\u03fe\u037b\u03fd\u037d\u03ff\u03c4\u03a4\u03c5\u03a5\u03d2\u1f50\u1f51\u1f59\u03cd\u1f7b\u038e\u1feb\u03d3\u1f7a\u1fea\u1fe0\u1fe8\u1fe6\u03cb\u03ab\u03d4\u1fe1\u1fe9\u1f54\u1f52\u1f56\u1f55\u1f5d\u1f53\u1f5b\u1f57\u1f5f\u03b0\u1fe3\u1fe2\u1fe7\u03c6\u03d5\u03a6\u1d60\u1d69\u03c7\u03a7\u1d61\u1d6a\u03c8\u03a8\u1d2a\u03c9\u03a9\u2126\u1f60\u1f68\u1f61\u1f69\u03ce\u1f7d\u038f\u1ffb\u1f7c\u1ffa\u1ff6\u1ff3\u1ffc\u1f64\u1f6c\u1f62\u1f6a\u1f66\u1f6e\u1fa0\u1fa8\u1f65\u1f6d\u1f63\u1f6b\u1f67\u1f6f\u1fa1\u1fa9\u1ff4\u1ff2\u1ff7\u1fa4\u1fac\u1fa2\u1faa\u1fa6\u1fae\u1fa5\u1fad\u1fa3\u1fab\u1fa7\u1faf\uab65\u03e1\u03e0\u0373\u0372\u03f8\u03f7\u2c81\u2c80\u2c83\u2c82\u2c85\u2c84\u2c87\u2c86\u2c89\u2c88\u2cb7\u2cb6\u2c8b\u2c8a\u2c8d\u2c8c\u2c8f\u2c8e\u2c91\u2c90\u2c93\u2c92\u2c95\u2c94\u2ce4\u2cb9\u2cb8\u2c97\u2c96\u2c99\u2c98\u2c9b\u2c9a\u2cbb\u2cba\u2cbd\u2cbc\u2c9d\u2c9c\u2c9f\u2c9e\u2ca1\u2ca0\u2ca3\u2ca2\u2ca5\u2ca4\u2ca7\u2ca6\u2ca9\u2ca8\u2cab\u2caa\u2cad\u2cac\u2caf\u2cae\u2cb1\u2cb0\u2cbf\u2cbe\u2cc1\u2cc0\u03e3\u03e2\u2cec\u2ceb\u2cc3\u2cc2\u2cc5\u2cc4\u2cc7\u2cc6\u03e5\u03e4\u03e7\u03e6\u2cf3\u2cf2\u2cc9\u2cc8\u03e9\u03e8\u2ccb\u2cca\u2ccd\u2ccc\u2ccf\u2cce\u2cd1\u2cd0\u2cd3\u2cd2\u2cd5\u2cd4\u03eb\u03ea\u2cee\u2ced\u2cd7\u2cd6\u03ed\u03ec\u2cd9\u2cd8\u2cdb\u2cda\u2cdd\u2cdc\u03ef\u03ee\u2cb3\u2cb2\u2cb5\u2cb4\u2cdf\u2cde\u2ce1\u2ce0\u2ce3\u2ce2\u0430\u2df6\u0410\u04d1\u04d0\u04d3\u04d2\u04d9\u04d8\u04db\u04da\u04d5\u04d4\u0431\u2de0\u0411\u0432\u1c80\u2de1\u0412\u0433\u2de2\u0413\u0453\u0403\u0491\u0490\u0493\u0492\u04fb\u04fa\u0495\u0494\u04f7\u04f6\u0434\u1c81\u2de3\u0414\u0501\u0500\ua681\ua680\u0452\u0402\ua663\ua662\u0503\u0502\u0499\u0498\u0435\u2df7\u0415\u0450\u0400\u04d7\u04d6\u0451\u0401\u0454\ua674\u0404\u0436\u2de4\u0416\u04c2\u04c1\u04dd\u04dc\u052b\u052a\ua685\ua684\u0497\u0496\u0437\u2de5\u0417\u04df\u04de\ua641\ua640\u0505\u0504\u0511\u0510\ua643\ua642\u0455\u0405\ua645\ua644\u04e1\u04e0\ua689\ua688\u0507\u0506\ua683\ua682\u0438\ua675\u0418\u045d\u040d\u04e5\u04e4\u04e3\u04e2\u048b\u048a\u0456\u0406\u0457\ua676\u0407\ua647\ua646\u0439\u0419\u0458\u0408\ua649\u2df8\ua648\u043a\u2de6\u041a\u045c\u040c\u049b\u049a\u04c4\u04c3\u04a1\u04a0\u049f\u049e\u049d\u049c\u051f\u051e\u051b\u051a\u043b\u2de7\u041b\u1d2b\u04c6\u04c5\u052f\u052e\u0513\u0512\u0521\u0520\u0459\u0409\ua665\ua664\u0509\u0508\u0515\u0514\u043c\u2de8\u041c\u04ce\u04cd\ua667\ua666\u043d\u2de9\u041d\u1d78\u0529\u0528\u04ca\u04c9\u04a3\u04a2\u04c8\u04c7\u0523\u0522\u04a5\u04a4\u045a\u040a\u050b\u050a\u043e\u1c82\u2dea\ua669\ua66b\ua66d\ua66e\ua699\ua69b\u041e\ua668\ua66a\ua66c\ua698\ua69a\u04e7\u04e6\u04e9\u04e8\u04eb\u04ea\u043f\u2deb\u041f\u0525\u0524\u04a7\u04a6\u0481\u0480\u0440\u2dec\u0420\u048f\u048e\u0517\u0516\u0441\u1c83\u2ded\u0421\u2df5\u050d\u050c\u04ab\u04aa\u0442\u1c84\u1c85\u2dee\u0422\ua68d\ua68c\u050f\u050e\u04ad\u04ac\ua68b\ua68a\u045b\u040b\u0443\ua677\u0423\u045e\u040e\u04f1\u04f0\u04f3\u04f2\u04ef\u04ee\u04af\u04ae\u04b1\u04b0\ua64b\u1c88\u2df9\ua64a\u0479\u0478\u0444\ua69e\u0424\u0445\u2def\u0425\u04fd\u04fc\u04ff\u04fe\u04b3\u04b2\u04bb\u04ba\u0527\u0526\ua695\ua694\u0461\ua67b\u0460\u047f\u047e\ua64d\ua64c\u047d\u047c\u047b\u047a\u0446\u2df0\u0426\ua661\ua660\ua68f\ua68e\u04b5\u04b4\ua691\ua690\u0447\u2df1\u0427\u04f5\u04f4\u052d\u052c\ua693\ua692\u04b7\u04b6\u04cc\u04cb\u04b9\u04b8\ua687\ua686\u04bd\u04bc\u04bf\u04be\u045f\u040f\u0448\u2df2\u0428\ua697\ua696\u0449\u2df3\u0429\ua64f\ua64e\u2e2f\ua67f\u044a\u1c86\ua678\u042a\ua69c\ua651\ua650\u044b\ua679\u042b\u04f9\u04f8\u044c\ua67a\u042c\ua69d\u048d\u048c\u0463\u1c87\u2dfa\u0462\ua653\ua652\u044d\u042d\u04ed\u04ec\u044e\u2dfb\u042e\ua655\ua654\ua657\u2dfc\ua656\u044f\u042f\u0519\u0518\u0465\ua69f\u0464\u0467\u2dfd\u0466\ua659\ua658\u046b\u2dfe\u046a\ua65b\ua65a\u0469\u0468\ua65d\ua65c\u046d\u2dff\u046c\u046f\u046e\u0471\u0470\u0473\u2df4\u0472\u0475\u0474\u0477\u0476\ua65f\ua65e\u04a9\u04a8\u051d\u051c\u04cf\u04c0\u2c30\u2c00\u2c31\u2c01\u2c32\u2c02\u2c33\u2c03\u2c34\u2c04\u2c35\u2c05\u2c36\u2c06\u2c37\u2c07\u2c38\u2c08\u2c39\u2c09\u2c3a\u2c0a\u2c3b\u2c0b\u2c3c\u2c0c\u2c3d\u2c0d\u2c3e\u2c0e\u2c3f\u2c0f\u2c40\u2c10\u2c41\u2c11\u2c42\u2c12\u2c43\u2c13\u2c44\u2c14\u2c45\u2c15\u2c46\u2c16\u2c47\u2c17\u2c48\u2c18\u2c49\u2c19\u2c4a\u2c1a\u2c4b\u2c1b\u2c4c\u2c1c\u2c4d\u2c1d\u2c4e\u2c1e\u2c4f\u2c1f\u2c50\u2c20\u2c51\u2c21\u2c52\u2c22\u2c53\u2c23\u2c54\u2c24\u2c55\u2c25\u2c56\u2c26\u2c57\u2c27\u2c58\u2c28\u2c59\u2c29\u2c5a\u2c2a\u2c5b\u2c2b\u2c5c\u2c2c\u2c5d\u2c2d\u2c5e\u2c2e\u10d0\u1c90\u2d00\u10a0\u10d1\u1c91\u2d01\u10a1\u10d2\u1c92\u2d02\u10a2\u10d3\u1c93\u2d03\u10a3\u10d4\u1c94\u2d04\u10a4\u10d5\u1c95\u2d05\u10a5\u10d6\u1c96\u2d06\u10a6\u10f1\u1cb1\u2d21\u10c1\u10d7\u1c97\u2d07\u10a7\u10d8\u1c98\u2d08\u10a8\u10d9\u1c99\u2d09\u10a9\u10da\u1c9a\u2d0a\u10aa\u10db\u1c9b\u2d0b\u10ab\u10dc\u1c9c\u10fc\u2d0c\u10ac\u10f2\u1cb2\u2d22\u10c2\u10dd\u1c9d\u2d0d\u10ad\u10de\u1c9e\u2d0e\u10ae\u10df\u1c9f\u2d0f\u10af\u10e0\u1ca0\u2d10\u10b0\u10e1\u1ca1\u2d11\u10b1\u10e2\u1ca2\u2d12\u10b2\u10f3\u1cb3\u2d23\u10c3\u10e3\u1ca3\u2d13\u10b3\u10e4\u1ca4\u2d14\u10b4\u10e5\u1ca5\u2d15\u10b5\u10e6\u1ca6\u2d16\u10b6\u10e7\u1ca7\u2d17\u10b7\u10e8\u1ca8\u2d18\u10b8\u10e9\u1ca9\u2d19\u10b9\u10ea\u1caa\u2d1a\u10ba\u10eb\u1cab\u2d1b\u10bb\u10ec\u1cac\u2d1c\u10bc\u10ed\u1cad\u2d1d\u10bd\u10ee\u1cae\u2d1e\u10be\u10f4\u1cb4\u2d24\u10c4\u10ef\u1caf\u2d1f\u10bf\u10f0\u1cb0\u2d20\u10c0\u10f5\u1cb5\u2d25\u10c5\u10f6\u1cb6\u10f7\u1cb7\u2d27\u10c7\u10f8\u1cb8\u10f9\u1cb9\u10fa\u1cba\u10fd\u1cbd\u2d2d\u10cd\u10fe\u1cbe\u10ff\u1cbf\u0561\u0531\u0560\u0562\u0532\u0563\u0533\u0564\u0534\u0565\u0535\u0587\u0566\u0536\u0567\u0537\u0568\u0538\u0569\u0539\u056a\u053a\u056b\u053b\u056c\u053c\u056d\u053d\u056e\u053e\u056f\u053f\u0570\u0540\u0571\u0541\u0572\u0542\u0573\u0543\u0574\u0544\ufb14\ufb15\ufb17\ufb13\u0575\u0545\u0588\u0576\u0546\u0577\u0547\u0578\u0548\u0579\u0549\u057a\u054a\u057b\u054b\u057c\u054c\u057d\u054d\u057e\u054e\ufb16\u057f\u054f\u0580\u0550\u0581\u0551\u0582\u0552\u0583\u0553\u0584\u0554\u0585\u0555\u0586\u0556\u0559\u05d0\u2135\ufb21\ufb2e\ufb2f\ufb30\ufb4f\u05d1\u2136\ufb31\ufb4c\u05d2\u2137\ufb32\u05d3\u2138\ufb22\ufb33\u05d4\ufb23\ufb34\u05d5\ufb4b\ufb35\u05f0\u05f1\u05d6\ufb36\u05d7\u05d8\ufb38\u05d9\ufb1d\ufb39\u05ef\u05f2\ufb1f\u05db\ufb24\u05da\ufb3b\ufb3a\ufb4d\u05dc\ufb25\ufb3c\u05de\ufb26\u05dd\ufb3e\u05e0\u05df\ufb40\u05e1\ufb41\u05e2\ufb20\u05e4\u05e3\ufb44\ufb43\ufb4e\u05e6\u05e5\ufb46\u05e7\ufb47\u05e8\ufb27\ufb48\u05e9\ufb2b\ufb2a\ufb49\ufb2d\ufb2c\u05ea\ufb28\ufb4a\u0800\u0801\u0802\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u0817\u081a\u081b\u0621\u0674\ufe80\u06fd\u0622\ufe82\ufe81\u0623\ufe84\ufe83\u0672\u0671\ufb51\ufb50\u0624\ufe86\ufe85\u0625\ufe88\ufe87\u0673\u0773\u0774\u0626\ufe8b\ufe8c\ufe8a\ufe89\ufbeb\ufbea\ufc97\ufc00\ufc98\ufc01\ufc99\ufc64\ufc65\ufc9a\ufcdf\ufc66\ufc02\ufc67\ufc9b\ufce0\ufbed\ufbec\ufbef\ufbee\ufbf3\ufbf2\ufbf1\ufbf0\ufbf5\ufbf4\ufbfb\ufbfa\ufc68\ufbf9\ufc03\ufc69\ufc04\ufbf8\ufbf7\ufbf6\u08a8\u08a9\u08ac\u0627\u08ad\ufe8e\ufe8d\ufd3c\ufd3d\u0675\ufdf3\ufdf2\u066e\u0628\ufe91\ufe92\ufe90\ufe8f\ufc9c\ufc05\ufc9d\ufc06\ufdc2\ufc9e\ufc07\ufd9e\ufc6a\ufc6b\ufc9f\ufce1\ufc6c\ufc08\ufc6d\ufca0\ufce2\ufc6e\ufc09\ufc6f\ufc0a\u067b\ufb54\ufb55\ufb53\ufb52\u067e\ufb58\ufb59\ufb57\ufb56\u0680\ufb5c\ufb5d\ufb5b\ufb5a\u0750\u0751\u0752\u0753\u0754\u0755\u08a0\u0756\u08be\u08a1\u08b6\u08b7\u0629\ufe94\ufe93\u062a\ufe97\ufe98\ufe96\ufe95\ufca1\ufc0b\ufd50\ufda0\ufd9f\ufca2\ufc0c\ufd52\ufd51\ufd53\ufca3\ufc0d\ufd54\ufda2\ufda1\ufc70\ufc71\ufca4\ufce3\ufc72\ufc0e\ufd55\ufd56\ufd57\ufda4\ufda3\ufc73\ufca5\ufce4\ufc74\ufc0f\ufc75\ufc10\u062b\ufe9b\ufe9c\ufe9a\ufe99\ufc11\ufc76\ufc77\ufca6\ufce5\ufc78\ufc12\ufc79\ufce6\ufc7a\ufc13\ufc7b\ufc14\u0679\ufb68\ufb69\ufb67\ufb66\u067a\ufb60\ufb61\ufb5f\ufb5e\u067c\u067d\u067f\ufb64\ufb65\ufb63\ufb62\u08b8\u08bf\u08c0\u062c\ufe9f\ufea0\ufe9e\ufe9d\ufca7\ufc15\ufda6\ufdbe\ufdfb\ufca8\ufc16\ufd59\ufd58\ufda7\ufda5\ufd1d\ufd01\ufd1e\ufd02\u0683\ufb78\ufb79\ufb77\ufb76\u0684\ufb74\ufb75\ufb73\ufb72\u0686\ufb7c\ufb7d\ufb7b\ufb7a\u06bf\u0687\ufb80\ufb81\ufb7f\ufb7e\u08a2\u08c1\u08c5\u08c6\u062d\ufea3\ufea4\ufea2\ufea1\ufca9\ufc17\ufdbf\ufcaa\ufc18\ufd5b\ufd5a\ufd1b\ufcff\ufd1c\ufd00\u062e\ufea7\ufea8\ufea6\ufea5\ufcab\ufc19\ufc1a\ufcac\ufc1b\ufd1f\ufd03\ufd20\ufd04\u0681\u0682\u0685\u0757\u0758\u076e\u076f\u0772\u077c\u062f\ufeaa\ufea9\u0630\ufeac\ufeab\ufc5b\u0688\ufb89\ufb88\u0689\u068a\u068b\u068c\ufb85\ufb84\u068d\ufb83\ufb82\u08ae\u068e\ufb87\ufb86\u068f\u0690\u06ee\u0759\u075a\u0631\ufeae\ufead\ufc5c\ufdf6\ufdfc\u0632\ufeb0\ufeaf\u0691\ufb8d\ufb8c\u0692\u0693\u0694\u0695\u0696\u0697\u0698\ufb8b\ufb8a\u0699\u06ef\u075b\u076b\u076c\u0771\u08aa\u08b2\u08b9\u0633\ufeb3\ufeb4\ufeb2\ufeb1\ufcad\ufd34\ufc1c\ufd5d\ufd5e\ufcae\ufd35\ufc1d\ufd5c\ufcaf\ufd36\ufc1e\ufda8\ufdc6\ufd2a\ufd0e\ufcb0\ufce7\ufc1f\ufd61\ufd60\ufd5f\ufd63\ufd62\ufd31\ufce8\ufd17\ufcfb\ufd18\ufcfc\u0634\ufeb7\ufeb8\ufeb6\ufeb5\ufd2d\ufd37\ufd25\ufd09\ufd69\ufd2e\ufd38\ufd26\ufd0a\ufd68\ufd67\ufdaa\ufd2f\ufd39\ufd27\ufd0b\ufd29\ufd0d\ufd30\ufce9\ufd28\ufd0c\ufd6b\ufd6a\ufd6d\ufd6c\ufd32\ufcea\ufd19\ufcfd\ufd1a\ufcfe\u069a\u069b\u069c\u06fa\u075c\u076d\u0770\u077d\u077e\u0635\ufebb\ufebc\ufeba\ufeb9\ufcb1\ufc20\ufd65\ufd64\ufda9\ufcb2\ufd2b\ufd0f\ufdf5\ufdf9\ufdfa\ufdf0\ufcb3\ufc21\ufdc5\ufd66\ufd21\ufd05\ufd22\ufd06\u0636\ufebf\ufec0\ufebe\ufebd\ufcb4\ufc22\ufcb5\ufc23\ufd6e\ufdab\ufcb6\ufc24\ufd70\ufd6f\ufd2c\ufd10\ufcb7\ufc25\ufd23\ufd07\ufd24\ufd08\u069d\u08af\u069e\u06fb\u0637\ufec3\ufec4\ufec2\ufec1\ufcb8\ufc26\ufd33\ufd3a\ufc27\ufd72\ufd71\ufd73\ufd74\ufd11\ufcf5\ufd12\ufcf6\u0638\ufec7\ufec8\ufec6\ufec5\ufcb9\ufd3b\ufc28\u069f\u08a3\u0639\ufecb\ufecc\ufeca\ufec9\ufcba\ufc29\ufdc4\ufd75\ufdf7\ufcbb\ufc2a\ufd77\ufd76\ufd78\ufdb6\ufd13\ufcf7\ufd14\ufcf8\u063a\ufecf\ufed0\ufece\ufecd\ufcbc\ufc2b\ufcbd\ufc2c\ufd79\ufd7b\ufd7a\ufd15\ufcf9\ufd16\ufcfa\u06a0\u08c3\u06fc\u075d\u075e\u075f\u08b3\u0641\ufed3\ufed4\ufed2\ufed1\ufcbe\ufc2d\ufcbf\ufc2e\ufcc0\ufc2f\ufd7d\ufd7c\ufcc1\ufc30\ufdc1\ufc7c\ufc31\ufc7d\ufc32\u06a1\u06a2\u08bb\u06a3\u06a4\ufb6c\ufb6d\ufb6b\ufb6a\u08a4\u06a5\u06a6\ufb70\ufb71\ufb6f\ufb6e\u0760\u0761\u066f\u0642\ufed7\ufed8\ufed6\ufed5\ufcc2\ufc33\ufdf1\ufcc3\ufc34\ufdb4\ufd7e\ufd7f\ufdb2\ufc7e\ufc35\ufc7f\ufc36\u06a7\u08bc\u06a8\u08c4\u08a5\u0643\ufedb\ufedc\ufeda\ufed9\ufc80\ufc37\ufcc4\ufc38\ufcc5\ufc39\ufcc6\ufc3a\ufcc7\ufceb\ufc81\ufc3b\ufcc8\ufcec\ufc82\ufc3c\ufdc3\ufdbb\ufdb7\ufc83\ufc3d\ufc84\ufc3e\u06a9\ufb90\ufb91\ufb8f\ufb8e\u06aa\u06ab\u06ac\u077f\u06ad\ufbd5\ufbd6\ufbd4\ufbd3\u06ae\u08b4\u06af\ufb94\ufb95\ufb93\ufb92\u08b0\u06b0\u06b1\ufb9c\ufb9d\ufb9b\ufb9a\u06b2\u06b3\ufb98\ufb99\ufb97\ufb96\u06b4\u0762\u063b\u063c\u0763\u0764\u08c2\u0644\ufedf\ufee0\ufede\ufedd\ufef6\ufef5\ufef8\ufef7\ufefa\ufef9\ufefc\ufefb\ufcc9\ufc3f\ufd83\ufd84\ufdba\ufdbc\ufdac\ufcca\ufc40\ufdb5\ufd80\ufd82\ufd81\ufccb\ufc41\ufd86\ufd85\ufccc\ufced\ufc85\ufc42\ufd88\ufd87\ufdad\ufccd\ufc86\ufc43\ufc87\ufc44\u06b5\u06b6\u06b7\u06b8\u076a\u08a6\u08c7\u0645\ufee3\ufee4\ufee2\ufee1\u06fe\ufc88\ufcce\ufc45\ufd8c\ufd92\ufd8d\ufdc0\ufccf\ufc46\ufd89\ufd8a\ufdf4\ufd8b\ufcd0\ufc47\ufd8e\ufd8f\ufdb9\ufcd1\ufc89\ufc48\ufdb1\ufc49\ufc4a\u0765\u0766\u08a7\u0646\ufee7\ufee8\ufee6\ufee5\ufcd2\ufc4b\ufdb8\ufdbd\ufd98\ufd97\ufd99\ufdc7\ufcd3\ufc4c\ufd95\ufd96\ufdb3\ufcd4\ufc4d\ufc8a\ufc8b\ufcd5\ufcee\ufc8c\ufc4e\ufd9b\ufd9a\ufc8d\ufcd6\ufcef\ufc8e\ufc4f\ufc8f\ufc50\u06ba\ufb9f\ufb9e\u08bd\u06bb\ufba2\ufba3\ufba1\ufba0\u06bc\u06bd\u06b9\u0767\u0768\u0769\u0647\ufeeb\ufeec\ufeea\ufee9\ufcd9\ufcd7\ufc51\ufcd8\ufc52\ufd93\ufd94\ufc53\ufc54\u06be\ufbac\ufbad\ufbab\ufbaa\u06c1\ufba8\ufba9\ufba7\ufba6\u06c2\u06c3\u06ff\u06d5\u06c0\ufba5\ufba4\u0648\u06e5\ufeee\ufeed\u0676\ufdf8\u06c4\u06c5\ufbe1\ufbe0\u06c6\ufbda\ufbd9\u06c7\ufbd8\ufbd7\u0677\ufbdd\u06c8\ufbdc\ufbdb\u06c9\ufbe3\ufbe2\u06ca\u06cb\ufbdf\ufbde\u08b1\u06cf\u0778\u0779\u08ab\u0649\ufbe8\ufbe9\ufef0\ufeef\ufc90\ufc5d\u064a\u06e6\ufef3\ufef4\ufef2\ufef1\u0678\ufcda\ufc55\ufdaf\ufcdb\ufc56\ufdae\ufcdc\ufc57\ufc91\ufc92\ufcdd\ufcf0\ufc93\ufc58\ufd9d\ufd9c\ufdb0\ufc94\ufcde\ufcf1\ufc95\ufc59\ufc96\ufc5a\u06cc\ufbfe\ufbff\ufbfd\ufbfc\u06cd\u06ce\u06d0\ufbe6\ufbe7\ufbe5\ufbe4\u06d1\u063d\u063e\u063f\u0620\u0775\u0776\u0777\u08ba\u06d2\ufbaf\ufbae\u06d3\ufbb1\ufbb0\u077a\u077b\u0710\u0712\u072d\u0713\u0714\u072e\u0716\u0715\u072f\u0717\u0718\u0719\u074d\u071a\u071b\u071c\u071d\u071e\u071f\u074e\u0720\u0721\u0722\u0723\u0724\u0725\u0726\u0727\u074f\u0728\u0729\u072a\u072b\u072c\u0860\u0861\u0862\u0863\u0864\u0865\u0866\u0867\u0868\u0869\u086a\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852\u0853\u0854\u0855\u0856\u0857\u0858\u0780\u0799\u079a\u0781\u0782\u0783\u079c\u0784\u0785\u0786\u0787\u07a2\u07a3\u0788\u07a5\u0789\u078a\u078b\u079b\u078c\u0798\u07a0\u07a1\u078d\u078e\u07a4\u078f\u0790\u079d\u079e\u079f\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u07b1\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07e8\u07d7\u07e9\u07d8\u07d9\u07ea\u07da\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07f4\u07f5\u2d30\u2d31\u2d32\u2d33\u2d34\u2d35\u2d36\u2d37\u2d38\u2d39\u2d3a\u2d3b\u2d66\u2d3c\u2d3d\u2d3e\u2d3f\u2d40\u2d41\u2d42\u2d43\u2d44\u2d45\u2d46\u2d47\u2d48\u2d49\u2d4a\u2d4b\u2d4c\u2d4d\u2d4e\u2d4f\u2d50\u2d51\u2d52\u2d53\u2d67\u2d54\u2d55\u2d56\u2d57\u2d58\u2d59\u2d5a\u2d5b\u2d5c\u2d5d\u2d5e\u2d5f\u2d60\u2d61\u2d62\u2d63\u2d64\u2d65\u2d6f\u1200\u1201\u1202\u1203\u1204\u1205\u1206\u1207\u1208\u1209\u120a\u120b\u120c\u120d\u120e\u120f\u2d80\u1210\u1211\u1212\u1213\u1214\u1215\u1216\u1217\u1218\u1219\u121a\u121b\u121c\u121d\u121e\u121f\u1380\u1381\u1382\u1383\u2d81\u1220\u1221\u1222\u1223\u1224\u1225\u1226\u1227\u1228\u1229\u122a\u122b\u122c\u122d\u122e\u122f\u2d82\u1230\u1231\u1232\u1233\u1234\u1235\u1236\u1237\u2d83\uab01\uab02\uab03\uab04\uab05\uab06\u1238\u1239\u123a\u123b\u123c\u123d\u123e\u123f\u2d84\u1240\u1241\u1242\u1243\u1244\u1245\u1246\u1247\u1248\u124a\u124b\u124c\u124d\u1250\u1251\u1252\u1253\u1254\u1255\u1256\u1258\u125a\u125b\u125c\u125d\u1260\u1261\u1262\u1263\u1264\u1265\u1266\u1267\u1384\u1385\u1386\u1387\u2d85\u1268\u1269\u126a\u126b\u126c\u126d\u126e\u126f\u1270\u1271\u1272\u1273\u1274\u1275\u1276\u1277\u2d86\u1278\u1279\u127a\u127b\u127c\u127d\u127e\u127f\u2d87\u1280\u1281\u1282\u1283\u1284\u1285\u1286\u1287\u1288\u128a\u128b\u128c\u128d\u1290\u1291\u1292\u1293\u1294\u1295\u1296\u1297\u2d88\u1298\u1299\u129a\u129b\u129c\u129d\u129e\u129f\u2d89\u12a0\u12a1\u12a2\u12a3\u12a4\u12a5\u12a6\u12a7\u2d8a\u12a8\u12a9\u12aa\u12ab\u12ac\u12ad\u12ae\u12af\u12b0\u12b2\u12b3\u12b4\u12b5\u12b8\u12b9\u12ba\u12bb\u12bc\u12bd\u12be\u12c0\u12c2\u12c3\u12c4\u12c5\u12c8\u12c9\u12ca\u12cb\u12cc\u12cd\u12ce\u12cf\u12d0\u12d1\u12d2\u12d3\u12d4\u12d5\u12d6\u12d8\u12d9\u12da\u12db\u12dc\u12dd\u12de\u12df\u2d8b\uab11\uab12\uab13\uab14\uab15\uab16\u12e0\u12e1\u12e2\u12e3\u12e4\u12e5\u12e6\u12e7\u12e8\u12e9\u12ea\u12eb\u12ec\u12ed\u12ee\u12ef\u12f0\u12f1\u12f2\u12f3\u12f4\u12f5\u12f6\u12f7\u2d8c\uab09\uab0a\uab0b\uab0c\uab0d\uab0e\u12f8\u12f9\u12fa\u12fb\u12fc\u12fd\u12fe\u12ff\u2d8d\u1300\u1301\u1302\u1303\u1304\u1305\u1306\u1307\u2d8e\u1308\u1309\u130a\u130b\u130c\u130d\u130e\u130f\u1310\u1312\u1313\u1314\u1315\u1318\u1319\u131a\u131b\u131c\u131d\u131e\u131f\u2d93\u2d94\u2d95\u2d96\u1320\u1321\u1322\u1323\u1324\u1325\u1326\u1327\u2d8f\u1328\u1329\u132a\u132b\u132c\u132d\u132e\u132f\u2d90\uab20\uab21\uab22\uab23\uab24\uab25\uab26\u1330\u1331\u1332\u1333\u1334\u1335\u1336\u1337\u2d91\u1338\u1339\u133a\u133b\u133c\u133d\u133e\u133f\uab28\uab29\uab2a\uab2b\uab2c\uab2d\uab2e\u1340\u1341\u1342\u1343\u1344\u1345\u1346\u1347\u1348\u1349\u134a\u134b\u134c\u134d\u134e\u134f\u1388\u1389\u138a\u138b\u1350\u1351\u1352\u1353\u1354\u1355\u1356\u1357\u138c\u138d\u138e\u138f\u2d92\u1358\u1359\u135a\u2da0\u2da1\u2da2\u2da3\u2da4\u2da5\u2da6\u2da8\u2da9\u2daa\u2dab\u2dac\u2dad\u2dae\u2db0\u2db1\u2db2\u2db3\u2db4\u2db5\u2db6\u2db8\u2db9\u2dba\u2dbb\u2dbc\u2dbd\u2dbe\u2dc0\u2dc1\u2dc2\u2dc3\u2dc4\u2dc5\u2dc6\u2dc8\u2dc9\u2dca\u2dcb\u2dcc\u2dcd\u2dce\u2dd0\u2dd1\u2dd2\u2dd3\u2dd4\u2dd5\u2dd6\u2dd8\u2dd9\u2dda\u2ddb\u2ddc\u2ddd\u2dde\u0950\ua8fd\u0972\u0904\u0905\u0906\u0973\u0974\u0975\u0976\u0977\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090d\u090e\u090f\u0910\ua8fe\u0911\u0912\u0913\u0914\u0915\u0958\u0916\u0959\u0917\u095a\u097b\u0918\u0919\u091a\u091b\u091c\u095b\u0979\u097c\u091d\u091e\u091f\u0920\u0978\u0921\u095c\u097e\u0922\u095d\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u095e\u092c\u097f\u092d\u092e\u092f\u095f\u097a\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093d\u097d\u1ce9\u1cea\u1ceb\u1cec\u1cee\u1cef\u1cf0\u1cf1\u1cfa\u1cf5\u1cf6\ua8f2\ua8f3\ua8f4\ua8f5\ua8f6\ua8f7\ua8fb\u093e\u093a\u093b\u094f\u0956\u0957\u093f\u0940\u0941\u0942\u0943\u0944\u0962\u0963\u0945\u0955\u0946\u0947\u094e\u0948\ua8ff\u0949\u094a\u094b\u094c\u094d\u0980\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u09e0\u098c\u09e1\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09dc\u09a2\u09dd\u09a3\u09a4\u09ce\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09df\u09b0\u09f0\u09b2\u09f1\u09b6\u09b7\u09b8\u09b9\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09e2\u09e3\u09c7\u09c8\u09cb\u09cc\u09cd\u09d7\u09fc\u0a74\u0a73\u0a09\u0a0a\u0a13\u0a05\u0a06\u0a10\u0a14\u0a72\u0a07\u0a08\u0a0f\u0a38\u0a36\u0a39\u0a51\u0a15\u0a16\u0a59\u0a17\u0a5a\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a5b\u0a1d\u0a1e\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a2a\u0a2b\u0a5e\u0a2c\u0a2d\u0a2e\u0a2f\u0a75\u0a30\u0a32\u0a33\u0a35\u0a5c\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a47\u0a48\u0a4b\u0a4c\u0a4d\u0ad0\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0ae0\u0a8c\u0ae1\u0a8d\u0a8f\u0a90\u0a91\u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0af9\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aaa\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab2\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0ab3\u0abd\u0abe\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ae2\u0ae3\u0ac5\u0ac7\u0ac8\u0ac9\u0acb\u0acc\u0acd\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b60\u0b0c\u0b61\u0b0f\u0b10\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b5c\u0b22\u0b5d\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b5f\u0b30\u0b32\u0b33\u0b35\u0b71\u0b36\u0b37\u0b38\u0b39\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44\u0b62\u0b63\u0b47\u0b48\u0b4b\u0b4c\u0b4d\u0b56\u0b57\u0bd0\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a\u0b8e\u0b8f\u0b90\u0b92\u0b93\u0b94\u0b83\u0b95\u0b99\u0b9a\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8\u0baa\u0bae\u0baf\u0bb0\u0bb2\u0bb5\u0bb4\u0bb3\u0bb1\u0ba9\u0b9c\u0bb6\u0bb7\u0bb8\u0bb9\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2\u0bc6\u0bc7\u0bc8\u0bca\u0bcb\u0bcc\u0bcd\u0bd7\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c60\u0c0c\u0c61\u0c0e\u0c0f\u0c10\u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c58\u0c1b\u0c1c\u0c59\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26\u0c27\u0c28\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c35\u0c36\u0c37\u0c38\u0c39\u0c33\u0c34\u0c5a\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c62\u0c63\u0c46\u0c47\u0c48\u0c4a\u0c4b\u0c4c\u0c4d\u0c55\u0c56\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\u0ce0\u0c8c\u0ce1\u0c8e\u0c8f\u0c90\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cb3\u0cde\u0cbd\u0cf1\u0cf2\u0c80\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0ce2\u0ce3\u0cc6\u0cc7\u0cc8\u0cca\u0ccb\u0ccc\u0ccd\u0cd5\u0cd6\u0d05\u0d06\u0d07\u0d08\u0d5f\u0d09\u0d0a\u0d0b\u0d60\u0d0c\u0d61\u0d0e\u0d0f\u0d10\u0d12\u0d13\u0d14\u0d15\u0d7f\u0d16\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d7a\u0d24\u0d25\u0d26\u0d27\u0d28\u0d7b\u0d29\u0d2a\u0d2b\u0d2c\u0d2d\u0d2e\u0d54\u0d2f\u0d55\u0d30\u0d4e\u0d7c\u0d32\u0d7d\u0d35\u0d36\u0d37\u0d38\u0d39\u0d33\u0d7e\u0d34\u0d56\u0d31\u0d3a\u0d3d\u0d04\u0d3e\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d62\u0d63\u0d46\u0d47\u0d48\u0d4a\u0d4b\u0d4c\u0d57\u0d4d\u0d3b\u0d3c\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db3\u0db4\u0db5\u0db6\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbd\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0dcf\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd6\u0dd8\u0df2\u0ddf\u0df3\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde\u0dca\uaaf2\uabc0\uabc1\uabc2\uabc3\uabc4\uabc5\uabc6\uabc7\uabc8\uabc9\uabca\uabcb\uabcc\uabcd\uabce\uabcf\uabd0\uabd1\uabd2\uabd3\uabd4\uabd5\uabd6\uabd7\uabd8\uabd9\uabda\uaae0\uaae1\uaae2\uaae3\uaae4\uaae5\uaae6\uaae7\uaae8\uaae9\uaaea\uabe3\uabe4\uabe5\uabe6\uabe7\uabe8\uabe9\uabea\uaaeb\uaaec\uaaed\uaaee\uaaef\uaaf5\uabdb\uabdc\uabdd\uabde\uabdf\uabe0\uabe1\uabe2\uabed\uaaf6\ua800\ua801\ua802\ua803\ua804\ua805\ua806\ua82c\ua807\ua808\ua809\ua80a\ua80c\ua80d\ua80e\ua80f\ua810\ua811\ua812\ua813\ua814\ua815\ua816\ua817\ua818\ua819\ua81a\ua81b\ua81c\ua81d\ua81e\ua81f\ua820\ua821\ua822\ua823\ua824\ua825\ua826\ua827\ua882\ua883\ua884\ua885\ua886\ua887\ua888\ua889\ua88a\ua88b\ua88c\ua88d\ua88e\ua88f\ua890\ua891\ua892\ua893\ua894\ua895\ua896\ua897\ua898\ua899\ua89a\ua89b\ua89c\ua89d\ua89e\ua89f\ua8a0\ua8a1\ua8a2\ua8a3\ua8a4\ua8a5\ua8a6\ua8a7\ua8a8\ua8a9\ua8aa\ua8ab\ua8ac\ua8ad\ua8ae\ua8af\ua8b0\ua8b1\ua8b2\ua8b3\ua8b4\ua8b5\ua8b6\ua8b7\ua8b8\ua8b9\ua8ba\ua8bb\ua8bc\ua8bd\ua8be\ua8bf\ua8c0\ua8c1\ua8c2\ua8c3\ua8c4\u1b83\u1bba\u1b84\u1b85\u1b86\u1b87\u1b88\u1b89\u1b8a\u1bbe\u1bae\u1b8b\u1b8c\u1b8d\u1b8e\u1b8f\u1b90\u1b91\u1b92\u1b93\u1b94\u1b95\u1b96\u1b97\u1b98\u1bbd\u1b99\u1bbf\u1bac\u1b9a\u1ba1\u1b9b\u1ba2\u1bbb\u1b9c\u1ba3\u1bbc\u1b9d\u1bad\u1b9e\u1b9f\u1baf\u1ba0\u1ba4\u1ba5\u1ba6\u1ba7\u1ba8\u1ba9\u1baa\u1bab\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e40\u0e41\u0e42\u0e43\u0e44\u0e45\u0ede\u0e81\u0e82\u0e84\u0e86\u0e87\u0e88\u0e89\u0eaa\u0e8a\u0e8c\u0e8e\u0edf\u0e8d\u0e8f\u0e90\u0e91\u0e92\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea5\u0ea7\u0ea8\u0ea9\u0eab\u0edc\u0edd\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba\u0ebb\u0ebc\u0ebd\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\uaa80\uaa81\uaa82\uaa83\uaa84\uaa85\uaa86\uaa87\uaa88\uaa89\uaa8a\uaa8b\uaa8c\uaa8d\uaa8e\uaa8f\uaa90\uaa91\uaa92\uaa93\uaa94\uaa95\uaa96\uaa97\uaa98\uaa99\uaa9a\uaa9b\uaa9c\uaa9d\uaa9e\uaa9f\uaaa0\uaaa1\uaaa2\uaaa3\uaaa4\uaaa5\uaaa6\uaaa7\uaaa8\uaaa9\uaaaa\uaaab\uaaac\uaaad\uaaae\uaaaf\uaab0\uaab1\uaab2\uaab3\uaab4\uaab5\uaab6\uaab7\uaab8\uaab9\uaaba\uaabb\uaabc\uaabd\uaabe\uaac0\uaac2\uaadb\uaadc\u0f40\u0f69\u0f90\u0fb9\u0f6b\u0f41\u0f91\u0f42\u0f43\u0f92\u0f93\u0f44\u0f94\u0f45\u0f95\u0f46\u0f96\u0f47\u0f97\u0f49\u0f99\u0f4a\u0f9a\u0f4b\u0f9b\u0f4c\u0f4d\u0f9c\u0f9d\u0f4e\u0f9e\u0f4f\u0f9f\u0f50\u0fa0\u0f51\u0f52\u0fa1\u0fa2\u0f53\u0fa3\u0f54\u0fa4\u0f55\u0fa5\u0f56\u0f57\u0fa6\u0fa7\u0f58\u0fa8\u0f59\u0fa9\u0f5a\u0faa\u0f5b\u0f5c\u0fab\u0fac\u0f5d\u0fad\u0fba\u0f5e\u0fae\u0f5f\u0faf\u0f60\u0fb0\u0f61\u0fb1\u0fbb\u0f62\u0f6a\u0fb2\u0fbc\u0f6c\u0f63\u0fb3\u0f64\u0fb4\u0f65\u0fb5\u0f66\u0fb6\u0f67\u0fb7\u0f68\u0f00\u0fb8\u0f88\u0f8d\u0f89\u0f8e\u0f8c\u0f8f\u0f8a\u0f8b\u0f71\u0f72\u0f73\u0f80\u0f81\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f84\u1c00\u1c01\u1c02\u1c03\u1c04\u1c05\u1c06\u1c07\u1c08\u1c09\u1c4d\u1c4e\u1c4f\u1c0a\u1c0b\u1c0c\u1c0d\u1c0e\u1c0f\u1c10\u1c11\u1c12\u1c13\u1c14\u1c15\u1c16\u1c17\u1c18\u1c19\u1c1a\u1c24\u1c1b\u1c25\u1c1c\u1c1d\u1c1e\u1c1f\u1c20\u1c21\u1c22\u1c23\u1c36\u1c26\u1c27\u1c28\u1c29\u1c2a\u1c2b\u1c2c\u1c2d\u1c2e\u1c2f\u1c30\u1c31\u1c32\u1c33\u1c34\u1c35\ua840\ua841\ua842\ua843\ua844\ua845\ua846\ua847\ua869\ua86a\ua86b\ua86c\ua848\ua849\ua84a\ua84b\ua84c\ua84d\ua84e\ua84f\ua850\ua851\ua852\ua853\ua867\ua854\ua855\ua856\ua857\ua868\ua86d\ua858\ua871\ua872\ua859\ua85a\ua86e\ua85b\ua85c\ua86f\ua870\ua85d\ua862\ua863\ua864\ua865\ua85e\ua85f\ua860\ua861\ua866\ua873\u1900\u1901\u1902\u1903\u1904\u1905\u1906\u1907\u1908\u191d\u1909\u190a\u190b\u191e\u190c\u190d\u190e\u190f\u1910\u1911\u1912\u1913\u1914\u1915\u1916\u1917\u1918\u1919\u191a\u191b\u191c\u1920\u1921\u1922\u1923\u1924\u1925\u1926\u1927\u1928\u1929\u192a\u192b\u1930\u1931\u1932\u1933\u1934\u1935\u1936\u1937\u1938\u1700\u1701\u1702\u1703\u1704\u1705\u1706\u1707\u1708\u1709\u170a\u170b\u170c\u170e\u170f\u1710\u1711\u1712\u1713\u1714\u1720\u1721\u1722\u1723\u1724\u1725\u1726\u1727\u1728\u1729\u172a\u172b\u172c\u172d\u172e\u172f\u1730\u1731\u1732\u1733\u1734\u1740\u1741\u1742\u1743\u1744\u1745\u1746\u1747\u1748\u1749\u174a\u174b\u174c\u174d\u174e\u174f\u1750\u1751\u1752\u1753\u1760\u1761\u1762\u1763\u1764\u1765\u1766\u1767\u1768\u1769\u176a\u176b\u176c\u176e\u176f\u1770\u1772\u1773\u1a00\u1a01\u1a02\u1a03\u1a04\u1a05\u1a06\u1a07\u1a08\u1a09\u1a0a\u1a0b\u1a0c\u1a0d\u1a0e\u1a0f\u1a10\u1a11\u1a12\u1a13\u1a14\u1a15\u1a16\u1a17\u1a18\u1a19\u1a1a\u1a1b\u1bc0\u1bc1\u1bc2\u1bc3\u1bc4\u1bc5\u1bc6\u1bc7\u1bc8\u1bc9\u1bca\u1bcb\u1bcc\u1bcd\u1bce\u1bcf\u1bd0\u1bd1\u1bd2\u1bd3\u1bd4\u1bd5\u1bd6\u1bd7\u1bd8\u1bd9\u1bda\u1bdb\u1bdc\u1bdd\u1bde\u1bdf\u1be0\u1be1\u1be2\u1be3\u1be4\u1be5\u1be7\u1be8\u1be9\u1bea\u1beb\u1bec\u1bed\u1bee\u1bef\u1bf0\u1bf1\u1bf2\u1bf3\ua930\ua931\ua932\ua933\ua934\ua935\ua936\ua937\ua938\ua939\ua93a\ua93b\ua93c\ua93d\ua93e\ua93f\ua940\ua941\ua942\ua943\ua944\ua945\ua946\ua947\ua948\ua949\ua94a\ua94b\ua94c\ua94d\ua94e\ua94f\ua950\ua951\ua952\ua953\ua90a\ua90b\ua90c\ua90d\ua90e\ua90f\ua910\ua911\ua912\ua913\ua914\ua915\ua916\ua917\ua918\ua919\ua91a\ua91b\ua91c\ua91d\ua91e\ua91f\ua920\ua921\ua922\ua923\ua924\ua925\ua926\ua927\ua928\ua929\ua92a\u1000\u1075\u1001\u1076\u1002\u1077\uaa60\ua9e9\u1003\ua9e0\ua9ea\u1004\u105a\u1005\u1078\uaa61\u1006\ua9e1\uaa62\uaa7e\u1007\uaa63\ua9eb\u1079\uaa72\u1008\u105b\ua9e2\uaa64\ua9ec\u1061\uaa7f\u1009\u107a\uaa65\ua9e7\u100a\u100b\uaa66\u100c\uaa67\u100d\uaa68\ua9ed\u100e\uaa69\ua9ee\u100f\u106e\ua9e3\ua9ef\u1010\u1011\u1012\u107b\ua9fb\u1013\uaa6a\ua9fc\u1014\u107c\uaa6b\u105e\u1015\u1016\u107d\u107e\uaa6f\u108e\ua9e8\u1017\u107f\ua9fd\u1018\ua9e4\ua9fe\u1019\u105f\u101a\u103b\u101b\uaa73\uaa7a\u103c\u101c\u1060\u101d\u103d\u1082\u1080\u1050\u1051\u1065\u101e\u103f\uaa6c\u101f\u1081\uaa6d\u103e\uaa6e\uaa71\u1020\ua9fa\u105c\u105d\u106f\u1070\u1066\u1021\u1022\u1023\u1024\u1025\u1026\u1052\u1053\u1054\u1055\u1027\u1028\u1029\u102a\u102c\u102b\u1083\u1072\u109c\u102d\u1071\u102e\u1033\u102f\u1073\u1074\u1030\u1056\u1057\u1058\u1059\u1031\u1084\u1035\u1085\u1032\u109d\u1034\u1062\u1067\u1068\ua9e5\u1086\u1039\u103a\u1063\u1064\u1069\u106a\u106b\u106c\u106d\u1087\u108b\u1088\u108c\u108d\u1089\u108a\u108f\u109a\u109b\uaa7b\uaa7c\uaa7d\uaa74\uaa75\uaa76\u1780\u1781\u1782\u1783\u1784\u1785\u1786\u1787\u1788\u1789\u178a\u178b\u178c\u178d\u178e\u178f\u1790\u1791\u1792\u1793\u1794\u1795\u1796\u1797\u1798\u1799\u179a\u179b\u179c\u179d\u179e\u179f\u17a0\u17a1\u17a2\u17dc\u17a3\u17a4\u17a5\u17a6\u17a7\u17a8\u17a9\u17aa\u17ab\u17ac\u17ad\u17ae\u17af\u17b0\u17b1\u17b2\u17b3\u17b6\u17b7\u17b8\u17b9\u17ba\u17bb\u17bc\u17bd\u17be\u17bf\u17c0\u17c1\u17c2\u17c3\u17c4\u17c5\u17d2\u1950\u1951\u1952\u1953\u1954\u1955\u1956\u1957\u1958\u1959\u195a\u195b\u195c\u195d\u195e\u195f\u1960\u1961\u1962\u1963\u1964\u1965\u1966\u1967\u1968\u1969\u196a\u196b\u196c\u196d\u1970\u1971\u1972\u1973\u1974\u1980\u1981\u1982\u1983\u1984\u1985\u1986\u1987\u1988\u1989\u198a\u198b\u198c\u198d\u198e\u198f\u1990\u1991\u1992\u1993\u1994\u1995\u1996\u1997\u1998\u1999\u199a\u199b\u199c\u19de\u19df\u199d\u199e\u199f\u19a0\u19a1\u19a2\u19a3\u19a4\u19a5\u19a6\u19a7\u19a8\u19a9\u19aa\u19ab\u19b0\u19b1\u19b2\u19b3\u19b4\u19b5\u19b6\u19b7\u19b8\u19b9\u19ba\u19bb\u19bc\u19bd\u19be\u19bf\u19c0\u19c1\u19c2\u19c3\u19c4\u19c5\u19c6\u19c7\u19c8\u19c9\u1a20\u1a21\u1a22\u1a23\u1a24\u1a25\u1a26\u1a58\u1a59\u1a27\u1a28\u1a29\u1a2a\u1a2b\u1a2c\u1a2d\u1a2e\u1a2f\u1a30\u1a31\u1a32\u1a33\u1a34\u1a35\u1a36\u1a37\u1a38\u1a39\u1a3a\u1a3b\u1a5a\u1a5b\u1a3c\u1a3d\u1a3e\u1a3f\u1a40\u1a41\u1a42\u1a43\u1a44\u1a45\u1a46\u1a54\u1a47\u1a48\u1a49\u1a4a\u1a4b\u1a4c\u1a53\u1a6b\u1a55\u1a56\u1a57\u1a5c\u1a5d\u1a5e\u1a4d\u1a4e\u1a4f\u1a50\u1a51\u1a52\u1a61\u1a6c\u1a62\u1a63\u1a64\u1a65\u1a66\u1a67\u1a68\u1a69\u1a6a\u1a6e\u1a6f\u1a73\u1a70\u1a71\u1a72\u1a6d\u1a60\uaa00\uaa01\uaa02\uaa03\uaa04\uaa05\uaa06\uaa07\uaa08\uaa09\uaa0a\uaa0b\uaa0c\uaa0d\uaa0e\uaa0f\uaa10\uaa11\uaa12\uaa13\uaa14\uaa15\uaa16\uaa17\uaa18\uaa19\uaa1a\uaa1b\uaa1c\uaa1d\uaa1e\uaa1f\uaa20\uaa21\uaa22\uaa23\uaa24\uaa25\uaa26\uaa27\uaa28\uaa33\uaa34\uaa35\uaa36\uaa29\uaa2a\uaa2b\uaa2c\uaa2d\uaa2e\uaa2f\uaa30\uaa31\uaa32\uaa40\uaa41\uaa42\uaa43\uaa44\uaa45\uaa46\uaa47\uaa48\uaa49\uaa4a\uaa4b\uaa4c\uaa4d\u1b05\u1b06\u1b07\u1b08\u1b09\u1b0a\u1b0b\u1b0c\u1b0d\u1b0e\u1b0f\u1b10\u1b11\u1b12\u1b13\u1b45\u1b46\u1b14\u1b15\u1b16\u1b17\u1b18\u1b19\u1b1a\u1b1b\u1b1c\u1b1d\u1b1e\u1b1f\u1b20\u1b21\u1b22\u1b47\u1b23\u1b24\u1b25\u1b26\u1b27\u1b48\u1b28\u1b29\u1b2a\u1b2b\u1b2c\u1b2d\u1b2e\u1b2f\u1b49\u1b30\u1b31\u1b32\u1b4a\u1b4b\u1b33\u1b35\u1b36\u1b37\u1b38\u1b39\u1b3a\u1b3b\u1b3c\u1b3d\u1b3e\u1b3f\u1b40\u1b41\u1b42\u1b43\u1b44\ua984\ua985\ua986\ua987\ua988\ua989\ua98a\ua98b\ua98c\ua98d\ua98e\ua98f\ua990\ua991\ua992\ua993\ua994\ua995\ua996\ua997\ua998\ua999\ua99a\ua99b\ua99c\ua99d\ua99e\ua99f\ua9a0\ua9a1\ua9a2\ua9a3\ua9a4\ua9a5\ua9a6\ua9a7\ua9a8\ua9a9\ua9aa\ua9be\ua9ab\ua9ac\ua9bf\ua9ad\ua9ae\ua9af\ua9b0\ua9b1\ua9b2\ua9b4\ua9b5\ua9bc\ua9b6\ua9b7\ua9b8\ua9b9\ua9bd\ua9ba\ua9bb\ua9c0\u1880\u1881\u1882\u1883\u1884\u1885\u1886\u1843\u1820\u1887\u1821\u1844\u185d\u1822\u1845\u185e\u1873\u1888\u185f\u1823\u1846\u1824\u1847\u1861\u1825\u1848\u1826\u1849\u1860\u1827\u1828\u1829\u184a\u1862\u188a\u189b\u182a\u184b\u182b\u184c\u1866\u182c\u184d\u182d\u184e\u1864\u189a\u1865\u182e\u184f\u182f\u1830\u1831\u1867\u189c\u189d\u18a2\u18a4\u18a5\u1832\u1850\u1868\u1833\u1851\u1869\u1834\u1852\u1871\u1878\u185c\u188b\u1835\u1853\u186a\u1877\u1836\u1855\u1872\u1837\u1875\u1838\u1856\u1839\u186b\u1876\u183a\u1857\u1863\u1874\u1889\u183b\u183c\u1854\u186e\u183d\u186f\u1858\u186c\u183e\u1859\u186d\u183f\u1840\u1841\u1842\u185a\u185b\u1870\u188c\u189e\u188d\u188e\u189f\u188f\u1890\u1898\u18a0\u1891\u18a1\u1892\u1893\u18a8\u1894\u18a3\u1895\u1899\u1896\u1897\u18a6\u18a7\u18aa\u18a9\u1c5a\u1c5b\u1c5c\u1c5d\u1c5e\u1c5f\u1c60\u1c61\u1c62\u1c63\u1c64\u1c65\u1c66\u1c67\u1c68\u1c69\u1c6a\u1c6b\u1c6c\u1c6d\u1c6e\u1c6f\u1c70\u1c71\u1c72\u1c73\u1c74\u1c75\u1c76\u1c77\u1c78\u1c79\u1c7a\u1c7b\u1c7c\u1c7d\uab70\u13a0\uab71\u13a1\uab72\u13a2\uab73\u13a3\uab74\u13a4\uab75\u13a5\uab76\u13a6\uab77\u13a7\uab78\u13a8\uab79\u13a9\uab7a\u13aa\uab7b\u13ab\uab7c\u13ac\uab7d\u13ad\uab7e\u13ae\uab7f\u13af\uab80\u13b0\uab81\u13b1\uab82\u13b2\uab83\u13b3\uab84\u13b4\uab85\u13b5\uab86\u13b6\uab87\u13b7\uab88\u13b8\uab89\u13b9\uab8a\u13ba\uab8b\u13bb\uab8c\u13bc\uab8d\u13bd\uab8e\u13be\uab8f\u13bf\uab90\u13c0\uab91\u13c1\uab92\u13c2\uab93\u13c3\uab94\u13c4\uab95\u13c5\uab96\u13c6\uab97\u13c7\uab98\u13c8\uab99\u13c9\uab9a\u13ca\uab9b\u13cb\uab9c\u13cc\uab9d\u13cd\uab9e\u13ce\uab9f\u13cf\uaba0\u13d0\uaba1\u13d1\uaba2\u13d2\uaba3\u13d3\uaba4\u13d4\uaba5\u13d5\uaba6\u13d6\uaba7\u13d7\uaba8\u13d8\uaba9\u13d9\uabaa\u13da\uabab\u13db\uabac\u13dc\uabad\u13dd\uabae\u13de\uabaf\u13df\uabb0\u13e0\uabb1\u13e1\uabb2\u13e2\uabb3\u13e3\uabb4\u13e4\uabb5\u13e5\uabb6\u13e6\uabb7\u13e7\uabb8\u13e8\uabb9\u13e9\uabba\u13ea\uabbb\u13eb\uabbc\u13ec\uabbd\u13ed\uabbe\u13ee\uabbf\u13ef\u13f8\u13f0\u13f9\u13f1\u13fa\u13f2\u13fb\u13f3\u13fc\u13f4\u13fd\u13f5\u1401\u1402\u1403\u1404\u1405\u1406\u1407\u1408\u1409\u140a\u140b\u140c\u140d\u140e\u140f\u1410\u1411\u1412\u1413\u1414\u1415\u1416\u1417\u1418\u1419\u141a\u141b\u141c\u141d\u141e\u141f\u1420\u1421\u1422\u1423\u1424\u1425\u1426\u1427\u1428\u1429\u142a\u142b\u142c\u142d\u142e\u142f\u1430\u1431\u1432\u1433\u1434\u1435\u1436\u1437\u1438\u1439\u143a\u143b\u143c\u143d\u143e\u143f\u1440\u1441\u1442\u1443\u1444\u1445\u1446\u1447\u1448\u1449\u144a\u144b\u144c\u144d\u144e\u144f\u1450\u1451\u1452\u1453\u1454\u1455\u1456\u1457\u1458\u1459\u145a\u145b\u145c\u145d\u145e\u145f\u1460\u1461\u1462\u1463\u1464\u1465\u1466\u1467\u1468\u1469\u146a\u146b\u146c\u146d\u146e\u146f\u1470\u1471\u1472\u1473\u1474\u1475\u1476\u1477\u1478\u1479\u147a\u147b\u147c\u147d\u147e\u147f\u1480\u1481\u1482\u1483\u1484\u1485\u1486\u1487\u1488\u1489\u148a\u148b\u148c\u148d\u148e\u148f\u1490\u1491\u1492\u1493\u1494\u1495\u1496\u1497\u1498\u1499\u149a\u149b\u149c\u149d\u149e\u149f\u14a0\u14a1\u14a2\u14a3\u14a4\u14a5\u14a6\u14a7\u14a8\u14a9\u14aa\u14ab\u14ac\u14ad\u14ae\u14af\u14b0\u14b1\u14b2\u14b3\u14b4\u14b5\u14b6\u14b7\u14b8\u14b9\u14ba\u14bb\u14bc\u14bd\u14be\u14bf\u14c0\u14c1\u14c2\u14c3\u14c4\u14c5\u14c6\u14c7\u14c8\u14c9\u14ca\u14cb\u14cc\u14cd\u14ce\u14cf\u14d0\u14d1\u14d2\u14d3\u14d4\u14d5\u14d6\u14d7\u14d8\u14d9\u14da\u14db\u14dc\u14dd\u14de\u14df\u14e0\u14e1\u14e2\u14e3\u14e4\u14e5\u14e6\u14e7\u14e8\u14e9\u14ea\u14eb\u14ec\u14ed\u14ee\u14ef\u14f0\u14f1\u14f2\u14f3\u14f4\u14f5\u14f6\u14f7\u14f8\u14f9\u14fa\u14fb\u14fc\u14fd\u14fe\u14ff\u1500\u1501\u1502\u1503\u1504\u1505\u1506\u1507\u1508\u1509\u150a\u150b\u150c\u150d\u150e\u150f\u1510\u1511\u1512\u1513\u1514\u1515\u1516\u1517\u1518\u1519\u151a\u151b\u151c\u151d\u151e\u151f\u1520\u1521\u1522\u1523\u1524\u1525\u1526\u1527\u1528\u1529\u152a\u152b\u152c\u152d\u152e\u152f\u1530\u1531\u1532\u1533\u1534\u1535\u1536\u1537\u1538\u1539\u153a\u153b\u153c\u153d\u153e\u153f\u1540\u1541\u1542\u1543\u1544\u1545\u1546\u1547\u1548\u1549\u154a\u154b\u154c\u154d\u154e\u154f\u1550\u1551\u1552\u1553\u1554\u1555\u1556\u1557\u1558\u1559\u155a\u155b\u155c\u155d\u155e\u155f\u1560\u1561\u1562\u1563\u1564\u1565\u1566\u1567\u1568\u1569\u156a\u156b\u156c\u156d\u156e\u156f\u1570\u1571\u1572\u1573\u1574\u1575\u1576\u1577\u1578\u1579\u157a\u157b\u157d\u166f\u157e\u157f\u1580\u1581\u1582\u1583\u1584\u1585\u1586\u1587\u1588\u1589\u158a\u158b\u158c\u158d\u1670\u158e\u158f\u1590\u1591\u1592\u1593\u1594\u1595\u1671\u1672\u1673\u1674\u1675\u1676\u1596\u1597\u1598\u1599\u159a\u159b\u159c\u159d\u159e\u159f\u15a0\u15a1\u15a2\u15a3\u15a4\u15a5\u15a6\u157c\u15a7\u15a8\u15a9\u15aa\u15ab\u15ac\u15ad\u15ae\u15af\u15b0\u15b1\u15b2\u15b3\u15b4\u15b5\u15b6\u15b7\u15b8\u15b9\u15ba\u15bb\u15bc\u15bd\u15be\u15bf\u15c0\u15c1\u15c2\u15c3\u15c4\u15c5\u15c6\u15c7\u15c8\u15c9\u15ca\u15cb\u15cc\u15cd\u15ce\u15cf\u15d0\u15d1\u15d2\u15d3\u15d4\u15d5\u15d6\u15d7\u15d8\u15d9\u15da\u15db\u15dc\u15dd\u15de\u15df\u15e0\u15e1\u15e2\u15e3\u15e4\u15e5\u15e6\u15e7\u15e8\u15e9\u15ea\u15eb\u15ec\u15ed\u15ee\u15ef\u15f0\u15f1\u15f2\u15f3\u15f4\u15f5\u15f6\u15f7\u15f8\u15f9\u15fa\u15fb\u15fc\u15fd\u15fe\u15ff\u1600\u1601\u1602\u1603\u1604\u1605\u1606\u1607\u1608\u1609\u160a\u160b\u160c\u160d\u160e\u160f\u1610\u1611\u1612\u1613\u1614\u1615\u1616\u1617\u1618\u1619\u161a\u161b\u161c\u161d\u161e\u161f\u1620\u1621\u1622\u1623\u1624\u1625\u1626\u1627\u1628\u1629\u162a\u162b\u162c\u162d\u162e\u162f\u1630\u1631\u1632\u1633\u1634\u1635\u1636\u1637\u1638\u1639\u163a\u163b\u163c\u163d\u163e\u163f\u1640\u1641\u1642\u1643\u1644\u1645\u1646\u1647\u1648\u1649\u164a\u164b\u164c\u164d\u164e\u164f\u1650\u1651\u1652\u1653\u1654\u1655\u1656\u1657\u1658\u1659\u165a\u165b\u165c\u165d\u165e\u165f\u1660\u1661\u1662\u1663\u1664\u1665\u1666\u1667\u1668\u1669\u166a\u166b\u166c\u1677\u1678\u1679\u167a\u167b\u167c\u167d\u167e\u167f\u18b0\u18b1\u18b2\u18b3\u18b4\u18b5\u18b6\u18b7\u18b8\u18b9\u18ba\u18bb\u18bc\u18bd\u18be\u18bf\u18c0\u18c1\u18c2\u18c3\u18c4\u18c5\u18c6\u18c7\u18c8\u18c9\u18ca\u18cb\u18cc\u18cd\u18ce\u18cf\u18d0\u18d1\u18d2\u18d3\u18d4\u18d5\u18d6\u18d7\u18d8\u18d9\u18da\u18db\u18dc\u18dd\u18de\u18df\u18e0\u18e1\u18e2\u18e3\u18e4\u18e5\u18e6\u18e7\u18e8\u18e9\u18ea\u18eb\u18ec\u18ed\u18ee\u18ef\u18f0\u18f1\u18f2\u18f3\u18f4\u18f5\u1681\u1682\u1683\u1684\u1685\u1686\u1687\u1688\u1689\u168a\u168b\u168c\u168d\u168e\u168f\u1690\u1691\u1692\u1693\u1694\u1695\u1696\u1697\u1698\u1699\u169a\u16a0\u16a1\u16a2\u16a4\u16a5\u16a6\u16a7\u16f0\u16a8\u16a9\u16ac\u16ad\u16ae\u16f4\u16af\u16b0\u16b1\u16b2\u16b3\u16b4\u16b5\u16b6\u16f1\u16b7\u16b9\u16e9\u16ba\u16bb\u16bc\u16bd\u16be\u16bf\u16c0\u16c1\u16c2\u16f5\u16c3\u16c4\u16c5\u16c6\u16ee\u16c7\u16c8\u16d5\u16c9\u16ca\u16cb\u16ea\u16cc\u16cd\u16ce\u16f2\u16cf\u16d0\u16d1\u16d2\u16d3\u16d4\u16d6\u16f6\u16d7\u16d8\u16d9\u16ef\u16da\u16db\u16dc\u16dd\u16de\u16df\u16f3\u16aa\u16f7\u16ab\u16f8\u16a3\u16e0\u16e3\u16b8\u16e4\u16e1\u16e2\u16e5\u16e6\u16e7\u16e8\ua500\ua501\ua502\ua503\ua504\ua505\ua506\ua507\ua508\ua509\ua50a\ua50b\ua50c\ua613\ua50d\ua50e\ua50f\ua510\ua511\ua512\ua513\ua514\ua515\ua516\ua517\ua518\ua519\ua51a\ua51b\ua51c\ua51d\ua51e\ua614\ua51f\ua520\ua521\ua522\ua523\ua524\ua525\ua526\ua527\ua528\ua529\ua52a\ua52b\ua52c\ua52d\ua52e\ua52f\ua530\ua531\ua532\ua533\ua615\ua534\ua535\ua536\ua537\ua538\ua539\ua53a\ua53b\ua53c\ua53d\ua53e\ua53f\ua540\ua541\ua542\ua543\ua544\ua545\ua546\ua547\ua616\ua548\ua549\ua54a\ua54b\ua54c\ua54d\ua54e\ua54f\ua550\ua551\ua552\ua617\ua553\ua554\ua555\ua556\ua557\ua558\ua610\ua618\ua559\ua55a\ua619\ua55b\ua55c\ua55d\ua55e\ua55f\ua560\ua61a\ua561\ua562\ua563\ua564\ua565\ua566\ua567\ua568\ua569\ua56a\ua611\ua56b\ua56c\ua56d\ua56e\ua62a\ua56f\ua570\ua571\ua572\ua573\ua574\ua575\ua576\ua577\ua578\ua579\ua57a\ua57b\ua57c\ua57d\ua57e\ua57f\ua580\ua581\ua582\ua583\ua584\ua585\ua61b\ua586\ua587\ua612\ua588\ua589\ua58a\ua58b\ua58c\ua58d\ua58e\ua58f\ua590\ua591\ua592\ua593\ua594\ua595\ua596\ua597\ua598\ua599\ua59a\ua59b\ua59c\ua59d\ua59e\ua59f\ua5a0\ua5a1\ua5a2\ua5a3\ua5a4\ua5a5\ua5a6\ua5a7\ua5a8\ua5a9\ua5aa\ua5ab\ua5ac\ua5ad\ua5ae\ua5af\ua5b0\ua5b1\ua5b2\ua5b3\ua5b4\ua61c\ua5b5\ua5b6\ua5b7\ua5b8\ua5b9\ua5ba\ua5bb\ua5bc\ua5bd\ua5be\ua5bf\ua5c0\ua5c1\ua5c2\ua5c3\ua5c4\ua5c5\ua5c6\ua5c7\ua5c8\ua5c9\ua5ca\ua5cb\ua61d\ua5cc\ua5cd\ua5ce\ua5cf\ua5d0\ua5d1\ua62b\ua61e\ua5d2\ua5d3\ua5d4\ua5d5\ua5d6\ua5d7\ua5d8\ua61f\ua5d9\ua5da\ua5db\ua5dc\ua5dd\ua5de\ua5df\ua5e0\ua5e1\ua5e2\ua5e3\ua5e4\ua5e5\ua5e6\ua5e7\ua5e8\ua5e9\ua5ea\ua5eb\ua5ec\ua5ed\ua5ee\ua5ef\ua5f0\ua5f1\ua5f2\ua5f3\ua5f4\ua5f5\ua5f6\ua5f7\ua5f8\ua5f9\ua5fa\ua5fb\ua5fc\ua5fd\ua5fe\ua5ff\ua600\ua601\ua602\ua603\ua604\ua605\ua606\ua607\ua608\ua609\ua60a\ua60b\ua60c\ua6a0\ua6a1\ua6a2\ua6a3\ua6a4\ua6a5\ua6a6\ua6a7\ua6a8\ua6a9\ua6aa\ua6ab\ua6ac\ua6ad\ua6ae\ua6af\ua6b0\ua6b1\ua6b2\ua6b3\ua6b4\ua6b5\ua6b6\ua6b7\ua6b8\ua6b9\ua6ba\ua6bb\ua6bc\ua6bd\ua6be\ua6bf\ua6c0\ua6c1\ua6c2\ua6c3\ua6c4\ua6c5\ua6c6\ua6c7\ua6c8\ua6c9\ua6ca\ua6cb\ua6cc\ua6cd\ua6ce\ua6cf\ua6d0\ua6d1\ua6d2\ua6d3\ua6d4\ua6d5\ua6d6\ua6d7\ua6d8\ua6d9\ua6da\ua6db\ua6dc\ua6dd\ua6de\ua6df\ua6e0\ua6e1\ua6e2\ua6e3\ua6e4\ua6e5\ua6e6\ua6e7\ua6e8\ua6e9\ua6ea\ua6eb\ua6ec\ua6ed\ua6ee\ua6ef\u1100\u3131\u3260\uffa1\u326e\u1101\u3132\uffa2\u1102\u3134\u3261\uffa4\u326f\u1103\u3137\u3262\uffa7\u3270\u1104\u3138\uffa8\u1105\u3139\u3263\uffa9\u3271\u1106\u3141\u3264\uffb1\u3272\u1107\u3142\u3265\uffb2\u3273\u1108\u3143\uffb3\u1109\u3145\u3266\uffb5\u3274\u110a\u3146\uffb6\u110b\u3147\u3267\uffb7\u3275\u327e\u110c\u3148\u3268\uffb8\u3276\u327d\u110d\u3149\uffb9\u110e\u314a\u3269\uffba\u3277\u327c\u110f\u314b\u326a\uffbb\u3278\u1110\u314c\u326b\uffbc\u3279\u1111\u314d\u326c\uffbd\u327a\u1112\u314e\u326d\uffbe\u327b\u1113\u1114\u3165\u1115\u3166\u1116\u1117\u1118\u1119\u111a\u3140\uffb0\u111b\u111c\u316e\u111d\u3171\u111e\u3172\u111f\u1120\u3173\u1121\u3144\uffb4\u1122\u3174\u1123\u3175\u1124\u1125\u1126\u1127\u3176\u1128\u1129\u3177\u112a\u112b\u3178\u112c\u3179\u112d\u317a\u112e\u317b\u112f\u317c\u1130\u1131\u1132\u317d\u1133\u1134\u1135\u1136\u317e\u1137\u1138\u1139\u113a\u113b\u113c\u113d\u113e\u113f\u1140\u317f\u1141\u1142\u1143\u1144\u1145\u1146\u1147\u3180\u1148\u1149\u114a\u114b\u114c\u3181\u114d\u114e\u114f\u1150\u1151\u1152\u1153\u1154\u1155\u1156\u1157\u3184\u1158\u3185\u1159\u3186\u115a\u115b\u115c\u115d\u115e\ua960\ua961\ua962\ua963\ua964\ua965\ua966\ua967\ua968\ua969\ua96a\ua96b\ua96c\ua96d\ua96e\ua96f\ua970\ua971\ua972\ua973\ua974\ua975\ua976\ua977\ua978\ua979\ua97a\ua97b\ua97c\u115f\u1160\u3164\uffa0\u1161\u314f\uffc2\u1162\u3150\uffc3\u1163\u3151\uffc4\u1164\u3152\uffc5\u1165\u3153\uffc6\u1166\u3154\uffc7\u1167\u3155\uffca\u1168\u3156\uffcb\u1169\u3157\uffcc\u116a\u3158\uffcd\u116b\u3159\uffce\u116c\u315a\uffcf\u116d\u315b\uffd2\u116e\u315c\uffd3\u116f\u315d\uffd4\u1170\u315e\uffd5\u1171\u315f\uffd6\u1172\u3160\uffd7\u1173\u3161\uffda\u1174\u3162\uffdb\u1175\u3163\uffdc\u1176\u1177\u1178\u1179\u117a\u117b\u117c\u117d\u117e\u117f\u1180\u1181\u1182\u1183\u1184\u3187\u1185\u3188\u1186\u1187\u1188\u3189\u1189\u118a\u118b\u118c\u118d\u118e\u118f\u1190\u1191\u318a\u1192\u318b\u1193\u1194\u318c\u1195\u1196\u1197\u1198\u1199\u119a\u119b\u119c\u119d\u119e\u318d\u119f\u11a0\u11a1\u318e\u11a2\u11a3\u11a4\u11a5\u11a6\u11a7\ud7b0\ud7b1\ud7b2\ud7b3\ud7b4\ud7b5\ud7b6\ud7b7\ud7b8\ud7b9\ud7ba\ud7bb\ud7bc\ud7bd\ud7be\ud7bf\ud7c0\ud7c1\ud7c2\ud7c3\ud7c4\ud7c5\ud7c6\u11a8\u11a9\u11aa\u3133\uffa3\u11ab\u11ac\u3135\uffa5\u11ad\u3136\uffa6\u11ae\u11af\u11b0\u313a\uffaa\u11b1\u313b\uffab\u11b2\u313c\uffac\u11b3\u313d\uffad\u11b4\u313e\uffae\u11b5\u313f\uffaf\u11b6\u11b7\u11b8\u11b9\u11ba\u11bb\u11bc\u11bd\u11be\u11bf\u11c0\u11c1\u11c2\u11c3\u11c4\u11c5\u11c6\u11c7\u3167\u11c8\u3168\u11c9\u11ca\u11cb\u11cc\u3169\u11cd\u11ce\u316a\u11cf\u11d0\u11d1\u11d2\u11d3\u316b\u11d4\u11d5\u11d6\u11d7\u316c\u11d8\u11d9\u316d\u11da\u11db\u11dc\u11dd\u316f\u11de\u11df\u3170\u11e0\u11e1\u11e2\u11e3\u11e4\u11e5\u11e6\u11e7\u11e8\u11e9\u11ea\u11eb\u11ec\u11ed\u11ee\u11ef\u11f0\u11f1\u3182\u11f2\u3183\u11f3\u11f4\u11f5\u11f6\u11f7\u11f8\u11f9\u11fa\u11fb\u11fc\u11fd\u11fe\u11ff\ud7cb\ud7cc\ud7cd\ud7ce\ud7cf\ud7d0\ud7d1\ud7d2\ud7d3\ud7d4\ud7d5\ud7d6\ud7d7\ud7d8\ud7d9\ud7da\ud7db\ud7dc\ud7dd\ud7de\ud7df\ud7e0\ud7e1\ud7e2\ud7e3\ud7e4\ud7e5\ud7e6\ud7e7\ud7e8\ud7e9\ud7ea\ud7eb\ud7ec\ud7ed\ud7ee\ud7ef\ud7f0\ud7f1\ud7f2\ud7f3\ud7f4\ud7f5\ud7f6\ud7f7\ud7f8\ud7f9\ud7fa\ud7fb\u30a1\uff67\u30a2\uff71\u3041\u3042\u32d0\u3303\u3300\u3301\u3302\u30a3\uff68\u30a4\uff72\u3043\u3044\u32d1\u3304\u3305\u30a5\uff69\u30a6\uff73\u3045\u3046\u32d2\u30f4\u3094\u3306\u30a7\uff6a\u30a8\uff74\u3047\u3048\u32d3\u3308\u3307\u30a9\uff6b\u30aa\uff75\u3049\u304a\u32d4\u330a\u3309\u30f5\u30ab\uff76\u3095\u304b\u32d5\u30ac\u304c\u330e\u330f\u330b\u330c\u330d\u30ad\uff77\u304d\u32d6\u30ae\u304e\u3310\u3311\u3313\u3312\u3314\u3315\u3316\u3317\u31f0\u30af\uff78\u304f\u32d7\u30b0\u3050\u3318\u3319\u331a\u331b\u30f6\u30b1\uff79\u3096\u3051\u32d8\u30b2\u3052\u331c\u30b3\uff7a\u3053\u32d9\u30b4\u3054\u331e\u30ff\u331d\u30b5\uff7b\u3055\u32da\u30b6\u3056\u331f\u3320\u31f1\u30b7\uff7c\u3057\u32db\u30b8\u3058\u3006\u3321\u31f2\u30b9\uff7d\u3059\u32dc\u30ba\u305a\u30bb\uff7e\u305b\u32dd\u30bc\u305c\u3322\u3323\u30bd\uff7f\u305d\u32de\u30be\u305e\u30bf\uff80\u305f\u32df\u30c0\u3060\u3324\u30c1\uff81\u3061\u32e0\u30c2\u3062\u30c3\uff6f\u30c4\uff82\u3063\u3064\u32e1\u30c5\u3065\u30c6\uff83\u3066\u32e2\u30c7\u3067\u3325\u31f3\u30c8\uff84\u3068\u32e3\u30c9\u3069\u3326\u3327\u30ca\uff85\u306a\u32e4\u3328\u30cb\uff86\u306b\u32e5\u31f4\u30cc\uff87\u306c\u32e6\u30cd\uff88\u306d\u32e7\u30ce\uff89\u306e\u32e8\u3329\u31f5\u30cf\uff8a\u306f\u32e9\u30d0\u3070\u30d1\u3071\u332b\u332c\u332d\u332a\u31f6\u30d2\uff8b\u3072\u32ea\u30d3\u3073\u30d4\u3074\u332e\u332f\u3330\u3331\u31f7\u30d5\uff8c\u3075\u32eb\u30d6\u3076\u30d7\u3077\u3334\u3332\u3333\u3335\u31f8\u30d8\uff8d\u3078\u32ec\u30d9\u3079\u30da\u307a\u333b\u333c\u3337\u3338\u333a\u3336\u3339\u31f9\u30db\uff8e\u307b\u32ed\u30dc\u307c\u30dd\u307d\u333d\u333e\u3340\u3341\u3342\u333f\u30de\uff8f\u307e\u32ee\u3343\u3344\u303c\u3345\u3346\u3347\u30df\uff90\u307f\u32ef\u3348\u3349\u334a\u31fa\u30e0\uff91\u3080\u32f0\u30e1\uff92\u3081\u32f1\u334d\u334b\u334c\u30e2\uff93\u3082\u32f2\u30e3\uff6c\u30e4\uff94\u3083\u3084\u32f3\u334e\u334f\u30e5\uff6d\u30e6\uff95\u3085\u3086\u32f4\u3350\u30e7\uff6e\u30e8\uff96\u3087\u3088\u32f5\u309f\u31fb\u30e9\uff97\u3089\u32f6\u31fc\u30ea\uff98\u308a\u32f7\u3351\u3352\u31fd\u30eb\uff99\u308b\u32f8\u3354\u3353\u31fe\u30ec\uff9a\u308c\u32f9\u3355\u3356\u31ff\u30ed\uff9b\u308d\u32fa\u30ee\u30ef\uff9c\u308e\u308f\u32fb\u30f7\u3357\u30f0\u3090\u32fc\u30f8\u30f1\u3091\u32fd\u30f9\u30f2\uff66\u3092\u32fe\u30fa\u30f3\uff9d\u3093\u3105\u31a0\u3106\u31b4\u3107\u31ac\u3108\u312a\u3109\u310a\u31b5\u310b\u312f\u310c\u310d\u31bb\u31a3\u310e\u31b6\u312b\u31ad\u310f\u31b7\u3110\u31a2\u3111\u3112\u312c\u31bc\u31bd\u3113\u3114\u3115\u3116\u3117\u31a1\u3118\u3119\u31b8\u31b9\u31ba\u311a\u31a9\u311b\u31a7\u31a6\u311c\u312e\u311d\u31a4\u31a5\u311e\u31ae\u311f\u3120\u31af\u3121\u3122\u3123\u3124\u31b2\u3125\u31b0\u31b1\u3126\u3127\u31aa\u31b3\u3128\u31ab\u31a8\u3129\u312d\u31be\u31bf\ua000\ua001\ua002\ua003\ua004\ua005\ua006\ua007\ua008\ua009\ua00a\ua00b\ua00c\ua00d\ua00e\ua00f\ua010\ua011\ua012\ua013\ua014\ua015\ua016\ua017\ua018\ua019\ua01a\ua01b\ua01c\ua01d\ua01e\ua01f\ua020\ua021\ua022\ua023\ua024\ua025\ua026\ua027\ua028\ua029\ua02a\ua02b\ua02c\ua02d\ua02e\ua02f\ua030\ua031\ua032\ua033\ua034\ua035\ua036\ua037\ua038\ua039\ua03a\ua03b\ua03c\ua03d\ua03e\ua03f\ua040\ua041\ua042\ua043\ua044\ua045\ua046\ua047\ua048\ua049\ua04a\ua04b\ua04c\ua04d\ua04e\ua04f\ua050\ua051\ua052\ua053\ua054\ua055\ua056\ua057\ua058\ua059\ua05a\ua05b\ua05c\ua05d\ua05e\ua05f\ua060\ua061\ua062\ua063\ua064\ua065\ua066\ua067\ua068\ua069\ua06a\ua06b\ua06c\ua06d\ua06e\ua06f\ua070\ua071\ua072\ua073\ua074\ua075\ua076\ua077\ua078\ua079\ua07a\ua07b\ua07c\ua07d\ua07e\ua07f\ua080\ua081\ua082\ua083\ua084\ua085\ua086\ua087\ua088\ua089\ua08a\ua08b\ua08c\ua08d\ua08e\ua08f\ua090\ua091\ua092\ua093\ua094\ua095\ua096\ua097\ua098\ua099\ua09a\ua09b\ua09c\ua09d\ua09e\ua09f\ua0a0\ua0a1\ua0a2\ua0a3\ua0a4\ua0a5\ua0a6\ua0a7\ua0a8\ua0a9\ua0aa\ua0ab\ua0ac\ua0ad\ua0ae\ua0af\ua0b0\ua0b1\ua0b2\ua0b3\ua0b4\ua0b5\ua0b6\ua0b7\ua0b8\ua0b9\ua0ba\ua0bb\ua0bc\ua0bd\ua0be\ua0bf\ua0c0\ua0c1\ua0c2\ua0c3\ua0c4\ua0c5\ua0c6\ua0c7\ua0c8\ua0c9\ua0ca\ua0cb\ua0cc\ua0cd\ua0ce\ua0cf\ua0d0\ua0d1\ua0d2\ua0d3\ua0d4\ua0d5\ua0d6\ua0d7\ua0d8\ua0d9\ua0da\ua0db\ua0dc\ua0dd\ua0de\ua0df\ua0e0\ua0e1\ua0e2\ua0e3\ua0e4\ua0e5\ua0e6\ua0e7\ua0e8\ua0e9\ua0ea\ua0eb\ua0ec\ua0ed\ua0ee\ua0ef\ua0f0\ua0f1\ua0f2\ua0f3\ua0f4\ua0f5\ua0f6\ua0f7\ua0f8\ua0f9\ua0fa\ua0fb\ua0fc\ua0fd\ua0fe\ua0ff\ua100\ua101\ua102\ua103\ua104\ua105\ua106\ua107\ua108\ua109\ua10a\ua10b\ua10c\ua10d\ua10e\ua10f\ua110\ua111\ua112\ua113\ua114\ua115\ua116\ua117\ua118\ua119\ua11a\ua11b\ua11c\ua11d\ua11e\ua11f\ua120\ua121\ua122\ua123\ua124\ua125\ua126\ua127\ua128\ua129\ua12a\ua12b\ua12c\ua12d\ua12e\ua12f\ua130\ua131\ua132\ua133\ua134\ua135\ua136\ua137\ua138\ua139\ua13a\ua13b\ua13c\ua13d\ua13e\ua13f\ua140\ua141\ua142\ua143\ua144\ua145\ua146\ua147\ua148\ua149\ua14a\ua14b\ua14c\ua14d\ua14e\ua14f\ua150\ua151\ua152\ua153\ua154\ua155\ua156\ua157\ua158\ua159\ua15a\ua15b\ua15c\ua15d\ua15e\ua15f\ua160\ua161\ua162\ua163\ua164\ua165\ua166\ua167\ua168\ua169\ua16a\ua16b\ua16c\ua16d\ua16e\ua16f\ua170\ua171\ua172\ua173\ua174\ua175\ua176\ua177\ua178\ua179\ua17a\ua17b\ua17c\ua17d\ua17e\ua17f\ua180\ua181\ua182\ua183\ua184\ua185\ua186\ua187\ua188\ua189\ua18a\ua18b\ua18c\ua18d\ua18e\ua18f\ua190\ua191\ua192\ua193\ua194\ua195\ua196\ua197\ua198\ua199\ua19a\ua19b\ua19c\ua19d\ua19e\ua19f\ua1a0\ua1a1\ua1a2\ua1a3\ua1a4\ua1a5\ua1a6\ua1a7\ua1a8\ua1a9\ua1aa\ua1ab\ua1ac\ua1ad\ua1ae\ua1af\ua1b0\ua1b1\ua1b2\ua1b3\ua1b4\ua1b5\ua1b6\ua1b7\ua1b8\ua1b9\ua1ba\ua1bb\ua1bc\ua1bd\ua1be\ua1bf\ua1c0\ua1c1\ua1c2\ua1c3\ua1c4\ua1c5\ua1c6\ua1c7\ua1c8\ua1c9\ua1ca\ua1cb\ua1cc\ua1cd\ua1ce\ua1cf\ua1d0\ua1d1\ua1d2\ua1d3\ua1d4\ua1d5\ua1d6\ua1d7\ua1d8\ua1d9\ua1da\ua1db\ua1dc\ua1dd\ua1de\ua1df\ua1e0\ua1e1\ua1e2\ua1e3\ua1e4\ua1e5\ua1e6\ua1e7\ua1e8\ua1e9\ua1ea\ua1eb\ua1ec\ua1ed\ua1ee\ua1ef\ua1f0\ua1f1\ua1f2\ua1f3\ua1f4\ua1f5\ua1f6\ua1f7\ua1f8\ua1f9\ua1fa\ua1fb\ua1fc\ua1fd\ua1fe\ua1ff\ua200\ua201\ua202\ua203\ua204\ua205\ua206\ua207\ua208\ua209\ua20a\ua20b\ua20c\ua20d\ua20e\ua20f\ua210\ua211\ua212\ua213\ua214\ua215\ua216\ua217\ua218\ua219\ua21a\ua21b\ua21c\ua21d\ua21e\ua21f\ua220\ua221\ua222\ua223\ua224\ua225\ua226\ua227\ua228\ua229\ua22a\ua22b\ua22c\ua22d\ua22e\ua22f\ua230\ua231\ua232\ua233\ua234\ua235\ua236\ua237\ua238\ua239\ua23a\ua23b\ua23c\ua23d\ua23e\ua23f\ua240\ua241\ua242\ua243\ua244\ua245\ua246\ua247\ua248\ua249\ua24a\ua24b\ua24c\ua24d\ua24e\ua24f\ua250\ua251\ua252\ua253\ua254\ua255\ua256\ua257\ua258\ua259\ua25a\ua25b\ua25c\ua25d\ua25e\ua25f\ua260\ua261\ua262\ua263\ua264\ua265\ua266\ua267\ua268\ua269\ua26a\ua26b\ua26c\ua26d\ua26e\ua26f\ua270\ua271\ua272\ua273\ua274\ua275\ua276\ua277\ua278\ua279\ua27a\ua27b\ua27c\ua27d\ua27e\ua27f\ua280\ua281\ua282\ua283\ua284\ua285\ua286\ua287\ua288\ua289\ua28a\ua28b\ua28c\ua28d\ua28e\ua28f\ua290\ua291\ua292\ua293\ua294\ua295\ua296\ua297\ua298\ua299\ua29a\ua29b\ua29c\ua29d\ua29e\ua29f\ua2a0\ua2a1\ua2a2\ua2a3\ua2a4\ua2a5\ua2a6\ua2a7\ua2a8\ua2a9\ua2aa\ua2ab\ua2ac\ua2ad\ua2ae\ua2af\ua2b0\ua2b1\ua2b2\ua2b3\ua2b4\ua2b5\ua2b6\ua2b7\ua2b8\ua2b9\ua2ba\ua2bb\ua2bc\ua2bd\ua2be\ua2bf\ua2c0\ua2c1\ua2c2\ua2c3\ua2c4\ua2c5\ua2c6\ua2c7\ua2c8\ua2c9\ua2ca\ua2cb\ua2cc\ua2cd\ua2ce\ua2cf\ua2d0\ua2d1\ua2d2\ua2d3\ua2d4\ua2d5\ua2d6\ua2d7\ua2d8\ua2d9\ua2da\ua2db\ua2dc\ua2dd\ua2de\ua2df\ua2e0\ua2e1\ua2e2\ua2e3\ua2e4\ua2e5\ua2e6\ua2e7\ua2e8\ua2e9\ua2ea\ua2eb\ua2ec\ua2ed\ua2ee\ua2ef\ua2f0\ua2f1\ua2f2\ua2f3\ua2f4\ua2f5\ua2f6\ua2f7\ua2f8\ua2f9\ua2fa\ua2fb\ua2fc\ua2fd\ua2fe\ua2ff\ua300\ua301\ua302\ua303\ua304\ua305\ua306\ua307\ua308\ua309\ua30a\ua30b\ua30c\ua30d\ua30e\ua30f\ua310\ua311\ua312\ua313\ua314\ua315\ua316\ua317\ua318\ua319\ua31a\ua31b\ua31c\ua31d\ua31e\ua31f\ua320\ua321\ua322\ua323\ua324\ua325\ua326\ua327\ua328\ua329\ua32a\ua32b\ua32c\ua32d\ua32e\ua32f\ua330\ua331\ua332\ua333\ua334\ua335\ua336\ua337\ua338\ua339\ua33a\ua33b\ua33c\ua33d\ua33e\ua33f\ua340\ua341\ua342\ua343\ua344\ua345\ua346\ua347\ua348\ua349\ua34a\ua34b\ua34c\ua34d\ua34e\ua34f\ua350\ua351\ua352\ua353\ua354\ua355\ua356\ua357\ua358\ua359\ua35a\ua35b\ua35c\ua35d\ua35e\ua35f\ua360\ua361\ua362\ua363\ua364\ua365\ua366\ua367\ua368\ua369\ua36a\ua36b\ua36c\ua36d\ua36e\ua36f\ua370\ua371\ua372\ua373\ua374\ua375\ua376\ua377\ua378\ua379\ua37a\ua37b\ua37c\ua37d\ua37e\ua37f\ua380\ua381\ua382\ua383\ua384\ua385\ua386\ua387\ua388\ua389\ua38a\ua38b\ua38c\ua38d\ua38e\ua38f\ua390\ua391\ua392\ua393\ua394\ua395\ua396\ua397\ua398\ua399\ua39a\ua39b\ua39c\ua39d\ua39e\ua39f\ua3a0\ua3a1\ua3a2\ua3a3\ua3a4\ua3a5\ua3a6\ua3a7\ua3a8\ua3a9\ua3aa\ua3ab\ua3ac\ua3ad\ua3ae\ua3af\ua3b0\ua3b1\ua3b2\ua3b3\ua3b4\ua3b5\ua3b6\ua3b7\ua3b8\ua3b9\ua3ba\ua3bb\ua3bc\ua3bd\ua3be\ua3bf\ua3c0\ua3c1\ua3c2\ua3c3\ua3c4\ua3c5\ua3c6\ua3c7\ua3c8\ua3c9\ua3ca\ua3cb\ua3cc\ua3cd\ua3ce\ua3cf\ua3d0\ua3d1\ua3d2\ua3d3\ua3d4\ua3d5\ua3d6\ua3d7\ua3d8\ua3d9\ua3da\ua3db\ua3dc\ua3dd\ua3de\ua3df\ua3e0\ua3e1\ua3e2\ua3e3\ua3e4\ua3e5\ua3e6\ua3e7\ua3e8\ua3e9\ua3ea\ua3eb\ua3ec\ua3ed\ua3ee\ua3ef\ua3f0\ua3f1\ua3f2\ua3f3\ua3f4\ua3f5\ua3f6\ua3f7\ua3f8\ua3f9\ua3fa\ua3fb\ua3fc\ua3fd\ua3fe\ua3ff\ua400\ua401\ua402\ua403\ua404\ua405\ua406\ua407\ua408\ua409\ua40a\ua40b\ua40c\ua40d\ua40e\ua40f\ua410\ua411\ua412\ua413\ua414\ua415\ua416\ua417\ua418\ua419\ua41a\ua41b\ua41c\ua41d\ua41e\ua41f\ua420\ua421\ua422\ua423\ua424\ua425\ua426\ua427\ua428\ua429\ua42a\ua42b\ua42c\ua42d\ua42e\ua42f\ua430\ua431\ua432\ua433\ua434\ua435\ua436\ua437\ua438\ua439\ua43a\ua43b\ua43c\ua43d\ua43e\ua43f\ua440\ua441\ua442\ua443\ua444\ua445\ua446\ua447\ua448\ua449\ua44a\ua44b\ua44c\ua44d\ua44e\ua44f\ua450\ua451\ua452\ua453\ua454\ua455\ua456\ua457\ua458\ua459\ua45a\ua45b\ua45c\ua45d\ua45e\ua45f\ua460\ua461\ua462\ua463\ua464\ua465\ua466\ua467\ua468\ua469\ua46a\ua46b\ua46c\ua46d\ua46e\ua46f\ua470\ua471\ua472\ua473\ua474\ua475\ua476\ua477\ua478\ua479\ua47a\ua47b\ua47c\ua47d\ua47e\ua47f\ua480\ua481\ua482\ua483\ua484\ua485\ua486\ua487\ua488\ua489\ua48a\ua48b\ua48c\ua4f8\ua4f9\ua4fa\ua4fb\ua4fd\ua4fc\ua4d0\ua4d1\ua4d2\ua4d3\ua4d4\ua4d5\ua4d6\ua4d7\ua4d8\ua4d9\ua4da\ua4db\ua4dc\ua4dd\ua4de\ua4df\ua4e0\ua4e1\ua4e2\ua4e3\ua4e4\ua4e5\ua4e6\ua4e7\ua4e8\ua4e9\ua4eb\ua4ed\ua4ea\ua4ec\ua4ee\ua4ef\ua4f0\ua4f1\ua4f2\ua4f3\ua4f4\ua4f5\ua4f6\ua4f7\u4e00\u4e01\u4e02\u4e03\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e90\u5344\u4e0d\u4e0e\u4e0f\u4e10\u4e11\u4e12\u4e13\u4e17\u4e14\u4e15\u4e16\u4e18\u4e19\u4e1a\u4e1b\u4e1c\u4e1d\u3400\u4e1e\u4e1f\u4e20\u4e21\u4e22\u3401\u3402\u4e23\u4e24\u4e25\u4e3d\u9fd6\u4e26\u4e27\u9fd7\u4db6\u4e28\u4e29\u4e2a\u4e2b\u3403\u3404\u4e2d\u4e2e\u4e2f\u4e30\u4e31\u4e32\u4e33\u4e34\u4e35\u4e36\u4e37\u4e38\u4e49\u4e39\u4e3a\u4e3b\u4e3c\u4e3e\u4e3f\u4e40\u4e41\u4e42\u4e43\u4e44\u3405\u4e45\u4e46\u4e47\u4e48\u4e4a\u4e4b\u4e4c\u5c39\u4e4d\u4e4e\u4e4f\u4e50\u4e51\u4e52\u4e53\u4e54\u3406\u4e55\u4e56\u4e57\u4e58\u4e59\u4e5a\u4e5b\u4e5c\u4e5d\u9fb4\u4e5e\u4e5f\u4e60\u3407\u3408\u3409\u4e63\u4e64\u4e65\u4e66\u340a\u340b\u4e67\u340c\u340d\u340e\u340f\u4e68\u4e69\u4e6a\u4e6b\u4e6c\u4e6d\u4e6e\u4e6f\u4e70\u3410\u3411\u3412\u3413\u3414\u3415\u4e71\u4e72\u3416\u3417\u3418\u4e73\u4e74\u4e75\u4e76\u4e77\u4e78\u3419\u341a\u341b\u4e79\u4e7a\u4e7b\u4e7c\u341c\u341d\u341e\u341f\u3420\u4e7d\u4e7e\u4e7f\u4e80\u3421\u3422\u3423\u4e81\u3424\u4e82\u4e83\u4e84\u3425\u3426\u4e85\u4e86\u4e87\u4e88\u3427\u4e89\u4e8a\u4e8b\u3428\u4e8c\u4e8d\u4e8e\u4e8f\u4e91\u4e92\u4e93\u4e94\u4e95\u4e96\u4e97\u4e98\u4e99\u4e9a\u4e9b\u4e9c\u4e9d\u4e9e\u4e9f\u3429\u4ea0\u4ea1\u4ea2\u4ea3\u4ea4\u4ea5\u4ea6\u4ea7\u342a\u342b\u4ea8\u4ea9\u4eaa\u342c\u4eab\u4eac\u342d\u4ead\u4eae\u4eaf\u4eb0\u4eb1\u4eb2\u4eb3\u4eb4\u4eb5\u4eb6\u4eb7\u342e\u4eb8\u342f\u4eb9\u4eba\u4ebb\u4ebc\u4ebd\u4ebe\u4ebf\u4ec0\u4ec1\u4ec2\u4ec3\u4ec4\u4ec5\u4ec6\u4ec7\u4ec8\u4ec9\u4eca\u4ecb\u4ecc\u4ecd\u4ece\u4ecf\u4ed0\u4ed1\u4ed2\u4ed3\u4ed4\u4ed5\u4ed6\u4ed7\u4ed8\u4ed9\u4eda\u4edb\u4edc\u4edd\u4ede\u4edf\u4ee0\u4ee1\u4ee2\u4ee3\u4ee4\u4ee5\u4ee6\u4ee7\u4ee8\u4ee9\u4eea\u4eeb\u4eec\u4eed\u3430\u3431\u3432\u3433\u3434\u3435\u3436\u3437\u4eee\u4eef\u4ef0\u4ef1\u4ef2\u4ef3\u4ef4\u4ef5\u4ef6\u4ef7\u4ef8\u4ef9\u4efa\u4efb\u4efc\u4efd\u4efe\u4eff\u4f00\u4f01\u4f02\u4f03\u4f04\u4f05\u4f06\u4f07\u4f08\u4f09\u4f0a\u4f0b\u4f0c\u4f0d\u4f0e\u4f0f\u4f10\u4f11\u4f12\u4f13\u4f14\u4f15\u4f16\u4f17\u4f18\u4f19\u4f1a\u4f1b\u4f1c\u4f1d\u4f1e\u4f1f\u4f20\u4f21\u4f22\u4f23\u4f24\u4f25\u4f26\u4f27\u4f28\u4f29\u4f2a\u4f2b\u4f2c\u4f64\u3438\u3439\u343a\u343b\u343c\u343d\u343e\u343f\u3440\u4f2d\u4f2e\u4f2f\u4f30\u4f31\u4f32\u4f33\u4f34\u4f35\u4f36\u4f37\u4f38\u4f39\u4f3a\u4f3b\u4f3c\u4f3d\u4f3e\u4f3f\u4f40\u4f41\u4f42\u4f43\u4f44\u4f45\u4f46\u4f47\u4f48\u4f49\u4f4a\u4f4b\u4f4c\u4f4d\u4f4e\u4f4f\u4f50\u4f51\u4f52\u4f53\u4f54\u4f55\u4f56\u4f57\u4f58\u4f59\u4f5a\u4f5b\u4f5c\u4f5d\u4f5e\u4f5f\u4f60\u4f61\u4f62\u4f63\u4f65\u4f66\u4f67\u4f68\u3441\u3442\u3443\u3444\u3445\u3446\u3447\u3448\u4f69\u4f6a\u4f6b\u4f6c\u4f6d\u4f6e\u4f6f\u4f70\u4f71\u4f72\u4f73\u4f74\u4f75\u4f76\u4f77\u4f78\u4f79\u4f7a\u4f7b\u4f7c\u4f7d\u4f7e\u4f7f\u4f80\u4f81\u4f82\u4f83\u4f84\u4f85\u4f86\u4f87\u4f88\u4f89\u4f8a\u4f8b\u4f8c\u4f8d\u4f8e\u4f8f\u4f90\u4f91\u4f92\u4f93\u4f94\u4f95\u4f96\u4f97\u4f98\u4f99\u4f9a\u4f9b\u4f9c\u4f9d\u4f9e\u4f9f\u4fa0\u4fa1\u4fa2\u4fa3\u4fa4\u4fa5\u4fa6\u4fa7\u4fa8\u4fa9\u4faa\u4fab\u4fac\u4fad\u9fc7\u3449\u344a\u344b\u344c\u344d\u344e\u344f\u3450\u3451\u3452\u3453\u3454\u3455\u3456\u4fae\u4faf\u4fb0\u4fb1\u4fb2\u4fb3\u4fb4\u4fb5\u4fb6\u4fb7\u4fb8\u4fb9\u4fba\u4fbb\u4fbc\u4fbd\u4fbe\u4fbf\u4fc0\u4fc1\u4fc2\u4fc3\u4fc4\u4fc5\u4fc6\u4fc7\u4fc8\u4fc9\u4fca\u4fcb\u4fcc\u4fcd\u4fce\u4fcf\u4fd0\u4fd1\u4fd2\u4fd3\u4fd4\u4fd5\u4fd6\u4fd7\u4fd8\u4fd9\u4fda\u4fdb\u4fdc\u4fdd\u4fde\u4fdf\u4fe0\u4fe1\u4fe2\u4fe3\u4fe4\u4fe5\u4fe6\u4fe7\u4fe8\u4fe9\u4fea\u4feb\u4fec\u4fed\u3457\u3458\u3459\u345a\u345b\u345c\u345d\u345e\u345f\u3460\u3461\u3462\u4fee\u4fef\u4ff0\u4ff1\u4ff2\u4ff3\u4ff4\u4ff5\u4ff6\u4ff7\u4ff8\u4ff9\u4ffa\u4ffb\u4ffc\u4ffd\u4ffe\u4fff\u5000\u5001\u5002\u5003\u5004\u5005\u5006\u5007\u5008\u5009\u500a\u500b\u500c\u500d\u500e\u500f\u5010\u5011\u5012\u5013\u5014\u5015\u5016\u5017\u5018\u5019\u501a\u501b\u501c\u501d\u501e\u501f\u5020\u5021\u5022\u5023\u5024\u5025\u5026\u5027\u5028\u5029\u502a\u502b\u502c\u502d\u502e\u502f\u5030\u5031\u5032\u5033\u5034\u5035\u5036\u5037\u5038\u5039\u503a\u503b\u503c\u503d\u503e\u503f\u3463\u3465\u3466\u3467\u3468\u3469\u346a\u346b\u346c\u346d\u5040\u5041\u5042\u5043\u5044\u5045\u5046\u5047\u5048\u5049\u504a\u504b\u504c\u504d\u504e\u504f\u5050\u5051\u5052\u5053\u5054\u5055\u5056\u5057\u5058\u5059\u505a\u505b\u505c\u505d\u505e\u505f\u5060\u5061\u5062\u5063\u5064\u5065\u5066\u5067\u5068\u5069\u506a\u506b\u506c\u506d\u506e\u506f\u5070\u5071\u5072\u5073\u5074\u5075\u5076\u5077\u5078\u5079\u507a\u507b\u507c\u507d\u507e\u507f\u3464\u346e\u346f\u3470\u3471\u3472\u5080\u5081\u5082\u5083\u5084\u5085\u5086\u5087\u5088\u5089\u508a\u508b\u508c\u508d\u508e\u508f\u5090\u5091\u5092\u5093\u5094\u5095\u5096\u5097\u5098\u5099\u509a\u509b\u509c\u509d\u509e\u509f\u50a0\u50a1\u50a2\u50a3\u50a4\u50a5\u50a6\u50a7\u50a8\u50a9\u9fd8\u3473\u3474\u3475\u3476\u3477\u3478\u3479\u347a\u50aa\u50ab\u50ac\u50ad\u50ae\u50af\u50b0\u50b1\u50b2\u50b3\u50b4\u50b5\u50b6\u50b7\u50b8\u50b9\u50ba\u50bb\u50bc\u50bd\u50be\u50bf\u50c0\u50c1\u50c2\u50c3\u50c4\u50c5\u50c6\u50c7\u50c8\u50c9\u50ca\u50cb\u50cc\u50cd\u9fd9\u347b\u347c\u347d\u347e\u347f\u3480\u3481\u50ce\u50cf\u50d0\u50d1\u50d2\u50d3\u50d4\u50d5\u50d6\u50d7\u50d8\u50d9\u50da\u50db\u50dc\u50dd\u50de\u50df\u50e0\u50e1\u50e2\u50e3\u50e4\u50e5\u50e6\u50e7\u50e8\u50e9\u50ea\u50eb\u50ec\u50ed\u50ee\u50ef\u50f0\u50f1\u50f3\u50f4\u50f7\u3482\u3483\u3484\u3485\u3486\u3487\u3488\u3489\u348a\u348b\u348c\u348d\u348e\u348f\u3490\u50f2\u50f5\u50f6\u50f8\u50f9\u50fa\u50fb\u50fc\u50fd\u50fe\u50ff\u5100\u5101\u5102\u5103\u5104\u5105\u5106\u5107\u5108\u5109\u510a\u510b\u510c\u510d\u510e\u510f\u3491\u3492\u3493\u3494\u3495\u3496\u3497\u3498\u5110\u5111\u5112\u5113\u5114\u5115\u5116\u5117\u5118\u5119\u511a\u511b\u511c\u511d\u511e\u512b\u9fda\u3499\u349a\u349b\u349c\u511f\u5120\u5121\u5122\u5123\u5124\u5125\u5126\u5127\u5128\u5129\u512a\u512c\u349d\u349e\u512d\u512e\u512f\u5130\u5131\u5132\u349f\u34a0\u5133\u5134\u5135\u34a1\u34a2\u34a3\u5136\u34a4\u34a5\u34a6\u5137\u5138\u5139\u513a\u34a7\u34a8\u513b\u513c\u513d\u34a9\u513e\u34aa\u513f\u5140\u5141\u5142\u5143\u5144\u5145\u5146\u5147\u5148\u5149\u514a\u34ab\u514b\u514c\u514d\u514e\u514f\u5150\u5151\u5152\u5153\u5154\u5155\u5156\u34ac\u5157\u5158\u5159\u515a\u515b\u34ad\u515c\u515d\u515e\u34ae\u515f\u5160\u5161\u5162\u5163\u34af\u5164\u5165\u5166\u5167\u34b0\u34b1\u5168\u6c3d\u34b2\u34b3\u34b4\u5169\u516a\u516b\u516c\u516d\u516e\u516f\u5170\u5171\u5172\u5173\u5174\u9fb9\u5175\u34b5\u34b6\u34b7\u5176\u5177\u5178\u5179\u517b\u34b8\u517a\u517c\u517d\u517e\u517f\u5180\u5181\u34b9\u5182\u5183\u5184\u5185\u5186\u5187\u5188\u5189\u518a\u518b\u518c\u518d\u518e\u518f\u5190\u34ba\u5191\u5192\u5193\u5194\u5195\u34bb\u34bc\u6700\u34bd\u34be\u34bf\u5196\u5197\u5198\u34c0\u34c1\u5199\u519a\u519b\u519c\u519d\u519e\u519f\u51a0\u34c2\u51a1\u51a2\u51a3\u51a4\u51a5\u51a6\u51a7\u51a8\u34c3\u51a9\u34c4\u51aa\u51ab\u34c5\u51ac\u51ad\u51ae\u51af\u51b0\u51b1\u51b2\u51b3\u51b4\u34c6\u34c7\u51b5\u51b6\u51b7\u51b8\u51b9\u51ba\u51bb\u9fd1\u34c8\u51bc\u51bd\u51be\u51bf\u51c0\u34c9\u34ca\u34cb\u34cc\u34cd\u51c1\u51c2\u51c3\u34ce\u34cf\u51c4\u51c5\u51c6\u51c7\u51c8\u51c9\u51ca\u51cb\u51cc\u51cd\u51ce\u34d0\u34d1\u34d2\u51cf\u51d0\u51d1\u34d3\u51d2\u51d3\u51d4\u51d5\u51d6\u34d4\u34d5\u51d7\u34d6\u51d8\u34d7\u51d9\u51da\u51db\u51dc\u51dd\u51de\u51df\u9fdb\u51e0\u51e1\u51e2\u51e3\u51e4\u51e5\u51e6\u51e7\u51e8\u51e9\u51ea\u51eb\u51ec\u51ed\u51ee\u51ef\u51f0\u34d8\u51f1\u51f2\u51f3\u51f4\u51f5\u51f6\u51f7\u51f8\u51f9\u51fa\u51fb\u51fc\u34d9\u51fd\u51fe\u51ff\u5200\u5201\u5202\u5203\u5204\u5205\u5206\u5207\u5208\u5209\u520a\u520b\u520c\u520d\u34da\u34db\u34dc\u520e\u520f\u5210\u5211\u5212\u5213\u5214\u5215\u5216\u5217\u5218\u5219\u521a\u521b\u34dd\u34de\u521c\u521d\u521e\u521f\u5220\u5221\u5222\u5223\u5224\u5225\u5226\u5227\u5228\u5229\u522a\u522b\u522c\u522d\u34df\u34e0\u522e\u522f\u5230\u5231\u5232\u5233\u5234\u5235\u5236\u5237\u5238\u5239\u523a\u523b\u523c\u523d\u523e\u523f\u5240\u5241\u5242\u34e1\u34e2\u34e3\u34e4\u34e5\u34e6\u5243\u5244\u5245\u5246\u5247\u5248\u5249\u524a\u524b\u524c\u524d\u524e\u524f\u5250\u5251\u34e7\u34e8\u34e9\u34ea\u34eb\u5252\u5253\u5254\u5255\u5256\u5257\u5258\u5259\u525a\u525b\u525c\u525d\u525e\u525f\u5260\u5261\u5262\u5263\u5264\u5265\u5266\u5267\u34ec\u34ed\u34ee\u34ef\u34f0\u5268\u526a\u526b\u526c\u526d\u526e\u526f\u5270\u5271\u5276\u34f1\u34f2\u34f3\u34f4\u34f5\u34f6\u5269\u5272\u5273\u5274\u5275\u34f7\u34f8\u34f9\u34fa\u34fb\u5277\u5278\u5279\u527a\u527b\u527c\u527d\u527e\u527f\u34fc\u34fd\u5280\u5281\u5282\u5283\u5284\u34fe\u34ff\u3500\u3501\u3502\u3503\u3504\u3505\u3506\u3507\u3508\u3509\u5285\u5286\u5287\u5288\u5289\u528a\u528b\u528c\u528d\u528e\u528f\u350a\u5290\u5291\u5292\u5293\u5294\u350b\u350c\u350d\u5295\u350e\u350f\u3510\u5296\u3511\u3512\u5297\u5298\u5299\u529a\u529b\u529c\u529d\u529e\u529f\u52a0\u52a1\u52a2\u3513\u361e\u52a3\u52a4\u52a5\u52a6\u52a7\u52a8\u3514\u3515\u3516\u52a9\u52aa\u52ab\u52ac\u52ad\u52ae\u52af\u52b0\u52b1\u52b2\u52b3\u52b4\u3517\u3518\u52b5\u52b6\u52b7\u52b8\u52b9\u52ba\u52bb\u52bc\u52bd\u52be\u52bf\u3519\u351a\u351b\u52c0\u52c1\u52c2\u52c3\u52c4\u52c5\u52c6\u52c7\u52c8\u52c9\u52ca\u52cb\u351c\u52cc\u52cd\u52ce\u52cf\u52d0\u52d1\u351d\u351e\u351f\u52d2\u52d3\u52d4\u52d5\u52d6\u52d8\u52d9\u52da\u3520\u52db\u52dc\u52dd\u52de\u3521\u52df\u52e0\u52e1\u52e2\u52e3\u52e4\u52e5\u52e6\u52e7\u52e8\u52e9\u52ea\u52eb\u52ec\u52ed\u3522\u52ee\u52ef\u52f0\u52f1\u52f2\u52f3\u52f4\u52f5\u52f6\u3523\u3524\u3525\u3526\u3527\u52f7\u52f8\u52f9\u52fa\u52fb\u52fc\u52fd\u52fe\u52ff\u5300\u5301\u5302\u5303\u5304\u5305\u5306\u5307\u5308\u5309\u530a\u530b\u530c\u530d\u530e\u530f\u5310\u3528\u5311\u5312\u3529\u5313\u352a\u5314\u5315\u5316\u352b\u5317\u352c\u5318\u5319\u352d\u531a\u531b\u531c\u531d\u531e\u531f\u5320\u5321\u5322\u5323\u5324\u5325\u352f\u3530\u5326\u5327\u5328\u5329\u532a\u532b\u9fa8\u532c\u532d\u532e\u3531\u532f\u3532\u3533\u3534\u5330\u5331\u5332\u3535\u5333\u5334\u5335\u5336\u5337\u3536\u5338\u5339\u533a\u533b\u3537\u533c\u533d\u533e\u533f\u5340\u3538\u5341\u5342\u5343\u5345\u5346\u5347\u5348\u3539\u5349\u534a\u534c\u534b\u534d\u534e\u534f\u5350\u353a\u353b\u5351\u5352\u5353\u5354\u5355\u5356\u9fba\u5357\u5358\u5359\u535a\u353c\u535b\u535c\u535d\u535e\u535f\u5360\u5361\u5362\u5363\u5364\u5365\u5366\u5367\u353d\u5368\u5369\u536a\u536b\u353e\u353f\u536c\u536d\u536e\u536f\u5370\u5371\u5372\u5373\u5374\u5375\u3540\u5376\u5377\u5378\u5379\u537a\u3541\u537b\u537c\u537d\u537e\u537f\u5380\u5381\u5382\u5383\u5384\u5385\u5386\u5387\u5388\u5389\u538a\u538b\u538c\u538d\u3542\u3543\u3544\u538e\u538f\u5390\u5391\u3545\u3546\u3547\u5392\u5393\u5394\u5395\u3548\u3549\u5396\u5397\u5398\u5399\u539a\u539b\u354a\u539c\u539d\u539e\u539f\u8652\u354b\u354c\u354d\u53a0\u53a1\u53a2\u53a3\u53a9\u53a4\u53a5\u53a6\u53a7\u53a8\u354e\u53aa\u53ab\u354f\u53ac\u53ad\u53ae\u53af\u53b0\u3550\u3551\u53b1\u53b2\u3552\u53b3\u3553\u53b4\u3554\u53b5\u53b6\u53b7\u53b8\u53b9\u3555\u53ba\u53bb\u53bc\u53bd\u53be\u53bf\u53c0\u53c1\u53c2\u3556\u53c3\u53c4\u3557\u3558\u53c5\u53c6\u53c7\u3559\u53c8\u53c9\u53ca\u53cb\u53cc\u53cd\u53ce\u355a\u355b\u53cf\u53d0\u53d1\u53d2\u355c\u53d3\u355d\u53d4\u53d5\u53d6\u53d7\u53d8\u355e\u53d9\u53da\u53db\u53dc\u53dd\u355f\u53de\u53df\u3560\u53e0\ufa0e\u3561\u53e1\u53e2\u58e1\u53e3\u53e4\u53e5\u53e6\u53e7\u53e8\u53e9\u53ea\u53eb\u53ec\u53ed\u53ee\u53ef\u53f0\u53f1\u53f2\u53f3\u53f4\u53f5\u53f6\u53f7\u53f8\u53f9\u53fa\u53fb\u53fc\u53fd\u53fe\u3563\u3564\u3565\u4db7\u53ff\u5400\u5401\u5402\u5403\u5404\u5405\u5406\u5407\u5408\u5409\u540a\u540b\u540c\u540d\u540e\u540f\u5410\u5411\u5412\u5413\u5414\u5415\u5416\u5417\u3566\u3567\u3568\u4db8\u5418\u5419\u541a\u541b\u541c\u541d\u541e\u541f\u5420\u5421\u5422\u5423\u5424\u5425\u5426\u5427\u5428\u5429\u542a\u542b\u542c\u542d\u542e\u542f\u5430\u5431\u5432\u5433\u5434\u5435\u5436\u5437\u5438\u5439\u543a\u543b\u543c\u543d\u543e\u543f\u5440\u5441\u5442\u5443\u5444\u5445\u5446\u5447\u5448\u5449\u544a\u544b\u544c\u544d\u544e\u544f\u5450\u5451\u5452\u5453\u5454\u5455\u5456\u5457\u5458\u5459\u545a\u545b\u545c\u3569\u356a\u356b\u356c\u356d\u356e\u356f\u3570\u3571\u3572\u3573\u3574\u3575\u3576\u545d\u545e\u545f\u5460\u5461\u5462\u5463\u5464\u5465\u5466\u5467\u5468\u5469\u546a\u546b\u546c\u546d\u546e\u546f\u5470\u5471\u5472\u5473\u5474\u5475\u5476\u5477\u5478\u5479\u547a\u547b\u547c\u547d\u547e\u547f\u5480\u5481\u5482\u5483\u5484\u5485\u5486\u5487\u5488\u5489\u548a\u548b\u548c\u548d\u548e\u548f\u5490\u5491\u5492\u5493\u5494\u5495\u5496\u5497\u5498\u5499\u549a\u549b\u549c\u549d\u3577\u3578\u3579\u357a\u357b\u357c\u357d\u357e\u357f\u549e\u549f\u54a0\u54a1\u54a2\u54a3\u54a4\u54a5\u54a6\u54a7\u54a8\u54a9\u54aa\u54ab\u54ac\u54ad\u54ae\u54af\u54b0\u54b1\u54b2\u54b3\u54b4\u54b5\u54b6\u54b7\u54b8\u54b9\u54ba\u54bb\u54bc\u54bd\u54be\u54bf\u54c0\u54c1\u54c2\u54c3\u54c4\u54c5\u54c6\u54c7\u54c8\u54c9\u54ca\u54cb\u54cc\u54cd\u54ce\u54cf\u54d0\u54d1\u54d2\u54d3\u54d4\u54d5\u54d6\u54d7\u54d8\u54d9\u54da\u54db\u54dc\u54dd\u54de\u54df\u3580\u3581\u3582\u3583\u3584\u3585\u3586\u3587\u3588\u3589\u358a\u358b\u358c\u358d\u358e\u54e0\u54e1\u54e2\u54e3\u54e4\u54e5\u54e6\u54e7\u54e8\u54e9\u54ea\u54eb\u54ec\u54ed\u54ee\u54ef\u54f0\u54f1\u54f2\u54f3\u54f4\u54f5\u54f6\u54f7\u54f8\u54f9\u54fa\u54fb\u54fc\u54fd\u54fe\u54ff\u5500\u5501\u5502\u5503\u5504\u5505\u5506\u5507\u5508\u5509\u550a\u550b\u550c\u550d\u550e\u550f\u5510\u5511\u5512\u5513\u5514\u5515\u5516\u5517\u5518\u5519\u551a\u551b\u551c\u551d\u551e\u551f\u5520\u5521\u5522\u5523\u5524\u5525\u5526\u5527\u358f\u3590\u3591\u3592\u3593\u3594\u3595\u3596\u3597\u3598\u3599\u359a\u359b\u359c\u359d\u359e\u5528\u5529\u552a\u552b\u552c\u552d\u552e\u552f\u5530\u5531\u5532\u5533\u5534\u5535\u5536\u5537\u5538\u5539\u553a\u553b\u553c\u553d\u553e\u553f\u5540\u5541\u5542\u5543\u5544\u5545\u5546\u5547\u5548\u5549\u554a\u554b\u554c\u554d\u554e\u554f\u5550\u5551\u5552\u5553\u5554\u5555\u5556\u5557\u5558\u555a\u555b\u555c\u555d\u555e\u5560\u5561\u5562\u5563\u5564\u5565\u5566\u5567\u5568\u5569\u556a\u556c\u556d\u556e\u556f\u5570\u5571\u5572\u5573\u5574\u5575\u5576\u5577\u5578\u5579\u359f\u35a0\u35a1\u35a2\u35a3\u35a4\u35a5\u35a6\u35a7\u35a8\u35a9\u35aa\u35ab\u35ac\u35ad\u35ae\u35af\u35b0\u35b1\u35b2\u35b3\u35b4\u35b5\u5559\u556b\u557a\u557b\u557c\u557d\u557e\u557f\u5580\u5581\u5582\u5583\u5584\u5585\u5586\u5587\u5588\u5589\u558a\u558b\u558c\u558e\u558f\u5590\u5591\u5592\u5593\u5594\u5595\u5596\u5597\u5598\u5599\u559a\u559b\u559c\u559d\u559e\u559f\u55a0\u55a1\u55a2\u55a3\u55a4\u55a5\u55a6\u55a7\u55a8\u55a9\u55aa\u55ab\u55ac\u55ad\u55ae\u55af\u55b0\u55b1\u55b2\u55b3\u55b4\u55b5\u55b6\u55b7\u55b8\u55b9\u55ba\u55bb\u55bc\u55bd\u55be\u5645\u35b6\u35b7\u35b8\u35b9\u35ba\u35bb\u35bc\u35bd\u35be\u35bf\u35c0\u35c1\u35c2\u35c3\u35c4\u35c5\u35c6\u35c7\u35c8\u35c9\u35ca\u35cb\u35cc\u35cd\u35ce\u35cf\u35d0\u35d1\u558d\u55bf\u55c0\u55c1\u55c2\u55c3\u55c4\u55c5\u55c6\u55c7\u55c8\u55c9\u55ca\u55cb\u55cc\u55cd\u55ce\u55cf\u55d0\u55d1\u55d2\u55d3\u55d4\u55d5\u55d6\u55d7\u55d8\u55d9\u55da\u55db\u55dc\u55dd\u55de\u55df\u55e0\u55e1\u55e2\u55e3\u55e4\u55e5\u55e6\u55e7\u55e8\u55e9\u55ea\u55eb\u55ec\u55ed\u55ee\u55ef\u55f0\u55f1\u55f2\u55f3\u55f4\u55f5\u35d2\u35d3\u35d4\u35d5\u35d6\u35d7\u35d8\u35d9\u35da\u35db\u35dc\u35dd\u35de\u35df\u35e0\u35e1\u55f6\u55f7\u55f8\u55f9\u55fa\u55fb\u55fc\u55fd\u55fe\u55ff\u5600\u5601\u5602\u5603\u5604\u5605\u5606\u5607\u5608\u5609\u560a\u560b\u560c\u560d\u560e\u560f\u5610\u5611\u5612\u5613\u5614\u5615\u5616\u5617\u5618\u5619\u561a\u561b\u561c\u561d\u561e\u5621\u5622\u5623\u5624\u5625\u5626\u5627\u5651\u5653\u35e2\u35e3\u35e4\u35e5\u35e6\u35e7\u35e8\u35e9\u35ea\u35eb\u35ec\u35ed\u35ee\u35ef\u35f0\u561f\u5620\u5628\u5629\u562a\u562b\u562c\u562d\u562e\u562f\u5630\u5631\u5632\u5633\u5634\u5635\u5636\u5637\u5638\u5639\u563a\u563b\u563c\u563d\u563e\u563f\u5640\u5641\u5642\u5643\u5644\u5646\u5647\u5648\u5649\u564a\u564b\u564c\u564d\u564e\u564f\u5650\u5652\u5654\u5656\u5657\u5658\u5659\u565a\u565b\u565c\u565d\u5674\u35f1\u35f2\u35f3\u35f4\u35f5\u35f6\u35f7\u35f8\u35f9\u35fa\u565e\u565f\u5660\u5661\u5662\u5663\u5664\u5665\u5666\u5667\u5668\u5669\u566a\u566b\u566c\u566d\u566e\u566f\u5670\u5671\u5672\u5673\u5675\u5676\u5677\u5678\u5679\u567a\u567b\u567c\u35fb\u35fc\u35fd\u35fe\u35ff\u3600\u3601\u3602\u3603\u3604\u567d\u567e\u567f\u5680\u5681\u5682\u5683\u5684\u5685\u5686\u5687\u5688\u5689\u568a\u568b\u568c\u568d\u568e\u568f\u5690\u5691\u5692\u5693\u56ba\u3605\u3606\u3607\u3608\u5694\u5695\u5696\u5697\u5698\u5699\u569a\u569b\u569c\u569d\u569e\u569f\u56a0\u56a1\u56a2\u56a3\u56a4\u3609\u360a\u360b\u360c\u360d\u360e\u360f\u56a5\u56a6\u56a7\u56a8\u56a9\u56aa\u56ab\u56ac\u56ad\u56ae\u56af\u56b0\u3610\u3611\u3612\u56b1\u56b2\u56b3\u56b4\u56b5\u56b6\u56b7\u56b8\u56b9\u3613\u3614\u56bb\u56bc\u56bd\u56be\u56bf\u56c0\u56c1\u56c2\u56c3\u56c4\u56cd\u3615\u3616\u3617\u56c5\u56c6\u56c7\u56c8\u56c9\u56ca\u56cb\u56ce\u3618\u56cc\u56cf\u56d0\u3619\u361a\u56d1\u56d2\u56d3\u56d4\u56d5\u361b\u361c\u56d6\u56d7\u56d8\u56d9\u56da\u56db\u56dc\u9fb1\u361d\u56dd\u56de\u56df\u56e0\u56e1\u56e2\u56e3\u361f\u56e4\u56e5\u56e6\u56e7\u56e8\u56e9\u56ea\u56eb\u56ec\u56ed\u56ee\u56ef\u56f0\u56f1\u56f2\u56f3\u56f4\u56f5\u56f6\u56f7\u56f8\u56f9\u56fa\u56fb\u56fc\u56fd\u56fe\u3620\u3621\u56ff\u5700\u3622\u5701\u5702\u5703\u5704\u5705\u5706\u3623\u5707\u5708\u5709\u570a\u570b\u570f\u570c\u570d\u570e\u5710\u5711\u5712\u5713\u5714\u5715\u5716\u5717\u5718\u5719\u3624\u571a\u571b\u571c\u3625\u571d\u571e\u571f\u5720\u5721\u9fb6\u5722\u5723\u5724\u5725\u5726\u5727\u3626\u5728\u5729\u572a\u572b\u572c\u572d\u572e\u572f\u5730\u5731\u5732\u5733\u5734\u5735\u5736\u5737\u5738\u5739\u573a\u573b\u573c\u573d\u573e\u573f\u5740\u5741\u5742\u5743\u5744\u5745\u5746\u5747\u5748\u5749\u574a\u574b\u574c\u574d\u574e\u574f\u5750\u5751\u5752\u5753\u5754\u5755\u5756\u5757\u5758\u5759\u575a\u575b\u575c\u575d\u575e\u575f\u5760\u3627\u3628\u3629\u362a\u362b\u362c\u362d\u362e\u362f\u3630\u5761\u5762\u5763\u5764\u5765\u5766\u5767\u5768\u5769\u576a\u576b\u576c\u576d\u576e\u576f\u5770\u5771\u5772\u5773\u5774\u5775\u5776\u5777\u5778\u5779\u577a\u577b\u577c\u577d\u577e\u577f\u5780\u5781\u5782\u5783\u5784\u5785\u5786\u5787\u5788\u5789\u578a\u3631\u3632\u3633\u3634\u3635\u578b\u578c\u578d\u578e\u578f\u5790\u5791\u5792\u5793\u5794\u5795\u5796\u5797\u5798\u5799\u579a\u579b\u579c\u579d\u579e\u579f\u57a0\u57a1\u57a2\u57a3\u57a4\u57a5\u57a6\u57a7\u57a8\u57a9\u57aa\u57ab\u57ac\u57ad\u57ae\u57af\u57b0\u57b1\u57b2\u57b3\u57b4\u57b5\u57ce\u9fcd\u3636\u3637\u3638\u3639\u363a\u363b\u363c\u363d\u363e\u57b6\u57b7\u57b8\u57b9\u57ba\u57bb\u57bc\u57bd\u57be\u57bf\u57c0\u57c1\u57c2\u57c3\u57c4\u57c5\u57c6\u57c7\u57c8\u57c9\u57ca\u57cb\u57cc\u57cd\u57cf\u57d0\u57d1\u57d2\u57d3\u57d4\u57d5\u57d6\u57d7\u57d8\u57d9\u57da\u57db\ufa0f\u363f\u3640\u3641\u3642\u3643\u3644\u3645\u3646\u57dc\u57dd\u57de\u57df\u57e0\u57e1\u57e2\u57e3\u57e4\u57e5\u57e6\u57e7\u57e8\u57e9\u57eb\u57ec\u57ed\u57ee\u57ef\u57f0\u57f1\u57f2\u57f3\u57f4\u57f5\u57f6\u57f7\u57f8\u57f9\u57fa\u57fb\u57fc\u57fd\u57fe\u57ff\u5800\u5801\u5802\u5803\u5804\u5805\u5806\u5807\u5808\u5809\u580a\u580b\u580c\u580d\u580e\u580f\u5810\u5811\u5812\u5813\u5814\u5815\u3647\u3648\u3649\u364a\u364b\u364c\u364d\u57ea\u5816\u5817\u5818\u5819\u581a\u581b\u581c\u581d\u581e\u581f\u5820\u5821\u5822\u5823\u5824\u5825\u5826\u5827\u5828\u5829\u582a\u582b\u582c\u582d\u582e\u582f\u5830\u5831\u5832\u5833\u5834\u5835\u5836\u5837\u5838\u5839\u583a\u583b\u583c\u583e\u583f\u5840\u5841\u5842\u5844\u5845\u5846\u5847\u5848\u364e\u364f\u3650\u3651\u3652\u3653\u3654\u3655\u3656\u3657\u3658\u583d\u5843\u5849\u584a\u584b\u584c\u584d\u584e\u584f\u5850\u5851\u5852\u5853\u5854\u5855\u5856\u5857\u5858\u5859\u585a\u585b\u585c\u585d\u585e\u585f\u5860\u5861\u5862\u5863\u5864\u5865\u5866\u5867\u5868\u5869\u586a\u586b\u586c\u586d\u586e\u586f\u5870\u5871\u3659\u365a\u365b\u365c\u365d\u365e\u365f\u5872\u5873\u5874\u5875\u5876\u5877\u5878\u5879\u587a\u587b\u587c\u587d\u587e\u587f\u5880\u5881\u5882\u5883\u5884\u5885\u5886\u5887\u5888\u5889\u588a\u588b\u588c\u588d\u588e\u588f\u5890\u5891\u5892\u5893\u5894\u5895\u5896\u5897\u5898\u5899\u589a\u589b\u3660\u3661\u3662\u3663\u3664\u3665\u3666\u589c\u589d\u589e\u589f\u58a0\u58a1\u58a2\u58a3\u58a4\u58a5\u58a6\u58a7\u58a9\u58aa\u58ab\u58ac\u58ad\u58ae\u58af\u58b0\u58b1\u58b2\u58b3\u58b4\u58b5\u58b6\u58b7\u58b8\u58b9\u3667\u3668\u3669\u366a\u366b\u366c\u366d\u366e\u366f\u58ba\u58bb\u58bc\u58bd\u58be\u58bf\u58c0\u58c1\u58c2\u58c3\u58c4\u58c5\u58c6\u58c7\u58c8\u58c9\u58ca\u58cb\u58cc\u9fb3\u3670\u3671\u3672\u3673\u3674\u3675\u3676\u58cd\u58ce\u58cf\u58d0\u58d1\u58d2\u58d3\u58d4\u58d5\u58d6\u58d7\u9fbc\u3677\u3678\u3679\u367a\u58d8\u58d9\u367b\u367c\u367d\u58da\u58db\u58dc\u58dd\u58de\u58df\u58e0\u58e2\u367e\u367f\u58e3\u58e4\u58e5\u3680\u58e6\u58e7\u58e8\u58e9\u3681\u58ea\u3682\u58eb\u58ec\u58ed\u58ee\u58ef\u58f0\u58f1\u58f2\u58f3\u58f4\u58f5\u58f6\u58f7\u58f8\u58f9\u58fa\u58fb\u3683\u58fc\u58fd\u58fe\u58ff\u5900\u3684\u5901\u5902\u5903\u5904\u5905\u5906\u5907\u5908\u3685\u590a\u590b\u590c\u5909\u590d\u3686\u3687\u590e\u590f\u5910\u5911\u5913\u5912\u5914\u5915\u5916\u5917\u5918\u3688\u5919\u591a\u591b\u591c\u591d\u3689\u591e\u591f\u5920\u5921\u368a\u5922\u5923\u5924\u5925\u368b\u368c\u5926\u368d\u5927\u5928\u5929\u592a\u592b\u592c\u592d\u592e\u592f\u5930\u5931\u5932\u5933\u5934\u368e\u5935\u5936\u5937\u5938\u5939\u593a\u593b\u593c\u368f\u3690\u3691\u593d\u593e\u593f\u5940\u5941\u5942\u3692\u3693\u5943\u5944\u5945\u5946\u5947\u5948\u5949\u594b\u594c\u594d\u3694\u3695\u3696\u3697\u3698\u3699\u594e\u594f\u5950\u5951\u5952\u5953\u5954\u5955\u5956\u369a\u369b\u594a\u5957\u5958\u5959\u595a\u595b\u595c\u595d\u595e\u369c\u369d\u369e\u595f\u5960\u5961\u5962\u5963\u5964\u5965\u369f\u5966\u5967\u5968\u36a0\u5969\u596a\u596b\u596c\u596d\u596e\u596f\u36a1\u5970\u5971\u5972\u5973\u5974\u5975\u5976\u36a2\u5977\u5978\u5979\u597a\u597b\u597c\u597d\u597e\u597f\u5980\u5981\u5982\u5983\u5984\u5985\u5986\u5987\u5988\u36a3\u36a4\u36a5\u36a6\u36a7\u36a8\u5989\u598a\u598b\u598c\u598e\u598f\u5990\u5991\u5992\u5993\u5994\u5995\u5996\u5997\u5998\u5999\u599a\u599b\u599c\u599d\u599e\u599f\u59a0\u59a1\u59a2\u59a3\u59a4\u59a5\u59a6\u59a7\u59a8\u59a9\u59aa\u59ab\u36a9\u36aa\u36ab\u36ac\u36ad\u36ae\u36af\u36e0\u59ac\u59ad\u59ae\u59af\u59b0\u59b1\u59b2\u59b3\u59b4\u59b5\u59b6\u59b7\u59b8\u59b9\u59ba\u59bb\u59bc\u59bd\u59be\u59bf\u59c0\u59c1\u59c2\u59c3\u59c4\u59c5\u59c6\u59c7\u59c8\u59c9\u59ca\u59cb\u59cc\u59cd\u59ce\u59cf\u59d0\u59d1\u59d2\u59d3\u59d4\u59d5\u59d6\u59d7\u36b0\u36b1\u36b2\u36b3\u36b4\u36b5\u36b6\u36b7\u36b8\u36b9\u36ba\u36bb\u36bc\u36bd\u36be\u36bf\u36c0\u36c1\u598d\u59d8\u59d9\u59da\u59db\u59dc\u59dd\u59de\u59df\u59e0\u59e1\u59e2\u59e3\u59e4\u59e5\u59e6\u59e7\u59e8\u59e9\u59ea\u59eb\u59ed\u59ee\u59ef\u59f0\u59f1\u59f2\u59f3\u59f4\u59f5\u59f6\u59f7\u59f8\u59f9\u59fa\u59fb\u59fc\u59fd\u59fe\u59ff\u5a00\u5a01\u5a02\u5a03\u5a05\u5a06\u5a07\u5a08\u36c2\u36c3\u36c4\u36c5\u36c6\u36c7\u36c8\u36c9\u36ca\u36cb\u36cc\u59ec\u5a09\u5a0a\u5a0b\u5a0c\u5a0d\u5a0e\u5a0f\u5a10\u5a11\u5a12\u5a13\u5a14\u5a15\u5a16\u5a17\u5a18\u5a19\u5a1a\u5a1b\u5a1c\u5a1d\u5a1e\u5a1f\u5a20\u5a21\u5a22\u5a23\u5a24\u5a25\u5a26\u5a27\u5a28\u5a29\u5a2a\u5a2b\u5a2d\u5a2e\u5a2f\u5a30\u5a31\u5a32\u5a33\u5a34\u5a3d\u36cd\u36ce\u36cf\u36d0\u36d1\u36d2\u36d3\u36d4\u36d5\u36d6\u36d7\u36d8\u36d9\u36da\u36db\u36dc\u36dd\u36de\u36df\u36e1\u36e2\u36e3\u36e4\u5a2c\u5a35\u5a36\u5a37\u5a38\u5a39\u5a3a\u5a3b\u5a3c\u5a3e\u5a3f\u5a40\u5a41\u5a42\u5a43\u5a44\u5a45\u5a46\u5a47\u5a48\u5a49\u5a4a\u5a4b\u5a4c\u5a4d\u5a4e\u5a4f\u5a50\u5a51\u5a52\u5a53\u5a54\u5a55\u5a56\u5a57\u5a58\u5a59\u5a5a\u5a5b\u5a5c\u5a5d\u5a5e\u5a5f\u5a60\u5a61\u5a62\u5a63\u5a64\u5a65\u5a66\u5a67\u5a68\u5a69\u5a6a\u5a6b\u5a6c\u5a6d\u5a6e\u5a6f\u5a70\u5a71\u5a72\u5a73\u5a74\u5a75\u5a76\u36e5\u36e6\u36e7\u36e8\u36e9\u36ea\u36eb\u36ec\u36ed\u5a77\u5a78\u5a79\u5a7a\u5a7b\u5a7c\u5a7d\u5a7e\u5a7f\u5a80\u5a81\u5a82\u5a83\u5a84\u5a85\u5a86\u5a87\u5a88\u5a89\u5a8a\u5a8b\u5a8c\u5a8d\u5a8e\u5a8f\u5a91\u5a92\u5a93\u5a94\u5a95\u5a96\u5a97\u5a98\u5a99\u5a9a\u5a9b\u5a9c\u5a9d\u5a9e\u5a9f\u5aa0\u5aa1\u5aa2\u5aa3\u5aa4\u5aa5\u5aa6\u5aa7\u5aa8\u5aa9\u5aaa\u5aab\u5aac\u5aad\u5aae\u5aaf\u5acf\u36ee\u36ef\u36f0\u36f1\u36f2\u36f3\u36f4\u36f5\u36f6\u36f7\u36f8\u36f9\u36fa\u36fb\u36fc\u36fd\u36fe\u36ff\u3700\u3701\u3702\u3703\u3704\u5a90\u5ab0\u5ab1\u5ab2\u5ab3\u5ab4\u5ab5\u5ab6\u5ab7\u5ab8\u5ab9\u5aba\u5abb\u5abc\u5abd\u5abe\u5abf\u5ac0\u5ac1\u5ac2\u5ac3\u5ac4\u5ac5\u5ac6\u5ac7\u5ac8\u5ac9\u5aca\u5acb\u5acc\u5acd\u5ace\u5ad0\u5ad1\u5ad2\u5ad3\u5ad4\u3705\u3706\u3707\u3708\u3709\u370a\u370b\u370c\u370d\u370e\u370f\u3710\u3711\u3712\u3713\u3714\u5ad5\u5ad6\u5ad7\u5ad8\u5ad9\u5ada\u5adb\u5adc\u5add\u5ade\u5adf\u5ae0\u5ae1\u5ae2\u5ae3\u5ae4\u5ae5\u5ae6\u5ae7\u5ae8\u5ae9\u5aea\u5aeb\u5aec\u5aed\u5aee\u5aef\u5af0\u5af1\u5af2\u3715\u3716\u3717\u3718\u3719\u371a\u371b\u371c\u371d\u371e\u371f\u3720\u3721\u3722\u5af3\u5af4\u5af5\u5af6\u5af7\u5af8\u5af9\u5afa\u5afb\u5afc\u5afd\u5afe\u5aff\u5b00\u5b01\u5b02\u5b03\u5b04\u5b05\u5b06\u5b07\u5b08\u5b09\u5b0a\u5b0b\u5b0c\u5b0d\u5b0e\u5b0f\u3723\u3724\u3725\u3726\u3727\u3728\u5b10\u5b11\u5b12\u5b13\u5b14\u5b15\u5b16\u5b17\u5b18\u5b19\u5b1a\u5b1b\u5b1c\u5b1d\u5b1e\u5b1f\u5b20\u5b21\u5b22\u5b34\u3729\u372a\u372b\u372c\u372d\u5b23\u5b24\u5b25\u5b26\u5b27\u5b28\u5b29\u5b2a\u5b2b\u5b2c\u5b2d\u5b2e\u5b2f\u5b30\u5b31\u5b32\u5b33\u5b35\u5b36\u5b37\u372e\u372f\u5b38\u5b3a\u5b3b\u5b3c\u5b3d\u3730\u3731\u5b39\u5b3e\u5b3f\u3732\u3733\u3734\u3735\u5b40\u5b41\u5b42\u5b43\u5b44\u5b45\u5b46\u3736\u3737\u3738\u5b47\u5b48\u5b49\u3739\u5b4a\u5b4b\u5b4c\u373a\u5b4d\u5b4e\u5b4f\u373b\u373c\u5b50\u5b51\u5b52\u5b53\u5b54\u5b55\u5b56\u5b57\u5b58\u5b59\u373d\u5b5a\u5b5b\u5b5c\u5b5d\u5b5e\u373e\u373f\u5b5f\u5b60\u5b61\u5b62\u5b63\u5b64\u5b65\u5b66\u5b67\u3740\u5b68\u5b69\u5b6a\u3741\u5b6b\u5b6c\u5b6d\u3742\u3743\u5b6e\u5b6f\u5b70\u5b72\u5b71\u3744\u5b73\u5b74\u3745\u5b75\u5b76\u5b77\u3746\u5b78\u5b79\u3747\u5b7a\u5b7b\u5b7c\u5b7d\u5b7e\u5b7f\u3748\u5b80\u3749\u5b81\u5b82\u5b83\u5b84\u374a\u374b\u5b85\u5b86\u5b87\u5b88\u5b89\u374c\u374d\u5b8a\u5b8b\u5b8c\u5b8d\u5b8e\u5b8f\u5b90\u5b91\u5b92\u374e\u374f\u3750\u3751\u5b93\u5b94\u5b95\u5b96\u5b97\u5b98\u5b99\u5b9a\u5b9b\u5b9c\u5b9d\u5b9e\u5b9f\u5ba0\u5ba1\u3752\u5ba2\u5ba3\u5ba4\u5ba5\u5ba6\u5ba8\u5ba9\u5baa\u5bab\u3753\u3754\u3755\u3756\u5ba7\u5bac\u5bad\u5bae\u5baf\u5bb0\u5bb1\u5bb2\u5bb3\u5bb4\u5bb5\u5bb6\u5bb7\u5bb8\u5bb9\u5bba\u5bbb\u5bbc\u5bbd\u5bbe\u3757\u3758\u3759\u375a\u5bbf\u5bc0\u5bc1\u5bc2\u5bc3\u5bc4\u5bc5\u5bc6\u5bc7\u5bc8\u5bc9\u375b\u375c\u375d\u375e\u375f\u3760\u3761\u5bca\u5bcb\u5bcc\u5bcd\u5bce\u5bcf\u5bd0\u5bd1\u5bd2\u5bd3\u5bd4\u5bd5\u5bea\u3762\u3763\u5bd6\u5bd7\u5bd8\u5bd9\u5bda\u5bdb\u5bdc\u5bdd\u3764\u3765\u3766\u3767\u3768\u5bde\u5bdf\u5be0\u5be1\u5be2\u5be3\u5be4\u5be5\u5be6\u5be7\u5be8\u3769\u376a\u376b\u376c\u5be9\u5beb\u5bec\u5bed\u5bee\u376d\u376e\u376f\u5bef\u5bf0\u5bf1\u5bf2\u3770\u5bf3\u5bf4\u5bf5\u5bf6\u5bf7\u3771\u3772\u5bf8\u5bf9\u3773\u5bfa\u5bfb\u5bfc\u5bfd\u5bfe\u5bff\u3774\u5c00\u3775\u5c01\u5c02\u5c06\u5c03\u5c04\u5c05\u3776\u5c07\u5c08\u5c09\u5c0a\u5c0b\u5c0c\u3777\u5c0d\u5c0e\u5c0f\u5c10\u5c11\u5c12\u5c13\u5c14\u5c15\u9fb8\u5c16\u5c17\u5c18\u5c19\u5c1a\u5c1b\u5c1c\u5c1d\u3778\u5c1e\u3779\u5c1f\u5c20\u5c21\u377a\u377b\u5c22\u5c23\u5c24\u5c25\u5c26\u5c27\u377c\u5c28\u5c29\u5c2a\u5c2b\u5c2c\u377d\u5c2d\u377e\u377f\u5c2e\u5c2f\u3780\u3781\u3782\u3783\u3784\u3785\u3786\u5c30\u5c31\u3787\u3788\u5c32\u5c33\u5c34\u3789\u378a\u5c35\u5c36\u5c37\u5c38\u5c3a\u5c3b\u5c3c\u378b\u5c3d\u378c\u378d\u5c3e\u5c3f\u5c40\u5c41\u5c42\u5c43\u378e\u5c44\u5c45\u5c46\u5c47\u5c48\u5c49\u5c4a\u378f\u3790\u3791\u5c4b\u5c4c\u5c4d\u5c4e\u5c4f\u3792\u3793\u3794\u3795\u3796\u5c50\u5c51\u5c52\u5c53\u5c54\u5c55\u5c56\u5c57\u5c58\u3797\u5c59\u5c5a\u5c5b\u5c5c\u5c5d\u3798\u3799\u5c5e\u5c5f\u5c60\u5c61\u379a\u379b\u5c62\u5c63\u379c\u379d\u379e\u5c64\u5c65\u5c66\u5c67\u379f\u37a0\u5c68\u5c69\u5c6a\u5c6b\u37a1\u5c6c\u5c6d\u5c6e\u4db9\u5c6f\u37a2\u5c70\u37a3\u37b7\u5c71\u4e62\u5c72\u5c73\u5c74\u5c75\u5c76\u5c77\u37a4\u37a5\u37a6\u37a7\u5c78\u5c79\u5c7a\u5c7b\u5c7c\u5c7d\u5c7e\u5c7f\u5c80\u5c81\u5c82\u5c83\u37a8\u37a9\u37aa\u37ab\u37ac\u37ad\u37ae\u37af\u5c84\u5c85\u5c86\u5c87\u5c88\u5c89\u5c8a\u5c8b\u5c8c\u5c8e\u5c8f\u5c90\u5c91\u5c92\u5c93\u5c94\u5c95\u5c96\u5c97\u5c98\u5c99\u5c9a\u5c9b\u5c9c\u37b0\u37b1\u37b2\u37b3\u37b4\u37b5\u37b6\u37b8\u5c9d\u5c9e\u5c9f\u5ca0\u5ca1\u5ca2\u5ca3\u5ca4\u5ca5\u5ca6\u5ca7\u5ca8\u5ca9\u5caa\u5cab\u5cac\u5cad\u5cae\u5caf\u5cb0\u5cb1\u5cb2\u5cb3\u5cb4\u5cb5\u5cb6\u5cb7\u5cb8\u5cb9\u5cba\u5cbb\u5cbc\u5cbd\u5cbe\u5cbf\u5cc0\u5cc1\u5cc2\u5cc3\u5cc4\u5cc5\u37b9\u37ba\u37bb\u37bc\u37bd\u37be\u37bf\u37c0\u37c1\u37c2\u37c3\u5c8d\u5cc6\u5cc7\u5cc8\u5cc9\u5cca\u5ccb\u5ccc\u5ccd\u5cce\u5ccf\u5cd0\u5cd1\u5cd2\u5cd3\u5cd4\u5cd5\u5cd6\u5cd7\u5cd8\u5cd9\u5cda\u5cdb\u5cdc\u5cdd\u5cde\u5cdf\u5ce0\u5ce1\u5ce2\u5ce3\u5ce4\u5ce5\u5ce6\u5ce7\u37c4\u37c5\u37c6\u5ce8\u5ce9\u5cea\u5ceb\u5cec\u5ced\u5cee\u5cef\u5cf0\u5cf1\u5cf2\u5cf3\u5cf4\u5cf5\u5cf6\u5cf7\u5cf8\u5cf9\u5cfa\u5cfb\u5cfc\u5cfd\u5cfe\u5cff\u5d00\u5d01\u5d02\u5d03\u5d04\u5d05\u37c7\u37c8\u37c9\u37ca\u37cb\u37cc\u37cd\u37ce\u37cf\u37d0\u37d1\u37d2\u37d3\u37d4\u37d5\u37d6\u5d06\u5d07\u5d08\u5d09\u5d0a\u5d0b\u5d0c\u5d0d\u5d0e\u5d0f\u5d10\u5d11\u5d12\u5d13\u5d14\u5d15\u5d16\u5d17\u5d18\u5d19\u5d1a\u5d1b\u5d1c\u5d1d\u5d1e\u5d1f\u5d20\u5d21\u5d22\u5d23\u5d24\u5d25\u5d26\u5d27\u5d28\u5d29\u5d2a\u5d2b\u5d2c\u5d2d\u5d2e\u5d2f\u5d30\u37d7\u37d8\u37d9\u37da\u37db\u37dc\u37dd\u37de\u37df\u37e0\u37e1\u37e2\u37e3\u37e4\u37e5\u5d31\u5d32\u5d33\u5d34\u5d35\u5d36\u5d37\u5d38\u5d39\u5d3a\u5d3b\u5d3c\u5d3d\u5d3e\u5d3f\u5d40\u5d41\u5d42\u5d43\u5d44\u5d45\u5d46\u5d47\u5d48\u5d49\u5d4b\u5d4c\u5d4d\u5d4e\u5d4f\u5d50\u5d51\u5d52\u5d53\u5d54\u5d55\u5d56\u5d57\u5d58\u5d59\u5d5a\u5d5b\u5d5c\u5d5d\ufa11\u37e6\u37e7\u37e8\u37e9\u37ea\u37eb\u37ec\u37ed\u37ee\u37ef\u5d4a\u5d5e\u5d5f\u5d60\u5d61\u5d62\u5d63\u5d64\u5d65\u5d66\u5d67\u5d68\u5d69\u5d6a\u5d6b\u5d6c\u5d6d\u5d6e\u5d6f\u5d70\u5d71\u5d72\u5d73\u5d74\u5d75\u5d76\u37f0\u37f1\u37f2\u37f3\u37f4\u37f5\u37f6\u37f7\u37f8\u5d77\u5d78\u5d79\u5d7a\u5d7b\u5d7c\u5d7d\u5d7e\u5d7f\u5d80\u5d81\u5d82\u5d83\u5d84\u5d85\u5d86\u5d87\u5d88\u5d89\u5d8a\u5d8b\u5d8c\u5d8d\u5d8e\u37f9\u37fa\u37fb\u37fc\u37fd\u37fe\u37ff\u3800\u3801\u3802\u3803\u3804\u5d8f\u5d90\u5d91\u5d92\u5d93\u5d94\u5d95\u5d96\u5d97\u5d98\u5d99\u5d9a\u5d9b\u5d9c\u5d9d\u5d9e\u5d9f\u5da0\u5da1\u5da2\u5da3\u5da4\u5da5\u3805\u3806\u3807\u3808\u3809\u380a\u380b\u380c\u380d\u380e\u380f\u3810\u5da6\u5da7\u5da8\u5da9\u5daa\u5dab\u5dac\u5dad\u5dae\u5daf\u5db0\u5db1\u5db2\u5db3\u5db4\u5db5\u5db6\u3811\u3812\u3813\u3814\u3815\u3816\u3817\u5db7\u5db8\u5db9\u5dba\u5dbc\u5dbd\u5dbe\u5dbf\u3818\u3819\u381a\u381b\u381c\u5dc0\u5dc1\u5dc2\u381d\u381e\u381f\u5dbb\u5dc3\u5dc4\u5dc5\u3820\u3821\u3822\u3823\u5dc6\u5dc7\u5dc8\u5dc9\u5dca\u5dcb\u5dcc\u3824\u5dcd\u5dcf\u5dd0\u3825\u3826\u5dce\u5dd1\u5dd2\u5dd3\u5dd4\u5dd5\u5dd7\u3827\u5dd6\u5dd8\u5dd9\u5dda\u3828\u5ddb\u5ddc\u5ddd\u5dde\u5ddf\u3829\u5de0\u5de2\u5de3\u5de4\u5de5\u382a\u5de6\u5de7\u5de8\u5de9\u5dea\u5deb\u5dec\u5ded\u5dee\u382b\u382c\u5def\u382d\u5df0\u382e\u5df1\u5df2\u5df3\u5df4\u382f\u5df5\u5df6\u3830\u5df7\u5df8\u5df9\u5dfa\u5dfb\u5dfc\u3831\u5dfd\u5dfe\u5dff\u5e00\u5e01\u5e02\u5e03\u5e04\u5e05\u3832\u3833\u5e06\u5e07\u5e08\u3834\u3835\u3836\u5e09\u5e0a\u5e0b\u5e0c\u5e0d\u5e0e\u5e0f\u5e10\u3837\u3838\u3839\u383a\u383b\u383c\u5e11\u5e12\u5e13\u5e14\u5e15\u5e16\u5e17\u5e18\u5e19\u5e1a\u5e1b\u5e1c\u383d\u383e\u383f\u3840\u5e1d\u5e1e\u5e1f\u5e20\u5e21\u5e22\u5e23\u5e24\u5e25\u5e26\u5e27\u3841\u3842\u3843\u3844\u3845\u3846\u5e28\u5e29\u5e2a\u5e2b\u5e2c\u5e2d\u5e2e\u5e2f\u5e30\u5e31\u3847\u3848\u5e32\u5e33\u5e34\u5e35\u5e36\u5e37\u5e38\u5e39\u5e3a\u5e3b\u5e3c\u3849\u384a\u384b\u384c\u384d\u384e\u5e3d\u5e3e\u5e3f\u5e40\u5e41\u5e42\u5e43\u5e44\u5e45\u5e46\u5e47\u5e49\u384f\u3850\u3851\u3852\u3853\u3854\u3855\u3856\u5e4a\u5e4b\u5e4c\u5e4d\u5e4e\u5e4f\u3857\u3858\u3859\u385a\u385b\u5e48\u5e51\u5e52\u5e53\u5e54\u5e55\u5e56\u5e57\u5e58\u5e59\u5e5b\u385c\u385d\u385e\u385f\u5e5a\u5e5c\u5e5d\u5e5e\u5e5f\u5e60\u5e61\u5e62\u5e63\u5e64\u5e65\u3860\u3861\u5e66\u5e67\u5e68\u5e69\u3862\u3863\u3864\u5e6a\u5e6b\u5e6c\u3865\u3866\u5e6d\u5e6e\u5e6f\u5e70\u5e71\u3867\u3868\u3869\u386a\u5e72\u5e73\u5e74\u5e75\u5e76\u5e77\u5e78\u5e79\u4e61\u5e7a\u5e7b\u5e7c\u5e7d\u5e7e\u386b\u386c\u386d\u386e\u5e7f\u5e80\u5e81\u5e82\u5e83\u5e84\u5e85\u5e86\u386f\u3870\u3871\u5e87\u5e88\u5e89\u5e8a\u5e8b\u5e8c\u5e8d\u5e8e\u5e8f\u5e90\u5e91\u5e92\u5e93\u5e94\u5e98\u3872\u3873\u5e95\u5e96\u5e97\u5e99\u5e9a\u5e9b\u5e9c\u5e9d\u5e9e\u5e9f\u3874\u3875\u3876\u3877\u3878\u3879\u387a\u387b\u5ea0\u5ea1\u5ea2\u5ea3\u5ea4\u5ea5\u5ea6\u387c\u387d\u387e\u387f\u3880\u3881\u3882\u5ea7\u5ea8\u5ea9\u5eaa\u5eab\u5eac\u5ead\u5eae\u5eaf\u3883\u3884\u3885\u3886\u3887\u5eb0\u5eb1\u5eb2\u5eb3\u5eb4\u5eb5\u5eb6\u5eb7\u5eb8\u5eb9\u5eba\u5ebb\u5ebc\u3888\u3889\u388a\u388b\u388c\u5ebd\u5ebe\u5ebf\u5ec0\u5ec1\u5ec2\u5ec3\u5eca\u388d\u388e\u388f\u3890\u5ec5\u5ec6\u5ec7\u5ec8\u5ec9\u5ecb\u5ecc\u9fee\u3891\u5ec4\u5ecd\u5ece\u5ecf\u5ed0\u5ed1\u5ed2\u5ed3\u5ed4\u5ed5\u5ed6\u5ed7\u5ed8\u3892\u3893\u3894\u3895\u5ed9\u5eda\u5edb\u5edc\u5edd\u5ede\u5edf\u5ee0\u5ee1\u5ee2\u5ee3\u5ee4\u3896\u3897\u3898\u5ee5\u5ee6\u5ee7\u5ee8\u5ee9\u5eea\u3899\u389a\u389b\u389c\u5eeb\u5eec\u5eed\u9f90\u389d\u5eee\u5eef\u5ef0\u389e\u5ef1\u5ef2\u5ef3\u5ef4\u5ef5\u389f\u5ef6\u5ef7\u5ef8\u5ef9\u38a0\u5efa\u5efb\u5efc\u5efd\u5efe\u5eff\u5f00\u5f01\u5f02\u5f03\u5f04\u5f05\u5f06\u5f07\u5f08\u5f09\u38a1\u5f0a\u38a2\u38a3\u5f0b\u5f0c\u5f0d\u5f0e\u5f0f\u5f10\u38a4\u38a5\u5f11\u5f12\u38a6\u5f13\u5f14\u5f15\u5f16\u38a7\u5f17\u5f18\u5f19\u5f1a\u5f1b\u5f1c\u38a8\u38a9\u38aa\u38ab\u5f1d\u5f1e\u5f1f\u5f20\u38ac\u38ad\u5f21\u5f22\u5f23\u5f24\u5f25\u5f26\u5f27\u5f28\u5f29\u5f2a\u38ae\u38af\u38b0\u38b1\u5f2b\u5f2c\u5f2d\u5f2e\u5f2f\u38b2\u38b3\u38b4\u38b5\u38b6\u38b7\u5f30\u5f31\u5f32\u5f33\u38b8\u38b9\u5f34\u5f35\u5f36\u5f37\u5f38\u5f39\u38ba\u38bb\u38bc\u5f3a\u5f3b\u5f3c\u5f3d\u5f3e\u38bd\u38be\u38bf\u5f3f\u5f40\u5f41\u5f42\u38c0\u38c1\u38c2\u5f43\u5f44\u5f45\u38c3\u5f46\u5f47\u5f48\u5f49\u38c4\u38c5\u5f4a\u5f4b\u5f4c\u5f4d\u38c6\u5f4e\u5f4f\u5f50\u5f51\u5f52\u5f53\u5f54\u5f55\u38c7\u5f56\u5f57\u5f58\u5f59\u5f5a\u5f5b\u5f5c\u38c8\u5f5d\u5f5e\u5f5f\u5f60\u5f61\u38c9\u5f62\u5f63\u5f64\u38ca\u38cb\u38cc\u38cd\u5f65\u5f66\u5f67\u5f68\u5f69\u5f6b\u5f6c\u38ce\u5f6d\u38cf\u38d0\u5f6e\u5f6f\u5f70\u38d1\u5f71\u38d2\u38d3\u5f72\u5f73\u38d4\u5f74\u5f75\u38d5\u5f76\u5f77\u5f78\u5f79\u5f7a\u5f7b\u9fc8\u9fc9\u38d6\u38d7\u5f7c\u5f7d\u5f7e\u5f7f\u5f80\u5f81\u5f82\u5f83\u5f84\u38d8\u38d9\u5f85\u5f86\u5f87\u5f88\u5f89\u5f8a\u5f8b\u5f8c\u5f8d\u5f94\u38da\u38db\u38dc\u38dd\u38de\u38df\u38e0\u38e1\u5f8e\u5f8f\u5f90\u5f91\u5f92\u5f93\u5f95\u38e2\u38e3\u5f96\u5f97\u5f98\u5f99\u5f9b\u5f9c\u5f9d\u5f9e\u5f9f\u5fa0\u5fa1\u5fa2\u5fa3\u5fa4\u38e4\u38e5\u38e6\u38e7\u38e8\u38e9\u5f9a\u5fa5\u5fa6\u5fa7\u5fa8\u5fa9\u5faa\u5fab\u38ea\u38eb\u38ec\u38ed\u38ee\u5fac\u5fad\u5fae\u5faf\u5fb0\u38ef\u5fb1\u5fb3\u5fb4\u38f0\u38f1\u38f2\u5fb2\u5fb5\u5fb6\u5fb7\u5fb8\u5fb9\u5fba\u38f3\u38f4\u5fbb\u5fbc\u38f5\u38f6\u5fbd\u5fbe\u38f7\u5fbf\u38f8\u5fc0\u5fc1\u38f9\u5fc2\u5fc3\u5fc4\u5fc5\u5fc6\u38fa\u5fc7\u5fc8\u5fc9\u5fca\u38fb\u5fcb\u5fcc\u5fcd\u5fce\u5fcf\u5fd0\u5fd1\u5fd2\u5fd3\u5fd4\u5fd5\u5fd6\u5fd7\u5fd8\u5fd9\u5fda\u5fdb\u5fdc\u38fc\u38fd\u38fe\u38ff\u3900\u5fdd\u5fde\u5fdf\u5fe0\u5fe1\u5fe2\u5fe3\u5fe4\u5fe5\u5fe6\u5fe7\u5fe8\u5fe9\u5fea\u5feb\u5fec\u5fed\u5fee\u5fef\u5ff0\u5ff1\u5ff2\u5ff3\u5ff4\u5ff5\u5ff6\u5ff7\u5ff8\u5ff9\u5ffa\u5ffb\u5ffc\u5ffd\u5ffe\u5fff\u6000\u6001\u6002\u6003\u6004\u6005\u6006\u3901\u3902\u3903\u3904\u3905\u3906\u3907\u3908\u3909\u390a\u390b\u6007\u6008\u6009\u600a\u600b\u600c\u600d\u600e\u600f\u6010\u6011\u6012\u6013\u6014\u6015\u6016\u6017\u6018\u6019\u601a\u601b\u601c\u601d\u601e\u601f\u6020\u6021\u6022\u6023\u6024\u6025\u6026\u6027\u6028\u6029\u602a\u602b\u602c\u602d\u602e\u602f\u6030\u6031\u6032\u6033\u6034\u6035\u6036\u6037\u6038\u6039\u603a\u603b\u603c\u603d\u603e\u603f\u390c\u390d\u390e\u390f\u3910\u3911\u3912\u3913\u3914\u3915\u3916\u3917\u3918\u3919\u6040\u6041\u6042\u6043\u6044\u6045\u6046\u6047\u6048\u6049\u604a\u604b\u604c\u604d\u604e\u604f\u6050\u6051\u6052\u6053\u6054\u6055\u6056\u6057\u6058\u6059\u605a\u605b\u605c\u605d\u605e\u605f\u6060\u6061\u6062\u6063\u6064\u6065\u6066\u6067\u6068\u6069\u606a\u606b\u606c\u606d\u606e\u606f\u6070\u6071\u6072\u6073\u6074\u6075\u6076\u6077\u6078\u6079\u607a\u607b\u607c\u607d\u391a\u391b\u391c\u391d\u391e\u391f\u3920\u3921\u3922\u3923\u3924\u3925\u3926\u3927\u3928\u3929\u392a\u392b\u392c\u392d\u393a\u607e\u607f\u6080\u6081\u6082\u6083\u6084\u6085\u6086\u6087\u6088\u6089\u608a\u608b\u608c\u608d\u608e\u608f\u6090\u6091\u6092\u6093\u6094\u6095\u6096\u6097\u6098\u6099\u609a\u609b\u609c\u609d\u609e\u609f\u60a0\u60a1\u60a2\u60a3\u60a4\u60a5\u60a6\u60a7\u60a8\u60a9\u60aa\u60ab\u60ac\u60ad\u60ae\u60af\u392e\u392f\u3930\u3931\u3932\u3933\u3934\u3935\u3936\u3937\u3938\u3939\u393b\u393c\u393d\u60b0\u60b1\u60b2\u60b3\u60b4\u60b5\u60b6\u60b7\u60b8\u60b9\u60ba\u60bb\u60bc\u60bd\u60be\u60bf\u60c0\u60c1\u60c2\u60c3\u60c4\u60c5\u60c6\u60c7\u60c8\u60c9\u60ca\u60cb\u60cc\u60cd\u60ce\u60cf\u60d0\u60d1\u60d2\u60d3\u60d4\u60d5\u60d6\u60d7\u60d8\u60d9\u60da\u60db\u60dc\u60dd\u60de\u60df\u60e0\u60e1\u60e2\u60e3\u60e4\u60e5\u60e6\u60e7\u60e8\u60e9\u60ea\u60eb\u60ec\u60ed\u60ee\u60ef\u393e\u393f\u3940\u3941\u3942\u3943\u3944\u3945\u3946\u3947\u3948\u3949\u394a\u394b\u394c\u394d\u394e\u394f\u3950\u3951\u3952\u3953\u3954\u3955\u3956\u3957\u3958\u3959\u60f0\u60f1\u60f2\u60f3\u60f4\u60f5\u60f6\u60f7\u60f8\u60f9\u60fa\u60fb\u60fc\u60fd\u60fe\u60ff\u6100\u6101\u6102\u6103\u6104\u6105\u6106\u6107\u6108\u6109\u610a\u610b\u610c\u610d\u610e\u610f\u6110\u6111\u6112\u6113\u6114\u6115\u6116\u6117\u6118\u6119\u611a\u611b\u611c\u611d\u611e\u611f\u6120\u6121\u6122\u6123\u6124\u6125\u6126\u6168\u395a\u395b\u395c\u395d\u395e\u395f\u3960\u3961\u3962\u3963\u3964\u3965\u3966\u3967\u3968\u3969\u396a\u396b\u6127\u6128\u6129\u612a\u612b\u612c\u612d\u612e\u612f\u6130\u6131\u6132\u6133\u6134\u6135\u6136\u6137\u6138\u6139\u613a\u613b\u613c\u613d\u613e\u613f\u6140\u6141\u6142\u6143\u6144\u6145\u6146\u6147\u6148\u6149\u614a\u614b\u614c\u614d\u614e\u614f\u6150\u6151\u396c\u396d\u396e\u396f\u3970\u3971\u3972\u3973\u3974\u3975\u3976\u3977\u3978\u3979\u397a\u397b\u6152\u6153\u6154\u6155\u6156\u6157\u6158\u6159\u615a\u615b\u615c\u615d\u615e\u615f\u6160\u6161\u6162\u6163\u6164\u6165\u6166\u6167\u6169\u616a\u616b\u616c\u616e\u616f\u6170\u6171\u6172\u6173\u6174\u6175\u6176\u6177\u6178\u6179\u617a\u617b\u617c\u617d\u617e\u617f\u6180\u6181\u6182\u6183\u6184\u6185\u6186\u6187\u6188\u397c\u397d\u397e\u397f\u3980\u3981\u3982\u3983\u3984\u3985\u3986\u3987\u3988\u616d\u6189\u618a\u618b\u618c\u618d\u618e\u618f\u6190\u6191\u6192\u6193\u6194\u6195\u6196\u6197\u6198\u6199\u619a\u619b\u619c\u619d\u619e\u619f\u61a0\u61a1\u61a2\u61a3\u61a4\u61a5\u61a6\u61a7\u61a8\u61a9\u61aa\u61ab\u61ac\u61ad\u61ae\u61af\u61b0\u61b1\u61b2\u61b3\u3989\u398a\u398b\u398c\u398d\u398e\u398f\u3990\u3991\u3992\u3993\u3994\u3995\u3996\u61b4\u61b5\u61b6\u61b7\u61b8\u61b9\u61ba\u61bc\u61bd\u61be\u61bf\u61c0\u61c1\u61c2\u61c3\u61c4\u61c5\u61c6\u61c7\u61c8\u61c9\u61ca\u61cb\u61cc\u61cd\u61ce\u61cf\u61d0\u61d1\u61d2\u61d3\u61d4\u3997\u3998\u3999\u61bb\u61d5\u61d6\u61d7\u61d8\u61d9\u61da\u61db\u61dc\u61dd\u61de\u61df\u61e0\u61e1\u61e2\u61e3\u61e4\u61e5\u61e6\u61e7\u61e8\u399a\u399b\u399c\u399d\u399e\u399f\u39a0\u61e9\u61ea\u61eb\u61ec\u61ed\u61ee\u61ef\u61f0\u61f1\u61f2\u61f3\u61f4\u39a1\u39a2\u61f5\u61f6\u61f7\u61f8\u39a3\u39a4\u39a5\u39a6\u39a7\u61f9\u61fa\u61fb\u39a8\u39a9\u39aa\u61fc\u61fd\u61fe\u61ff\u6200\u6201\u6202\u39ab\u39ac\u6203\u6204\u6205\u6206\u6207\u39ad\u6208\u6209\u620a\u620b\u620c\u620d\u620e\u620f\u6210\u6211\u6212\u6213\u39ae\u39af\u6214\u6215\u6216\u6217\u39b0\u39b1\u39b2\u6218\u39b3\u6219\u39b4\u39b5\u39b6\u621a\u621b\u621c\u621d\u39b7\u621e\u621f\u6226\u39b8\u6220\u6221\u6222\u6223\u6224\u6225\u39b9\u6227\u6228\u6229\u622a\u622b\u622c\u39ba\u622d\u622e\u622f\u6231\u39bb\u39bc\u6230\u6232\u6234\u39bd\u6233\u6235\u6236\u6237\u6238\u6239\u623a\u623b\u623c\u39be\u623d\u623e\u623f\u6240\u39bf\u39c0\u6241\u6242\u6243\u39c1\u39c2\u6244\u6245\u6246\u6247\u6248\u6249\u624a\u624b\u624c\u624d\u9fb5\u624e\u6250\u6251\u6252\u6253\u6254\u6255\u6256\u39c3\u39c4\u39c5\u624f\u6257\u6258\u6259\u625a\u625b\u625c\u625d\u625e\u6260\u6261\u6262\u6263\u6264\u6265\u6266\u6267\u6268\u6269\u626a\u626b\u626c\u39c6\u39c7\u39c8\u625f\u626d\u626e\u626f\u6270\u6271\u6272\u6273\u6274\u6275\u6276\u6277\u6279\u627a\u627b\u627c\u627d\u627e\u627f\u6280\u6281\u6282\u6283\u6284\u6285\u6286\u6287\u6288\u6289\u628a\u628b\u628c\u628d\u628e\u628f\u6290\u6291\u6292\u6293\u6294\u6295\u6296\u6297\u6298\u6299\u629a\u629b\u629c\u629d\u629e\u629f\u62a0\u62a1\u62a2\u62a3\u62a4\u62a5\u39c9\u39ca\u39cb\u39cc\u39cd\u39ce\u39cf\u39d0\u39d1\u6278\u62a6\u62a7\u62a8\u62a9\u62aa\u62ab\u62ac\u62ad\u62ae\u62af\u62b0\u62b1\u62b2\u62b3\u62b4\u62b5\u62b6\u62b7\u62b8\u62b9\u62ba\u62bb\u62bc\u62bd\u62be\u62bf\u62c0\u62c1\u62c2\u62c3\u62c4\u62c5\u62c6\u62c7\u62c8\u62c9\u62ca\u62cb\u62cc\u62cd\u62ce\u62cf\u62d0\u62d1\u62d2\u62d3\u62d4\u62d5\u62d6\u62d7\u62d8\u62d9\u62da\u62db\u62dc\u62dd\u62de\u62df\u62e0\u62e1\u62e2\u62e3\u62e4\u62e5\u62e6\u62e7\u62e8\u62e9\u39d2\u39d3\u39d4\u39d5\u39d6\u39d7\u39d8\u39d9\u39da\u39db\u39dc\u39dd\u39de\u39df\u39e0\u62ea\u62eb\u62ec\u62ed\u62ee\u62ef\u62f0\u62f1\u62f2\u62f3\u62f4\u62f5\u62f6\u62f7\u62f8\u62f9\u62fa\u62fb\u62fc\u62fd\u62fe\u62ff\u6300\u6301\u6302\u6303\u6304\u6305\u6306\u6307\u6308\u6309\u630a\u630b\u630c\u630d\u630e\u630f\u6310\u6311\u6312\u6313\u6314\u6315\u6316\u6317\u6318\u6319\u631a\u631b\u631c\u631d\u631e\u631f\u6320\u6321\u6322\u6323\u6324\u6325\u6326\u6327\u39e1\u39e2\u39e3\u39e4\u39e5\u39e6\u39e7\u39e8\u39e9\u39ea\u39eb\u39ec\u39ed\u39ee\u39ef\u39f0\u39f1\u6328\u6329\u632a\u632b\u632c\u632d\u632e\u632f\u6330\u6331\u6332\u6333\u6334\u6335\u6336\u6337\u6338\u6339\u633a\u633b\u633c\u633d\u633e\u633f\u6340\u6341\u6342\u6343\u6344\u6345\u6346\u6347\u6348\u6349\u634a\u634b\u634c\u634d\u634e\u634f\u6350\u6351\u6352\u6353\u6354\u6355\u6356\u6357\u6358\u6359\u635a\u635b\u635c\u635d\u635e\u635f\u6360\u6361\u6362\u6363\u6364\u39f2\u39f3\u39f4\u39f5\u39f6\u39f7\u39f8\u6365\u6366\u6367\u6368\u6369\u636a\u636b\u636c\u636d\u636e\u636f\u6370\u6371\u6372\u6373\u6374\u6375\u6376\u6377\u6378\u6379\u637a\u637b\u637c\u637d\u637e\u637f\u6380\u6381\u6382\u6383\u6384\u6385\u6386\u6387\u6388\u6389\u638a\u638b\u638c\u638d\u638e\u638f\u6390\u6391\u6392\u6393\u6394\u6395\u6396\u6397\u6398\u6399\u639a\u639b\u639c\u639d\u639e\u639f\u63a0\u63a1\u63a2\u63a3\u63a4\u63a5\u63a6\u63a7\u63a8\u63a9\u63aa\u63ab\u63ac\u63ad\u63ae\u63af\u63b0\u63b1\u63b3\u63b4\u63b5\u63b6\u63b7\u63b8\u63b9\u63ba\u63bb\u63bc\u63bd\u39f9\u39fa\u39fb\u39fc\u39fd\u39fe\u39ff\u3a00\u3a01\u3a02\u3a03\u3a04\u3a05\u3a06\u3a07\u3a08\u63b2\u63be\u63bf\u63c0\u63c1\u63c2\u63c3\u63c4\u63c6\u63c7\u63c8\u63c9\u63ca\u63cb\u63cc\u63cd\u63ce\u63cf\u63d0\u63d1\u63d2\u63d3\u63d4\u63d5\u63d6\u63d7\u63d8\u63d9\u63da\u63db\u63dc\u63dd\u63de\u63df\u63e0\u63e1\u63e2\u63e3\u63e4\u63e5\u63e6\u63e7\u63e8\u63e9\u63ea\u63eb\u63ec\u63ed\u63ee\u63ef\u63f0\u63f1\u63f2\u63f3\u63f4\u63f5\u63f6\u63f7\u63f8\u63f9\u63fa\u63fb\u63fc\u63fd\u63fe\u63ff\u6400\u6401\u6402\u6403\u6404\u6405\u6452\u3a09\u3a0a\u3a0b\u3a0c\u3a0d\u3a0e\u3a0f\u3a10\u3a11\u3a12\u3a13\u3a14\u3a15\u3a17\u3a18\u6406\u6407\u6408\u6409\u640a\u640b\u640c\u640d\u640e\u640f\u6410\u6411\u6412\u6413\u6414\u6415\u6416\u6417\u6418\u6419\u641a\u641b\u641c\u641d\u641e\u641f\u6420\u6421\u6422\u6423\u6424\u6425\u6426\u6427\u6428\u6429\u642a\u642b\u642c\u642d\u642e\u642f\u6430\u6432\u6433\u6434\u6435\u6436\u6437\u6438\u6439\u643a\u643b\u643c\u643d\u643e\u643f\u6440\u6441\u6442\u6443\u6444\u6445\u6446\u6447\u6448\u6449\u644a\u3a16\u3a19\u3a1a\u3a1b\u3a1c\u3a1d\u3a1e\u3a1f\u3a20\u3a21\u3a22\u3a23\u3a24\u3a25\u3a26\u3a27\u3a28\u3a29\u3a2a\u3a2b\u3a2c\u63c5\u644b\u644c\u644d\u644e\u644f\u6450\u6451\u6453\u6454\u6455\u6457\u6458\u6459\u645a\u645b\u645c\u645d\u645e\u645f\u6460\u6461\u6462\u6463\u6464\u6465\u6466\u6467\u6468\u6469\u646a\u646b\u646c\u646d\u646e\u646f\u6470\u6471\u6472\u6473\u6474\u6475\u6476\u6477\u6478\u6479\u647a\u647b\u647c\u647d\u647e\u647f\u6480\u6481\u6482\u6483\u6484\u6487\u3a2d\u3a2e\u3a2f\u3a30\u3a31\u3a32\u3a33\u3a34\u3a35\u3a36\u3a37\u3a38\u3a39\u3a3a\u3a3b\u3a3c\u3a3d\u3a3e\u3a3f\u3a40\u3a41\u3a42\u6431\u6456\u6485\u6486\u6488\u6489\u648a\u648b\u648c\u648d\u648e\u648f\u6490\u6491\u6492\u6493\u6494\u6495\u6496\u6497\u6498\u6499\u649a\u649b\u649c\u649d\u649e\u649f\u64a0\u64a1\u64a2\u64a3\u64a4\u64a5\u64a6\u64a7\u64a8\u64a9\u64aa\u64ab\u64ac\u64ad\u64ae\u64af\u64b0\u64b1\u64b2\u64b3\u64b4\u64b5\u64b6\u64b7\u64b8\u64b9\u64ba\u64c6\u3a43\u3a44\u3a45\u3a46\u3a47\u3a48\u3a49\u3a4a\u3a4b\u3a4c\u3a4d\u3a4e\u3a4f\u3a50\u3a51\u64bb\u64bc\u64bd\u64be\u64bf\u64c0\u64c1\u64c2\u64c3\u64c4\u64c5\u64c7\u64c8\u64c9\u64ca\u64cb\u64cc\u64cd\u64ce\u64cf\u64d0\u64d1\u64d2\u64d3\u64d4\u64d5\u64d6\u64d7\u64d8\u64d9\u64da\u64db\u64dc\u64dd\u64de\u3a52\u3a53\u3a54\u3a55\u3a56\u3a57\u3a58\u3a59\u64df\u64e0\u64e1\u64e2\u64e3\u64e4\u64e6\u64e7\u64e8\u64e9\u64ea\u64eb\u64ec\u64ed\u64ee\u64ef\u64f0\u64f1\u3a5a\u3a5b\u3a5c\u3a5d\u3a5e\u3a5f\u64e5\u64f2\u64f3\u64f4\u64f5\u64f6\u64f7\u64f8\u64f9\u64fa\u64fb\u64fc\u64fd\u64fe\u64ff\u6500\u6501\u6502\u6503\u6504\u6505\u6506\u3a60\u3a61\u3a62\u3a63\u3a64\u3a65\u3a66\u3a67\u3a68\u3a69\u3a6a\u3a6b\u6507\u6508\u6509\u650a\u650b\u650c\u650d\u650e\u650f\u6510\u6512\u3a6c\u3a6d\u6511\u6513\u6514\u6515\u6516\u6517\u6518\u6519\u651a\u3a6e\u3a6f\u3a70\u651b\u651c\u651d\u3a71\u3a72\u3a73\u3a74\u651e\u651f\u6520\u6521\u6522\u6523\u6524\u6526\u6527\u9fdc\u6525\u6528\u6529\u652a\u652b\u3a75\u3a76\u3a77\u652c\u652d\u652e\u3a78\u3a79\u652f\u6530\u3a7a\u6531\u3a7b\u3a7c\u3a7d\u6532\u3a7e\u6533\u6534\u6535\u6536\u6537\u6538\u6539\u653a\u653b\u653c\u3a7f\u3a80\u653d\u653e\u653f\u3a81\u3a82\u6540\u6541\u6542\u6543\u6544\u6545\u3a83\u3a84\u3a85\u3a86\u6546\u6547\u6548\u6549\u654a\u654b\u654c\u3a87\u3a88\u3a89\u555f\u654d\u654e\u654f\u6550\u6551\u6552\u6553\u6554\u6555\u6556\u6557\u6558\u6559\u655a\u655b\u3a8a\u3a8b\u3a8c\u3a8d\u3a8e\u655c\u655d\u655e\u655f\u6560\u6561\u6562\u6563\u6564\u6565\u6566\u6567\u6568\u6569\u656a\u656d\u3a8f\u3a90\u3a91\u3a92\u3a93\u3a94\u3a95\u3a96\u3a97\u3a98\u3a99\u3a9a\u656b\u656c\u656e\u656f\u6570\u3a9b\u3a9c\u3a9d\u3a9e\u3a9f\u6571\u6572\u6573\u3aa0\u3aa1\u3aa2\u3aa3\u6574\u6575\u6576\u6577\u6578\u6579\u657a\u657b\u3aa4\u3aa5\u3aa6\u657c\u657d\u657e\u657f\u3aa7\u3aa8\u3aa9\u3aaa\u6580\u6581\u6582\u6583\u8d01\u3aab\u3aac\u6584\u6585\u6586\u3aad\u3aae\u6587\u6588\u3aaf\u6589\u658a\u658b\u3ab0\u658c\u658d\u658e\u658f\u6590\u6591\u6592\u3ab1\u6593\u6594\u6595\u6596\u6597\u3ab2\u3ab3\u6598\u3ab4\u3ab5\u6599\u659a\u3ab6\u659b\u659c\u3ab7\u659d\u3ab8\u659e\u659f\u65a0\u65a1\u3ab9\u65a2\u65a3\u3aba\u3abb\u65a4\u65a5\u65a6\u65a7\u65a8\u65a9\u65aa\u65ab\u3abc\u3abd\u3abe\u3abf\u65ac\u65ad\u65ae\u65af\u3ac0\u65b0\u65b1\u65b2\u3ac1\u65b3\u3ac2\u65b4\u65b5\u65b6\u65b7\u65b8\u65b9\u3ac3\u65ba\u65bb\u65bc\u3ac4\u65bd\u65be\u65bf\u65c0\u3ac6\u65c1\u65c2\u65c3\u65c4\u65c5\u65c6\u65ca\u3ac5\u3ac7\u3ac8\u3ac9\u65c7\u65c8\u65c9\u65cb\u65cc\u65cd\u65ce\u65cf\u3aca\u3acb\u3acc\u65d0\u65d1\u3acd\u65d2\u65d3\u65d4\u65d5\u65d6\u65d7\u3ace\u3acf\u65d8\u65d9\u65da\u65db\u65dc\u65dd\u65de\u65df\u65e0\u65e1\u65e2\u65e3\u65e4\u65e5\u65e6\u65e7\u65e8\u65e9\u65ea\u65eb\u65ec\u65ed\u65ee\u65ef\u3ad0\u3ad1\u65f0\u65f1\u65f2\u65f3\u65f4\u65f5\u65f6\u65f7\u65f8\u3ad2\u3ad3\u3ad4\u3ad5\u3ad6\u3ad7\u65f9\u65fa\u65fb\u65fc\u65fd\u65fe\u65ff\u6600\u6601\u6602\u6603\u6604\u6605\u6606\u6607\u6608\u6609\u660a\u660b\u660c\u660d\u660e\u660f\u6610\u6611\u6612\u6613\u6614\u6615\u6616\u6617\u6618\u6619\u3ad8\u3ad9\u3ada\u661a\u661b\u661c\u661d\u661e\u661f\u6620\u6621\u6622\u6623\u6624\u6625\u6626\u6627\u6628\u6629\u662a\u662b\u662c\u662d\u662e\u662f\u6630\u6631\u6632\u6633\u6634\u6635\u6636\u6637\u6638\u6639\u663a\u663b\u663c\u663d\u663e\u663f\u3adb\u3adc\u3add\u3ade\u3adf\u3ae0\u3ae1\u3ae2\u3ae3\u3ae4\u6640\u6641\u6642\u6643\u6644\u6645\u6646\u6647\u6648\u6649\u664a\u664b\u664c\u664d\u664e\u664f\u6650\u6651\u6652\u6653\u6654\u6655\u6656\u3ae5\u3ae6\u3ae7\u3ae8\u3ae9\u3aea\u3aeb\u3aec\u3aed\u3aee\u52d7\u6657\u6658\u6659\u665a\u665b\u665c\u665d\u665e\u665f\u6660\u6661\u6662\u6663\u6664\u6665\u6666\u6667\u6668\u6669\u66fd\u3aef\u3af0\u3af1\u3af2\u3af3\u3af4\u666a\u666b\u666c\u666d\u666e\u666f\u6670\u6671\u6672\u6673\u6674\u6675\u6676\u6677\u6678\u6679\u667a\u667b\u667c\u667d\u667e\u667f\u6680\u6681\u6682\u6683\u6691\u3af5\u3af6\u3af7\u3af8\u3af9\u3afa\u3afb\u3afc\u3afd\u3afe\u3aff\u3b00\u6684\u6685\u6686\u6687\u6688\u6689\u668a\u668b\u668c\u668d\u668e\u668f\u6690\u6692\u6693\u6694\u6695\u6696\u6697\u6698\u6699\u3b01\u3b02\u3b03\u3b04\u3b05\u3b06\u3b07\u3b08\u3b09\u3b0a\u3b0b\u3b0c\u669a\u669b\u669c\u669d\u669e\u669f\u66a0\u66a1\u66a2\u66a3\u66a4\u66a5\u66a6\u66a7\u66a8\u3b0d\u3b0e\u3b0f\u3b10\u66a9\u66aa\u66ab\u66ac\u66ad\u66ae\u66af\u66b0\u66b1\u66b2\u66b3\u66b4\u66b5\u66b6\u66b7\u3b11\u3b12\u3b13\u3b14\u3b15\u3b16\u66b8\u66b9\u66ba\u66bb\u66bc\u66bd\u66be\u66bf\u66c0\u66c1\u66c2\u66c3\u66c4\u66c5\u66c6\u66c7\u66c8\u66c9\u66ca\u66cb\u66cc\u66cd\u9fa7\u3b17\u3b18\u3b19\u3b1a\u3b1b\u3b1c\u3b1d\u3b1e\u3b1f\u66ce\u66cf\u66d0\u66d1\u66d2\u66d3\u66d4\u66d5\u66d6\u66d7\u66da\u3b20\u3b21\u3b22\u66d8\u66d9\u66db\u66dc\u3b23\u3b24\u3b25\u3b26\u3b27\u3b28\u66dd\u66de\u66df\u66e0\u66e1\u66e2\u3b29\u3b2a\u66e3\u66e4\u66e5\u66e6\u66e7\u66e8\u3b2b\u66e9\u3b2c\u3b2d\u66ea\u66eb\u66ec\u3b2e\u66ed\u66ee\u66ef\u3b2f\u66f0\u66f1\u66f2\u66f3\u3b30\u66f4\u66f5\u66f6\u66f7\u66f8\u66fa\u66f9\u66fb\u66fc\u66fe\u66ff\u6701\u6702\u6703\u6704\u6705\u6706\u3b31\u3b32\u6707\u6708\u6709\u670a\u670b\u670c\u670d\u3b33\u4dba\u4dbb\u670e\u670f\u6710\u6712\u6713\u6714\u6715\u3b34\u3b35\u6716\u6717\u6718\u6719\u671a\u671b\u3b36\u3b37\u671c\u671d\u671e\u671f\u3b38\u4dbc\u6720\u4dbd\u6722\u3b3a\u3b3b\u81a4\u3b3c\u3b3d\u3b3e\u6723\u6724\u3b3f\u3b40\u6726\u6727\u6728\u6729\u672a\u672b\u672c\u672d\u672e\u672f\u6730\u6731\u6732\u6733\u6734\u6735\u6736\u6737\u6738\u6739\u673a\u673b\u673c\u673d\u673e\u673f\u6740\u6741\u6742\u6743\u3b41\u6744\u6745\u6746\u6747\u6748\u6749\u674a\u674b\u674c\u674d\u674e\u674f\u6750\u6751\u6752\u6753\u6754\u6755\u6756\u6757\u6758\u6759\u675a\u675b\u675c\u675d\u675e\u675f\u6760\u6761\u6762\u6763\u6764\u6765\u6766\u6767\u6768\u6769\u6781\u3b42\u3b43\u3b44\u3b45\u3b46\u676a\u676b\u676c\u676d\u676e\u676f\u6770\u6771\u6772\u6773\u6774\u6775\u6776\u6777\u6778\u6779\u677a\u677b\u677c\u677d\u677e\u677f\u6780\u6782\u6783\u6784\u6785\u6786\u6787\u6788\u6789\u678a\u678b\u678c\u678d\u678e\u678f\u6790\u6791\u6792\u6793\u6794\u6795\u6796\u6797\u6798\u6799\u679a\u679b\u679c\u679d\u679e\u679f\u67a0\u67a1\u67a2\u67a3\u67a4\u67a5\u67a6\u67a7\u67a8\u67a9\u67aa\u67ab\u67ac\u67ad\u3b47\u3b48\u3b49\u3b4a\u3b4b\u3b4c\u3b4d\u3b4e\u3b4f\u3b50\u67ae\u67af\u67b0\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7\u67b8\u67b9\u67ba\u67bb\u67bc\u67bd\u67be\u67bf\u67c0\u67c1\u67c2\u67c3\u67c4\u67c5\u67c6\u67c7\u67c8\u67c9\u67ca\u67cb\u67cc\u67cd\u67ce\u67cf\u67d0\u67d1\u67d2\u67d3\u67d4\u67d5\u67d6\u67d7\u67d8\u67d9\u67da\u67db\u67dc\u67dd\u67de\u67df\u67e0\u67e1\u67e2\u67e3\u67e4\u67e5\u67e6\u67e7\u67e8\u67e9\u67ea\u67eb\u67ec\u67ed\u67ee\u67ef\u67f0\u67f1\u67f2\u67f3\u67f4\u67f5\u67f6\u67f7\u67f8\u67f9\u67fa\u67fb\u67fc\u67fd\u67fe\u67ff\u6800\u6801\u6802\u6803\u6804\u6805\u6806\u6807\u6808\u6809\u680a\u680b\u680c\u680d\u680e\u680f\u6810\u6811\u3b51\u3b52\u3b53\u3b54\u3b55\u3b56\u3b57\u6812\u6813\u6814\u6815\u6816\u6817\u6818\u6819\u681a\u681b\u681c\u681d\u681e\u681f\u6820\u6821\u6822\u6823\u6824\u6825\u6826\u6827\u6828\u6829\u682a\u682b\u682c\u682d\u682e\u682f\u6830\u6831\u6832\u6833\u6834\u6835\u6836\u6837\u6838\u6839\u683a\u683b\u683c\u683d\u683e\u683f\u6840\u6841\u6842\u6843\u6844\u6845\u6846\u6847\u6848\u6849\u684a\u684b\u684c\u684d\u684e\u684f\u6850\u6851\u6852\u6853\u6854\u6855\u6856\u6857\u6858\u6859\u685a\u685b\u685c\u685d\u685e\u685f\u6860\u6861\u6862\u6863\u6864\u6865\u6866\u6867\u6868\u6869\u686a\u9fc4\u3b58\u3b59\u3b5a\u3b5b\u3b5c\u3b5d\u3b5e\u3b5f\u3b60\u3b61\u3b62\u3b63\u3b64\u3b65\u3b66\u3b67\u686b\u686c\u686d\u686e\u686f\u6870\u6871\u6872\u6873\u6874\u6875\u6876\u6877\u6878\u6879\u687a\u687b\u687c\u687d\u687e\u687f\u6880\u6881\u6882\u6883\u6884\u6885\u6886\u6887\u6888\u6889\u688a\u688b\u688c\u688d\u688e\u688f\u6890\u6891\u6892\u6893\u6894\u6895\u6896\u6897\u6898\u6899\u689a\u689b\u689c\u689d\u689e\u689f\u68a0\u68a1\u68a2\u68a3\u68a4\u68a5\u68a6\u68a7\u68a8\u68a9\u68aa\u68ab\u68ac\u68ad\u68ae\u68af\u68b0\u68b1\u68b2\u68b3\u68b4\u68b5\u68b6\u68b7\u68b8\u68b9\u68ba\u68bb\u68bc\u68bd\u68be\u68bf\u68c0\u68c1\u68c2\u3b68\u3b69\u3b6a\u3b6b\u3b6c\u3b6d\u3b6e\u3b6f\u3b70\u3b71\u3b72\u3b73\u3b74\u3b75\u3b77\u68c3\u68c4\u68c5\u68c6\u68c7\u68c8\u68c9\u68ca\u68cb\u68cc\u68cd\u68ce\u68cf\u68d0\u68d1\u68d2\u68d3\u68d4\u68d5\u68d6\u68d7\u68d8\u68d9\u68da\u68db\u68dc\u68dd\u68de\u68df\u68e0\u68e1\u68e2\u68e3\u68e4\u68e5\u68e6\u68e7\u68e8\u68e9\u68ea\u68eb\u68ec\u68ed\u68ee\u68ef\u68f0\u68f1\u68f2\u68f3\u68f4\u68f5\u68f6\u68f7\u68f8\u68f9\u68fa\u68fb\u68fc\u68fd\u68fe\u68ff\u6900\u6901\u6902\u6903\u6904\u6905\u6906\u6907\u6908\u6909\u690a\u690b\u690c\u690d\u690e\u690f\u6910\u6911\u6912\u6913\u6914\u6915\u6916\u6917\u6918\u6919\u691a\u691b\u691c\u691d\u691e\u691f\u6920\u6921\u6922\u6923\u6924\u6925\u6926\u6927\u6928\u6929\u692a\u692b\u692c\u692d\u692e\u3b76\u3b78\u3b79\u3b7a\u3b7b\u3b7c\u3b7d\u3b7e\u3b7f\u3b80\u3b81\u3b82\u3b83\u3b84\u3b85\u3b86\u3b87\u3b88\u3b89\u3b8a\u692f\u6930\u6931\u6932\u6933\u6934\u6935\u6936\u6937\u6938\u6939\u693a\u693b\u693c\u693d\u693e\u693f\u6940\u6941\u6942\u6943\u6944\u6945\u6946\u6947\u6948\u6949\u694a\u694b\u694c\u694d\u694e\u694f\u6950\u6951\u6952\u6953\u6954\u6955\u6956\u6957\u6958\u6959\u695a\u695b\u695c\u695d\u695e\u695f\u6960\u6961\u6962\u6963\u6964\u6965\u6966\u6967\u6968\u6969\u696a\u696b\u696c\u696d\u696e\u696f\u6970\u6971\u6972\u6973\u6974\u6975\u6976\u6977\u6978\u6979\u697a\u697b\u697c\u697d\u697e\u697f\u6980\u6981\u6982\u6983\u6984\u6985\u6986\u6987\u6988\u6989\u698b\u698c\u6994\u6998\ufa13\u3b8b\u3b8c\u3b8d\u3b8e\u3b8f\u3b90\u3b91\u3b92\u3b93\u3b94\u3b95\u3b96\u3b97\u3b98\u3b99\u3b9a\u3b9b\u3b9c\u3b9d\u3b9e\u3b9f\u3ba0\u3ba2\u3ba3\u698a\u698d\u698e\u698f\u6990\u6991\u6992\u6993\u6995\u6996\u6997\u6999\u699a\u699b\u699c\u699d\u699e\u699f\u69a0\u69a1\u69a2\u69a3\u69a4\u69a5\u69a6\u69a7\u69a8\u69a9\u69aa\u69ab\u69ac\u69ad\u69ae\u69af\u69b0\u69b1\u69b2\u69b3\u69b4\u69b5\u69b6\u69b7\u69b8\u69b9\u69ba\u69bb\u69bc\u69bd\u69be\u69bf\u69c0\u69c1\u69c2\u69c3\u69c4\u69c5\u69c6\u69c7\u69c8\u69c9\u69ca\u69cb\u69cc\u69cd\u69ce\u69cf\u69d0\u69d1\u69d2\u69d3\u69d4\u69d5\u69d6\u69d7\u69d8\u69d9\u69da\u69db\u69dc\u69dd\u69de\u69df\u69e0\u69e1\u69e9\u6a2e\ufa14\u3ba4\u3ba5\u3ba6\u3ba7\u3ba8\u3ba9\u3baa\u3bab\u3bac\u3bad\u3bae\u3baf\u3bb0\u3bb1\u3bb2\u3bb3\u3bb4\u3bb5\u3bb6\u3bb7\u3bb8\u3bb9\u3bba\u3bbb\u3bbc\u3bbd\u69e2\u69e3\u69e4\u69e5\u69e6\u69e7\u69e8\u69ea\u69eb\u69ec\u69ed\u69ee\u69ef\u69f0\u69f1\u69f2\u69f3\u69f4\u69f5\u69f6\u69f7\u69f8\u69f9\u69fa\u69fb\u69fc\u69fd\u69fe\u69ff\u6a00\u6a01\u6a02\u6a03\u6a04\u6a05\u6a06\u6a07\u6a08\u6a09\u6a0a\u6a0b\u6a0c\u6a0d\u6a0e\u6a0f\u6a10\u6a11\u6a12\u6a13\u6a14\u6a15\u6a16\u6a17\u6a18\u6a19\u6a1a\u6a1b\u6a1c\u6a1d\u6a1e\u6a1f\u6a20\u6a21\u6a22\u6a23\u6a24\u6a25\u6a26\u6a27\u6a28\u6a29\u6a2a\u6a2b\u6a2c\u6a2d\u6a2f\u6a30\u6a31\u6a65\u6a74\u3bbe\u3bbf\u3bc0\u3bc1\u3bc2\u3bc3\u3bc4\u3bc5\u3bc6\u3bc7\u3bc8\u3bc9\u3bca\u3bcb\u3bcc\u3bcd\u3bce\u3bcf\u3bd1\u3bd2\u3be0\u6a32\u6a33\u6a34\u6a35\u6a36\u6a37\u6a38\u6a39\u6a3a\u6a3b\u6a3c\u6a3d\u6a3e\u6a3f\u6a40\u6a41\u6a42\u6a43\u6a44\u6a45\u6a46\u6a47\u6a48\u6a49\u6a4a\u6a4b\u6a4c\u6a4d\u6a4e\u6a4f\u6a50\u6a51\u6a52\u6a53\u6a54\u6a55\u6a56\u6a57\u6a58\u6a59\u6a5a\u6a5b\u6a5c\u6a5d\u6a5e\u6a5f\u6a60\u6a61\u6a62\u6a63\u6a64\u6a66\u6a67\u6a68\u6a69\u6a6a\u6a6b\u6a6c\u6a6d\u6a6e\u6a6f\u6a70\u6a71\u6a72\u6a73\u6a75\u6a76\u6a77\u6a78\u6a79\u6a7a\u6a7b\u6a7c\u3bd0\u3bd3\u3bd4\u3bd5\u3bd6\u3bd7\u3bd8\u3bd9\u3bda\u3bdb\u3bdc\u3bdd\u3bde\u3bdf\u3be1\u3be2\u3be3\u3be4\u3be5\u3be6\u3be7\u3be8\u3be9\u6a7d\u6a7e\u6a7f\u6a80\u6a81\u6a82\u6a83\u6a84\u6a85\u6a86\u6a87\u6a88\u6a89\u6a8a\u6a8b\u6a8c\u6a8d\u6a8e\u6a8f\u6a90\u6a91\u6a92\u6a93\u6a94\u6a95\u6a96\u6a97\u6a98\u6a99\u6a9a\u6a9b\u6a9c\u6a9d\u6a9e\u6a9f\u6aa0\u6aa1\u6aa2\u6aa3\u6aa4\u6aa5\u6aa6\u6aa7\u6aa8\u6aa9\u6aaa\u3bea\u3beb\u3bec\u3bed\u3bee\u3bef\u3bf0\u3bf1\u3bf2\u3bf3\u3bf4\u3bf5\u3bf6\u6aab\u6aac\u6aad\u6aae\u6aaf\u6ab0\u6ab1\u6ab2\u6ab3\u6ab4\u6ab5\u6ab6\u6ab7\u6ab8\u6ab9\u6aba\u6abb\u6abc\u6abd\u6abe\u6abf\u6ac0\u6ac1\u6ac2\u6ac3\u6ac4\u6ac5\u6ac6\u6ac7\u6ac8\u6ac9\u6aca\u3bf7\u3bf8\u3bf9\u3bfa\u3bfb\u3bfc\u3bfd\u6acb\u6acc\u6acd\u6ace\u6acf\u6ad0\u6ad1\u6ad2\u6ad3\u6ad4\u6ad5\u6ad6\u6ad7\u6ad8\u6ad9\u6ada\u6adb\u6adc\u6add\u6ade\u6adf\u6ae0\u6ae1\u6ae2\u6ae3\u6ae4\u6ae5\u6ae6\u6aeb\u6aed\u3bfe\u3bff\u3c00\u3c01\u3c02\u3c03\u3c04\u3c05\u3c06\u3c07\u3c08\u6ae7\u6ae8\u6ae9\u6aea\u6aec\u6aee\u6aef\u6af0\u6af1\u6af2\u6af3\u6af4\u6af5\u6af6\u3c09\u3c0a\u3c0b\u3c0c\u3c0d\u3c0e\u3c0f\u3c10\u3c11\u3c12\u3c13\u3c16\u6af8\u6af9\u6afa\u6afb\u6afc\u6afd\u6afe\u6aff\u6b00\u6b01\u6b02\u6b03\u6b04\u6b05\u6b0c\u3c14\u3c15\u6af7\u6b06\u6b07\u6b08\u6b09\u6b0a\u6b0b\u6b0d\u6b0e\u3c17\u3c18\u6b0f\u6b10\u6b11\u6b12\u3c19\u3c1a\u3c1b\u3c1c\u6b13\u6b14\u6b15\u6b16\u6b17\u6b18\u6b19\u6b1a\u6b1b\u6b1c\u6b1d\u9fdd\u6b1e\u6b1f\u6b20\u6b21\u6b22\u6b24\u3c1d\u3c1e\u6b23\u6b25\u6b26\u6b27\u3c1f\u3c20\u3c21\u3c22\u6b28\u6b29\u6b2a\u3c23\u3c24\u3c25\u3c26\u3c27\u3c28\u6b2b\u6b2c\u6b2d\u6b2e\u6b2f\u6b30\u6b31\u3c29\u3c2a\u3c2b\u3c2c\u3c2d\u4dbe\u6b32\u6b33\u6b34\u6b35\u6b36\u6b37\u6b38\u3c2e\u3c2f\u3c30\u3c31\u6b39\u6b3a\u6b3b\u6b3c\u6b3d\u6b3e\u6b3f\u3c32\u3c33\u3c34\u3c35\u3c36\u3c38\u6b40\u6b41\u6b42\u6b43\u6b44\u6b45\u6b46\u6b47\u6b48\u3c39\u3c3a\u3c3b\u3c3c\u3c3d\u3c3e\u6b49\u6b4a\u6b4b\u6b4c\u6b4d\u3c3f\u3c40\u3c41\u6b4e\u6b4f\u6b50\u6b51\u6b52\u6b53\u3c42\u3c43\u6b54\u6b55\u6b56\u6b57\u6b58\u6b59\u6b5a\u3c44\u3c45\u6b5b\u6b5c\u6b5d\u3c46\u3c47\u3c48\u3c49\u6b5e\u6b5f\u6b60\u3c4a\u3c4b\u6b61\u3c4c\u3c4d\u3c4e\u6b62\u9fb0\u6b63\u6b64\u3c4f\u6b65\u3c50\u3c51\u6b66\u6b67\u6b68\u6b69\u6b6a\u6b6b\u3c52\u3c53\u3c54\u6b6c\u6b6d\u6b6e\u6b6f\u3c55\u3c56\u6b70\u6b71\u6b72\u6b73\u6b74\u3c57\u6b75\u6b76\u6b77\u6b78\u3c58\u6b79\u6b7a\u6b7b\u3c59\u6b7c\u6b7d\u6b7e\u6b7f\u6b80\u6b81\u3c5a\u3c5b\u3c5c\u3c5d\u6b82\u6b83\u6b84\u6b85\u6b86\u6b87\u3c5e\u3c5f\u3c60\u6b88\u6b89\u6b8a\u6b8b\u3c37\u3c61\u6b8c\u6b8d\u6b8e\u6b8f\u6b90\u6b91\u6b92\u6b93\u3c62\u3c63\u6b94\u6b95\u6b96\u6b97\u6b98\u6b99\u6b9a\u3c64\u3c65\u3c66\u3c67\u3c68\u3c69\u6b9b\u6b9c\u3c6a\u3c6b\u3c6c\u3c6d\u3c6e\u6b9d\u6b9e\u6b9f\u6ba0\u6ba1\u3c6f\u3c70\u3c71\u3c72\u6ba2\u6ba3\u6ba4\u6ba5\u6ba6\u3c73\u3c74\u6ba7\u6ba8\u6ba9\u6baa\u6bab\u3c75\u3c76\u3c77\u6bac\u6bad\u6bae\u3c78\u6baf\u6bb0\u6bb1\u9fde\u3c79\u3c7a\u6bb2\u3c7b\u6bb3\u3c7c\u6bb4\u3c7d\u6bb5\u6bb6\u6bb7\u3c7e\u3c7f\u6bb8\u6bb9\u6bba\u6bbb\u3c80\u6bbc\u6bbd\u3c81\u3c82\u3c83\u6bbe\u6bbf\u6bc0\u6bc1\u6bc2\u3c84\u6bc3\u6bc4\u3c85\u6bc5\u6bc6\u6bc7\u6bc8\u3c86\u3c87\u3c88\u3c89\u3c8a\u6bc9\u6bca\u6bcb\u6bcc\u6bcd\u6bce\u6bcf\u6bd0\u6bd1\u6bd2\u6bd3\u6bd4\u6bd5\u6bd6\u6bd7\u6bd8\u3c8b\u6bd9\u6bda\u6bdb\u3c8c\u6bdc\u6bdd\u6bde\u6bdf\u3c8d\u3c8e\u3c8f\u3c90\u3c91\u6be0\u6be1\u3c92\u6be2\u6be3\u6be4\u6be5\u6be6\u6be7\u6be8\u6be9\u6bea\u3c93\u6beb\u6bec\u6bed\u6bee\u3c94\u3c95\u3c96\u3c97\u3c98\u3c99\u3c9a\u6bef\u6bf0\u6bf1\u6bf2\u6bf3\u6bf4\u6bf5\u6bf6\u3c9b\u3c9c\u3c9d\u3c9e\u6bf7\u6bf8\u6bf9\u6bfa\u6bfb\u6bfc\u6bfd\u3c9f\u3ca0\u3ca1\u3ca2\u3ca3\u6bfe\u3ca4\u3ca5\u3ca6\u3ca7\u3ca8\u3ca9\u6bff\u6c00\u6c01\u6c02\u6c03\u6c04\u6c05\u6c06\u6c07\u3caa\u3cab\u3cac\u3cad\u3cae\u6c08\u6c09\u6c0a\u6c0b\u3caf\u3cb0\u6c0c\u3cb1\u6c0d\u6c0e\u3cb2\u6c0f\u6c10\u6c11\u6c12\u6c13\u3cb3\u6c14\u6c15\u6c16\u6c17\u6c18\u6c19\u6c1a\u6c1b\u6c1c\u6c1d\u3cb4\u6c1e\u6c1f\u6c20\u6c21\u6c22\u6c23\u6c24\u6c25\u6c26\u6c27\u6c28\u6c29\u6c2a\u6c2b\u3cb5\u6c2c\u6c2d\u6c2e\u6c2f\u6c30\u6c31\u6c32\u6c33\u3cb6\u3cb7\u9feb\u6c34\u6c35\u6c3a\u6c36\u6c37\u6c38\u6c39\u3cb8\u6c3b\u6c3c\u6c3e\u6c3f\u6c40\u6c41\u6c42\u6c43\u6c44\u6c45\u6c46\u6c47\u6c48\u6c49\u3cb9\u3cba\u3cbb\u3cbc\u6c4a\u6c4b\u6c4c\u6c4d\u6c4e\u6c4f\u6c50\u6c51\u6c52\u6c53\u6c54\u6c55\u6c56\u6c57\u6c58\u6c59\u6c5a\u6c5b\u6c5c\u6c5d\u6c5e\u6c5f\u6c60\u6c61\u6c62\u6c63\u6c64\u3cbd\u3cbe\u3cbf\u6c65\u6c66\u6c67\u6c68\u6c69\u6c6a\u6c6b\u6c6c\u6c6d\u6c6e\u6c6f\u6c70\u6c71\u6c72\u6c73\u6c74\u6c75\u6c76\u6c77\u6c78\u6c79\u6c7a\u6c7b\u6c7c\u6c7d\u6c7e\u6c7f\u6c80\u6c81\u6c82\u6c83\u6c84\u6c85\u6c86\u6c87\u6c88\u6c89\u6c8a\u6c8b\u6c8c\u6c8d\u6c8e\u6c8f\u6c90\u6c91\u6c92\u6c93\u6c94\u6c95\u6c96\u6c97\u6c98\u6c99\u6c9a\u6c9b\u6c9c\u6c9d\u6c9e\u6c9f\u6ca0\u6ca1\u6ca2\u6ca3\u6ca4\u6ca5\u6ca6\u6ca7\u6ca8\u6ca9\u6caa\u3cc0\u3cc1\u3cc2\u3cc3\u3cc4\u3cc5\u3cc6\u3cc7\u3cc8\u3cc9\u3cca\u6cab\u6cac\u6cad\u6cae\u6caf\u6cb0\u6cb1\u6cb2\u6cb3\u6cb4\u6cb5\u6cb6\u6cb7\u6cb8\u6cb9\u6cba\u6cbb\u6cbc\u6cbd\u6cbe\u6cbf\u6cc0\u6cc1\u6cc2\u6cc3\u6cc4\u6cc5\u6cc6\u6cc7\u6cc8\u6cc9\u6cca\u6ccb\u6ccc\u6ccd\u6cce\u6ccf\u6cd0\u6cd1\u6cd2\u6cd3\u6cd4\u6cd5\u6cd6\u6cd7\u6cd8\u6cd9\u6cdb\u6cdc\u6cdd\u6cde\u6cdf\u6ce0\u6ce1\u6ce2\u6ce3\u6ce4\u6ce5\u6ce6\u6ce7\u6ce8\u6ce9\u6cea\u6ceb\u6cec\u6ced\u6cee\u6cef\u6cf0\u6cf1\u6cf2\u6cf3\u6cf4\u6cf6\u6cf7\u6cf8\u6cf9\u6cfa\u6cfb\u6cfc\u6cfd\u6cfe\u3ccb\u3ccc\u3ccd\u3cce\u3ccf\u3cd0\u3cd1\u3cd2\u3cd3\u3cd4\u3cd5\u6cda\u6cff\u6d00\u6d01\u6d02\u6d03\u6d04\u6d05\u6d06\u6d07\u6d08\u6d09\u6d0a\u6d0b\u6d0c\u6d0e\u6d0f\u6d10\u6d11\u6d12\u6d13\u6d14\u6d15\u6d17\u6d18\u6d19\u6d1a\u6d1b\u6d1c\u6d1d\u6d1e\u6d1f\u6d20\u6d21\u6d22\u6d23\u6d24\u6d25\u6d26\u6d27\u6d28\u6d29\u6d2a\u6d2b\u6d2c\u6d2d\u6d2e\u6d2f\u6d30\u6d31\u6d32\u6d33\u6d34\u6d35\u6d36\u6d37\u6d38\u6d39\u6d3a\u6d3b\u6d3c\u6d3d\u6d3e\u6d3f\u6d40\u6d41\u6d42\u6d43\u6d44\u6d45\u6d46\u6d47\u6d48\u6d49\u6d4a\u6d4b\u6d4c\u6d4d\u6d4e\u6d4f\u6d50\u6d51\u6d52\u6d53\u6d54\u6d55\u3cd6\u3cd7\u3cd8\u3cd9\u3cda\u3cdb\u3cdc\u3cdd\u3cde\u3cdf\u3ce0\u3ce1\u3ce2\u3ce3\u6d0d\u6d16\u6d56\u6d57\u6d58\u6d59\u6d5a\u6d5b\u6d5c\u6d5d\u6d5e\u6d5f\u6d60\u6d61\u6d62\u6d63\u6d64\u6d65\u6d66\u6d67\u6d68\u6d69\u6d6a\u6d6b\u6d6c\u6d6d\u6d6e\u6d6f\u6d70\u6d71\u6d72\u6d73\u6d74\u6d75\u6d76\u6d77\u6d78\u6d79\u6d7a\u6d7b\u6d7c\u6d7d\u6d7e\u6d7f\u6d80\u6d81\u6d82\u6d83\u6d84\u6d85\u6d86\u6d87\u6d88\u6d89\u6d8a\u6d8b\u6d8c\u6d8d\u6d8e\u6d8f\u6d90\u6d91\u6d92\u6d93\u6d94\u6d95\u6d96\u6d97\u6d98\u6d9a\u6d9b\u6d9c\u6d9d\u6d9e\u6d9f\u6da0\u6da1\u6da2\u6da3\u6da4\u6da5\u6da6\u6da7\u6da8\u6da9\u3ce4\u3ce5\u3ce6\u3ce7\u3ce8\u3ce9\u3cea\u3ceb\u3cec\u3ced\u3cee\u3cef\u3cf0\u3cf1\u3cf2\u3cf3\u6d99\u6daa\u6dab\u6dac\u6dad\u6dae\u6daf\u6db0\u6db1\u6db2\u6db3\u6db4\u6db5\u6db6\u6db7\u6db8\u6db9\u6dba\u6dbb\u6dbc\u6dbd\u6dbe\u6dbf\u6dc0\u6dc1\u6dc2\u6dc3\u6dc4\u6dc5\u6dc6\u6dc7\u6dc8\u6dc9\u6dca\u6dcb\u6dcc\u6dcd\u6dce\u6dcf\u6dd0\u6dd1\u6dd2\u6dd3\u6dd4\u6dd5\u6dd6\u6dd7\u6dd8\u6dd9\u6dda\u6ddb\u6ddc\u6ddd\u6dde\u6ddf\u6de0\u6de1\u6de2\u6de3\u6de4\u6de5\u6de6\u6de7\u6de8\u6de9\u6dea\u6deb\u6dec\u6ded\u6dee\u6def\u6df0\u6df1\u6df2\u6df3\u6df4\u6df5\u6df6\u6df7\u6df8\u6df9\u6dfa\u6dfb\u6dfc\u6dfd\u6dfe\u6dff\u6e00\u6e01\u6e02\u6e04\u6e05\u6e06\u6e07\u6e08\u6e09\u6e0a\u6e0b\u6e0c\u6e0d\u6e0e\u6e0f\u6e10\u6e11\u6e12\u6e13\u6e14\u6e15\u6e16\u6e17\u6e1a\u6e74\u3cf4\u3cf5\u3cf6\u3cf7\u3cf8\u3cf9\u3cfa\u3cfb\u3cfc\u3cfd\u3cfe\u3cff\u3d00\u3d01\u3d02\u3d03\u3d04\u3d05\u3d06\u3d07\u3d08\u3d09\u3d0a\u3d0b\u3d0c\u3d0d\u3d0e\u6e03\u6e18\u6e19\u6e1b\u6e1c\u6e1d\u6e1e\u6e1f\u6e20\u6e21\u6e22\u6e23\u6e24\u6e25\u6e26\u6e27\u6e28\u6e29\u6e2a\u6e2b\u6e2c\u6e2d\u6e2e\u6e2f\u6e30\u6e31\u6e32\u6e33\u6e34\u6e35\u6e36\u6e37\u6e38\u6e39\u6e3a\u6e3b\u6e3c\u6e3d\u6e3e\u6e3f\u6e40\u6e41\u6e42\u6e43\u6e44\u6e45\u6e46\u6e47\u6e48\u6e49\u6e4a\u6e4b\u6e4c\u6e4d\u6e4e\u6e4f\u6e50\u6e51\u6e52\u6e53\u6e54\u6e55\u6e56\u6e57\u6e58\u6e59\u6e5a\u6e5b\u6e5c\u6e5d\u6e5e\u6e5f\u6e60\u6e61\u6e62\u6e63\u6e64\u6e65\u6e66\u6e67\u6e68\u6e69\u6e6a\u6e6b\u6e6c\u6e6d\u6e6e\u6e6f\u6e70\u6e71\u6e72\u6e73\u6e75\u6e76\u6e77\u6e78\u6e79\u6e7a\u6e7b\u6e7c\u6e7d\u6e7e\u6e7f\u6e80\u6e81\u6e82\u6e83\u6e84\u6e85\u6e86\u6e87\u6e88\u6e89\u6e8a\u6e8b\u6e8c\u6ecb\u6ede\u9fcc\u3d0f\u3d10\u3d11\u3d12\u3d13\u3d14\u3d15\u3d16\u3d17\u3d18\u3d19\u3d1a\u3d1b\u3d1c\u3d1d\u3d1e\u3d1f\u3d20\u3d21\u3d22\u3d23\u3d24\u6e8d\u6e8e\u6e8f\u6e90\u6e91\u6e92\u6e93\u6e94\u6e95\u6e96\u6e97\u6e98\u6e99\u6e9a\u6e9b\u6e9c\u6e9d\u6e9e\u6e9f\u6ea0\u6ea1\u6ea2\u6ea3\u6ea4\u6ea5\u6ea6\u6ea7\u6ea8\u6ea9\u6eaa\u6eab\u6eac\u6ead\u6eae\u6eaf\u6eb0\u6eb1\u6eb2\u6eb3\u6eb4\u6eb5\u6eb6\u6eb7\u6eb8\u6eb9\u6eba\u6ebb\u6ebc\u6ebd\u6ebe\u6ebf\u6ec0\u6ec1\u6ec2\u6ec3\u6ec4\u6ec5\u6ec6\u6ec7\u6ec8\u6ec9\u6eca\u6ecd\u6ece\u6ecf\u6ed0\u6ed1\u6ed2\u6ed3\u6ed4\u6ed5\u6ed6\u6ed7\u6ed8\u6ed9\u6edb\u6edc\u6edd\u6edf\u6ee0\u6ee1\u6ee2\u6ee3\u6ee4\u6ee5\u6ee6\u6ee7\u6ee8\u6ee9\u6eea\u6f13\u3d25\u3d26\u3d27\u3d28\u3d29\u3d2a\u3d2b\u3d2c\u3d2d\u3d2e\u3d2f\u3d30\u3d31\u3d32\u3d33\u3d34\u3d35\u3d36\u3d37\u3d38\u3d39\u3d3a\u3d3b\u6ecc\u6eda\u6eeb\u6eec\u6eed\u6eee\u6eef\u6ef0\u6ef1\u6ef2\u6ef3\u6ef4\u6ef5\u6ef6\u6ef7\u6ef8\u6ef9\u6efa\u6efb\u6efc\u6efd\u6efe\u6eff\u6f00\u6f01\u6f02\u6f03\u6f04\u6f05\u6f06\u6f07\u6f08\u6f09\u6f0a\u6f0c\u6f0d\u6f0e\u6f0f\u6f10\u6f11\u6f12\u6f14\u6f15\u6f16\u6f17\u6f18\u6f19\u6f1a\u6f1b\u6f1c\u6f1d\u6f1e\u6f1f\u6f20\u6f21\u6f22\u6f23\u6f24\u6f25\u6f26\u6f27\u6f28\u6f29\u6f2a\u6f2b\u6f2c\u6f2d\u6f2e\u6f2f\u6f30\u6f31\u6f32\u6f33\u6f34\u6f35\u6f36\u6f37\u6f38\u6f39\u6f3a\u6f3b\u6f3c\u6f3e\u6f3f\u6f40\u6f41\u6f42\u6f43\u6f44\u6f45\u6f46\u6f47\u6f48\u6f49\u6f4a\u6f4b\u6f4c\u6f4d\u3d3c\u3d3d\u3d3e\u3d3f\u3d40\u3d41\u3d42\u3d43\u3d44\u3d45\u3d46\u3d47\u3d48\u3d49\u6f0b\u6f3d\u6f4e\u6f4f\u6f50\u6f51\u6f52\u6f53\u6f54\u6f55\u6f56\u6f57\u6f58\u6f59\u6f5a\u6f5b\u6f5c\u6f5d\u6f5e\u6f5f\u6f60\u6f61\u6f62\u6f63\u6f64\u6f65\u6f66\u6f67\u6f68\u6f69\u6f6a\u6f6b\u6f6c\u6f6d\u6f6e\u6f6f\u6f70\u6f71\u6f72\u6f73\u6f74\u6f75\u6f76\u6f77\u6f78\u6f79\u6f7a\u6f7b\u6f7c\u6f7d\u6f7e\u6f7f\u6f81\u6f82\u6f83\u6f84\u6f85\u6f86\u6f87\u6f88\u6f89\u6f8a\u6f8b\u6f8c\u6f8d\u6f8e\u6f8f\u6f90\u6f91\u6f92\u6f93\u6f94\u6f95\u6f96\u6f97\u6f98\u6f9a\u6f9b\u6f9c\u6f9d\u6fd0\u9ff0\u3d4a\u3d4b\u3d4c\u3d4d\u3d4e\u3d4f\u3d50\u3d51\u3d52\u3d53\u3d54\u3d55\u3d56\u3d57\u3d58\u3d59\u3d5a\u3d5b\u3d5c\u6f99\u6f9e\u6f9f\u6fa0\u6fa1\u6fa2\u6fa3\u6fa4\u6fa5\u6fa6\u6fa7\u6fa8\u6fa9\u6faa\u6fab\u6fac\u6fad\u6fae\u6faf\u6fb0\u6fb1\u6fb2\u6fb3\u6fb4\u6fb5\u6fb6\u6fb7\u6fb8\u6fb9\u6fba\u6fbb\u6fbc\u6fbd\u6fbe\u6fbf\u6fc0\u6fc1\u6fc2\u6fc3\u6fc4\u6fc5\u6fc6\u6fc7\u6fc8\u6fc9\u6fca\u6fcb\u6fcc\u6fcd\u6fce\u6fcf\u6fd1\u6fd2\u6fd3\u6fd6\u3d5d\u3d5e\u3d5f\u3d60\u3d61\u3d62\u3d63\u3d64\u3d65\u3d66\u3d67\u3d68\u3d69\u3d6a\u3d6b\u3d6c\u3d6d\u3d6e\u6f80\u6fd4\u6fd5\u6fd7\u6fd8\u6fd9\u6fda\u6fdb\u6fdc\u6fdd\u6fde\u6fdf\u6fe0\u6fe1\u6fe2\u6fe3\u6fe4\u6fe5\u6fe6\u6fe7\u6fe8\u6fe9\u6fea\u6feb\u6fec\u6fed\u6fee\u6fef\u6ff0\u6ff1\u6ff2\u6ff4\u6ff5\u6ff6\u6ff7\u6ff8\u3d6f\u3d70\u3d71\u3d72\u3d73\u3d74\u3d75\u3d76\u3d77\u3d78\u3d79\u3d7a\u3d7b\u3d7c\u6ff9\u6ffa\u6ffb\u6ffc\u6ffd\u6ffe\u6fff\u7000\u7001\u7002\u7003\u7004\u7005\u7006\u7007\u7008\u7009\u700a\u700b\u700c\u700d\u700e\u700f\u7010\u7011\u7012\u7013\u7014\u3d7d\u3d7e\u3d7f\u3d80\u3d81\u3d82\u3d83\u6ff3\u7015\u7016\u7017\u7018\u7019\u701a\u701b\u701c\u701d\u701e\u701f\u7020\u7021\u7022\u7023\u7024\u7025\u7026\u7027\u7028\u7029\u702a\u702b\u702c\u702d\u702e\u3d84\u3d85\u3d86\u3d87\u3d88\u3d89\u3d8a\u3d8b\u702f\u7030\u7031\u7032\u7033\u7034\u7035\u7036\u7037\u7038\u7039\u703a\u703b\u703c\u703d\u703e\u703f\u7040\u7041\u3d8c\u3d8d\u3d8e\u3d8f\u3d90\u3d91\u7042\u7043\u7044\u7045\u7046\u7047\u7048\u7049\u704a\u704b\u704c\u704d\u704f\u7050\u3d92\u3d93\u3d94\u3d95\u3d96\u3d97\u3d98\u7051\u7052\u7053\u7054\u7055\u7056\u7057\u7058\u3d99\u3d9a\u7059\u705a\u705b\u705c\u3d9b\u3d9c\u3d9d\u705d\u705e\u705f\u7060\u7061\u3d9e\u3d9f\u7062\u7063\u704e\u7064\u7065\u7066\u3da0\u7067\u7068\u7069\u706a\u706b\u706c\u706d\u706e\u706f\u7070\u7071\u7072\u7073\u7087\u3da1\u3da2\u7074\u7075\u7076\u7077\u7078\u7079\u707a\u707b\u707c\u707d\u707e\u707f\u7080\u3da3\u3da4\u3da5\u7081\u7082\u7083\u7084\u7085\u7086\u7088\u7089\u708a\u708b\u708c\u708d\u708e\u708f\u7090\u7091\u7092\u7093\u7094\u7095\u7096\u7097\u7098\u7099\u709a\u709b\u709c\u709d\u709e\u3da6\u3da7\u3da8\u3da9\u3daa\u709f\u70a0\u70a1\u70a2\u70a3\u70a4\u70a5\u70a6\u70a7\u70a8\u70a9\u70aa\u70ab\u70ac\u70ad\u70ae\u70af\u70b0\u70b1\u70b2\u70b3\u70b4\u70b5\u70b6\u70b7\u70b8\u70b9\u70ba\u70bb\u70bc\u70bd\u70be\u70bf\u70c0\u70c1\u70c2\u70c3\u3dab\u3dac\u3dad\u3dae\u3daf\u3db0\u3db1\u3db2\u70c4\u70c5\u70c6\u70c7\u70c8\u70c9\u70ca\u70cb\u70cc\u70cd\u70ce\u70cf\u70d0\u70d1\u70d2\u70d3\u70d4\u70d5\u70d6\u70d7\u70d8\u70d9\u70da\u70db\u70dc\u70dd\u70de\u70df\u70e0\u70e1\u70e2\u70e3\u70e4\u70e5\u70e6\u70e7\u70e8\u70e9\u70ea\u70eb\u70ec\u70ed\u70ee\u3db3\u3db4\u3db5\u3db6\u3db7\u3db8\u70ef\u70f0\u70f1\u70f2\u70f3\u70f4\u70f5\u70f6\u70f7\u70f8\u70f9\u70fa\u70fb\u70fc\u70fd\u70fe\u70ff\u7100\u7101\u7102\u7103\u7104\u7105\u7106\u7107\u7108\u7109\u710a\u710b\u710c\u710d\u710e\u710f\u7110\u7111\u7112\u7113\u7115\u7116\u7117\u7118\u3db9\u3dba\u3dbb\u3dbc\u3dbd\u3dbe\u3dbf\u3dc0\u3dc1\u7114\u7119\u711a\u711b\u711c\u711d\u711e\u711f\u7120\u7121\u7122\u7123\u7124\u7125\u7126\u7127\u7128\u7129\u712a\u712b\u712c\u712d\u712e\u712f\u7130\u7131\u7132\u7133\u7134\u7135\u7136\u7137\u7138\u7139\u713a\u713b\u713c\u713d\u713e\u713f\u7140\u716e\u3dc2\u3dc3\u3dc4\u3dc5\u3dc6\u3dc7\u3dc8\u3dc9\u3dca\u3dcb\u3dcc\u3dcd\u7141\u7142\u7143\u7144\u7145\u7146\u7147\u7148\u7149\u714a\u714b\u714c\u714d\u714e\u714f\u7150\u7151\u7152\u7153\u7154\u7155\u7156\u7157\u7158\u7159\u715a\u715b\u715c\u715d\u715e\u715f\u7160\u7161\u7162\u7163\u7164\u7165\u7166\u7167\u7168\u7169\u716a\u716b\u716c\u716d\u716f\u7170\u7171\u7172\u7173\u7174\u7175\u7176\u7177\u7178\u717a\u3ba1\u3dce\u3dcf\u3dd0\u3dd1\u3dd2\u3dd3\u3dd4\u3dd5\u3dd6\u3dd7\u3dd8\u3dd9\u3dda\u3ddb\u3ddc\u3ddd\u3dde\u7179\u717b\u717c\u717d\u717e\u717f\u7180\u7181\u7182\u7183\u7184\u7185\u7186\u7187\u7188\u7189\u718a\u718b\u718c\u718d\u718e\u718f\u7190\u7191\u7192\u7193\u7194\u7195\u7196\u7197\u7198\u7199\u71a6\u84b8\u3ddf\u3de0\u3de1\u3de2\u3de3\u3de4\u3de5\u3de6\u3de7\u3de8\u3de9\u3dea\u719a\u719b\u719c\u719d\u719e\u719f\u71a0\u71a1\u71a2\u71a3\u71a4\u71a5\u71a7\u71a8\u71a9\u71aa\u71ab\u71ac\u71ad\u71ae\u71af\u71b0\u71b1\u71b2\u71b3\u71b4\u71b5\u9ed9\u9fa6\u9fbd\u3deb\u3dec\u3ded\u3dee\u3def\u3df0\u3df1\u3df2\u71b6\u71b7\u71b8\u71b9\u71ba\u71bb\u71bc\u71bd\u71be\u71bf\u71c0\u71c1\u71c2\u71c3\u71c4\u71c5\u71c6\u71c7\u71c8\u71c9\u71ca\u71cb\u71cc\u71cd\u71ce\u71cf\u71d0\u71d1\u71d2\u71d3\u71d4\u71d5\u71d6\u71d7\u71d8\u71d9\u71da\u71db\u71dc\u71dd\u71de\u3df3\u3df4\u3df5\u3df6\u3df7\u3df8\u3df9\u3dfa\u3dfb\u3dfc\u3dfd\u71df\u71e0\u71e1\u71e2\u71e3\u71e4\u71e5\u71e6\u71e7\u71e8\u71e9\u71ea\u71eb\u71ec\u71ed\u71ee\u71ef\u71f0\u71f1\u71f2\u71f3\u71f4\u71f5\u71f6\u71f7\u9fea\u3dfe\u3dff\u3e00\u3e01\u3e02\u3e03\u71f8\u71f9\u71fa\u71fb\u71fc\u71fd\u71fe\u71ff\u7200\u7201\u7202\u7203\u3e04\u3e05\u7204\u7205\u7206\u7207\u7208\u7209\u720a\u720c\u720d\u720e\u7215\u3e06\u3e07\u3e09\u720b\u720f\u7210\u7211\u7212\u7213\u7214\u7216\u7217\u7218\u3e08\u3e0a\u3e0b\u3e0c\u7219\u721a\u721b\u3e0d\u721c\u721d\u721e\u721f\u7220\u3e0e\u7221\u7222\u3e0f\u3e10\u7223\u7224\u7225\u7226\u3e11\u7227\u7228\u7229\u722a\u722b\u722c\u722d\u3e12\u3e13\u722e\u722f\u7230\u7231\u3e14\u7232\u7233\u3e15\u5655\u7234\u7235\u7236\u7237\u7238\u3e16\u7239\u3e17\u3e18\u723a\u3e19\u723b\u3e1a\u723c\u723d\u723e\u4e2c\u723f\u7240\u7241\u3e1b\u7242\u3e1c\u7243\u7244\u7245\u7246\u7247\u7248\u3e1d\u3e1e\u7249\u724a\u3e1f\u3e20\u3e21\u724b\u724c\u724d\u724e\u724f\u7250\u7251\u7252\u7253\u7254\u3e22\u7255\u7256\u7257\u3e23\u3e24\u7258\u3e25\u7259\u3e26\u3e27\u725a\u725b\u725c\u725d\u725e\u725f\u3e28\u7260\u7261\u7262\u7263\u7264\u3e29\u3e2a\u7265\u7266\u7267\u7268\u7269\u726a\u726b\u726c\u3e2b\u3e2c\u3e2d\u3e2e\u3e2f\u726d\u726e\u726f\u7270\u7271\u7272\u7273\u7274\u7275\u3e30\u3e31\u3e32\u3e33\u3e34\u7276\u7277\u7278\u7279\u727a\u3e35\u3e36\u3e37\u3e38\u727b\u727c\u727d\u727e\u727f\u7281\u3e39\u3e3a\u3e3b\u3e3c\u3e3d\u3e3e\u3e3f\u3e40\u7280\u7282\u7283\u7284\u7285\u7286\u7287\u7288\u7289\u728a\u728b\u3e41\u3e42\u3e43\u728c\u728d\u728e\u728f\u7290\u7291\u3e45\u3e46\u3e47\u3e48\u7292\u7293\u7294\u7295\u7296\u7297\u3e44\u3e49\u3e4a\u3e4b\u7298\u7299\u729a\u729b\u3e4c\u3e4d\u3e4e\u3e4f\u3e50\u729c\u729d\u729e\u729f\u3e51\u3e52\u3e53\u72a0\u3e54\u3e55\u3e56\u3e57\u3e58\u72a1\u72a2\u72a3\u72a4\u72a5\u72a6\u3e59\u72a7\u72a8\u3e5a\u72a9\u3e5b\u72aa\u72ab\u72ac\u72ad\u72ae\u72af\u72b0\u72b1\u72b2\u72b3\u72b4\u72b5\u72b6\u72b7\u72b8\u72b9\u72ba\u72bb\u72bc\u72bd\u72be\u72bf\u72c0\u72c1\u72c2\u72c3\u72c4\u72c5\u72c6\u72c7\u72c8\u3e5c\u3e5d\u3e5e\u3e5f\u3e60\u72c9\u72ca\u72cb\u72cc\u72cd\u72ce\u72cf\u72d0\u72d1\u72d2\u72d3\u72d4\u72d5\u72d6\u72d7\u72d8\u72d9\u72da\u72db\u72dc\u72dd\u72de\u3e61\u3e62\u3e63\u3e64\u3e65\u3e66\u3e67\u3e68\u3e69\u3e6a\u72df\u72e0\u72e1\u72e2\u72e3\u72e4\u72e5\u72e6\u72e7\u72e8\u72e9\u72ea\u72eb\u72ec\u72ed\u72ee\u72ef\u72f0\u72f1\u72f2\u3e6b\u3e6c\u3e6d\u3e6e\u3e6f\u3e70\u72f3\u72f4\u72f5\u72f6\u72f7\u72f8\u72f9\u72fa\u72fb\u72fc\u72fd\u72fe\u72ff\u7300\u7301\u7302\u7303\u3e71\u3e72\u3e73\u3e74\u3e75\u3e76\u3e77\u3e78\u7304\u7305\u7306\u7307\u7308\u7309\u730a\u730b\u730c\u730d\u730e\u730f\u7310\u7311\u7312\u7313\u7314\u7315\u7316\u7317\u7318\u7319\u731a\u731b\u731c\u731d\u731e\u731f\u7320\u7321\u732a\u732b\u3e79\u3e7a\u3e7b\u3e7c\u3e7d\u3e7e\u3e7f\u3e80\u7322\u7323\u7324\u7325\u7326\u7327\u7328\u7329\u732c\u732d\u732e\u732f\u7330\u7331\u7332\u7333\u7334\u7335\u7336\u7337\u7338\u7339\u7341\u3e81\u3e82\u3e83\u3e84\u3e85\u3e86\u733a\u733b\u733c\u733d\u733e\u733f\u7340\u7342\u7343\u7345\u7346\u7347\u7348\u7349\u734a\u3e87\u3e88\u3e89\u3e8a\u3e8b\u3e8c\u3e8d\u7344\u734c\u734d\u734e\u734f\u7350\u7351\u7352\u7353\u7354\u7355\u3e8e\u3e8f\u3e90\u3e91\u3e92\u3e93\u734b\u7356\u7357\u7358\u7359\u735a\u735b\u735c\u735d\u735e\u735f\u7360\u7361\u7362\u7363\u7364\u3e94\u3e95\u3e96\u3e97\u3e98\u3e99\u7365\u7366\u7367\u7368\u7369\u736a\u736b\u736c\u736d\u3e9a\u3e9b\u3e9c\u736e\u736f\u7370\u7371\u7372\u7373\u7374\u3e9d\u3e9e\u3e9f\u7375\u7376\u7377\u7378\u3ea0\u7379\u737a\u737b\u3ea1\u3ea2\u737c\u737d\u3ea3\u3ea4\u3ea5\u3ea6\u737e\u737f\u7380\u7381\u7382\u7383\u3ea7\u7384\u7385\u7386\u7387\u7388\u7389\u738b\u738a\u738c\u738d\u738e\u738f\u7390\u7391\u3ea8\u3ea9\u3eaa\u3eab\u7392\u7393\u7394\u7395\u7396\u7397\u7398\u7399\u739a\u739b\u3eac\u3ead\u3eae\u3eaf\u739c\u739d\u739e\u739f\u73a0\u73a1\u73a2\u73a3\u73a4\u73a5\u73a6\u73a7\u73a8\u73a9\u73aa\u73ab\u73ac\u73ad\u73ae\u73af\u73b0\u73b1\u9ff1\u3eb0\u3eb1\u3eb2\u3eb3\u3eb4\u3eb5\u3eb6\u73b2\u73b3\u73b4\u73b5\u73b6\u73b7\u73b8\u73b9\u73ba\u73bb\u73bc\u73bd\u73be\u73bf\u73c0\u73c1\u73c2\u73c3\u73c4\u73c5\u73c6\u73c7\u73c8\u73c9\u73ca\u73cb\u73cc\u73cd\u73ce\u73cf\u73d0\u73d1\u3eb7\u3eb8\u3eb9\u3eba\u3ebb\u3ebc\u3ebd\u3ebe\u73d2\u73d3\u73d4\u73d5\u73d6\u73d7\u73d8\u73d9\u73da\u73db\u73dc\u73dd\u73de\u73df\u73e0\u73e1\u73e2\u73e3\u73e4\u73e5\u73e6\u73e7\u73e8\u73e9\u73ea\u73eb\u73ec\u73ed\u73ee\u73ef\u73f0\u73f1\u73f2\u7409\u3ebf\u3ec0\u3ec1\u3ec2\u3ec3\u3ec4\u3ec5\u3ec6\u3ec7\u3ec8\u73f3\u73f4\u73f5\u73f6\u73f8\u73f9\u73fa\u73fb\u73fc\u73fd\u73fe\u73ff\u7400\u7401\u7402\u7403\u7404\u7405\u7406\u7407\u7408\u740a\u740b\u740c\u740d\u740e\u740f\u7410\u7411\u7412\u7413\u3ec9\u3eca\u3ecb\u3ecc\u3ecd\u3ece\u3ecf\u3ed0\u73f7\u7414\u7415\u7416\u7417\u7418\u7419\u741a\u741b\u741c\u741d\u741f\u7420\u7421\u7422\u7423\u7424\u7425\u7426\u7427\u7428\u7429\u742a\u742b\u742c\u742d\u742e\u742f\u7430\u7431\u7432\u7433\u7434\u7435\u7436\u7437\u7438\u7439\u743a\u743b\u743c\u3ed1\u3ed2\u3ed3\u3ed4\u3ed5\u3ed6\u3ed7\u3ed8\u3ed9\u3eda\u3edb\u3edc\u741e\u743d\u743e\u743f\u7440\u7441\u7442\u7443\u7444\u7445\u7446\u7447\u7448\u7449\u744a\u744b\u744c\u744d\u744e\u744f\u7450\u7451\u7452\u7453\u7454\u7455\u7456\u7457\u7458\u7459\u745a\u745b\u745c\u745d\u745e\u745f\u3edd\u3ede\u3edf\u3ee0\u3ee1\u3ee2\u3ee3\u3ee4\u3ee5\u3ee6\u7460\u7461\u7462\u7463\u7464\u7465\u7466\u7467\u7468\u7469\u746a\u746b\u746c\u746d\u746e\u746f\u7470\u7471\u7472\u7473\u7474\u7475\u7476\u7477\u7478\u3ee7\u3ee8\u3ee9\u3eea\u7479\u747a\u747b\u747c\u747d\u747e\u747f\u7480\u7481\u7482\u7483\u7484\u7485\u7486\u7487\u7488\u7489\u748a\u748b\u748c\u748e\u7493\u3eeb\u3eec\u3eed\u3eee\u3eef\u3ef0\u3ef1\u3ef2\u3ef3\u3ef4\u748d\u748f\u7490\u7491\u7492\u7494\u7495\u7496\u7497\u7498\u7499\u749a\u749b\u749c\u749d\u749e\u749f\u74a0\u74a1\u74a3\u74a4\u3ef5\u3ef6\u3ef7\u3ef8\u3ef9\u3efa\u3efb\u3efc\u74a2\u74a5\u74a6\u74a7\u74a8\u74a9\u74aa\u74ab\u74ac\u74ad\u74ae\u74af\u74b0\u74b1\u74b2\u74b3\u74b4\u3efd\u3efe\u3eff\u3f00\u3f01\u3f02\u74b5\u74b6\u74b7\u74b8\u74b9\u74ba\u74bb\u74bc\u74bd\u74be\u74bf\u74c0\u74c1\u74c2\u74c3\u74c4\u74c5\u74c6\u74c7\u74c8\u74c9\u74ca\u74cb\u3f03\u3f04\u3f05\u3f06\u74cc\u74cd\u74ce\u74cf\u74d0\u74d1\u74d2\u3f07\u74d3\u74d4\u74d5\u74d6\u74d7\u74d8\u74d9\u74da\u3f08\u74db\u74dc\u74dd\u3f09\u3f0a\u74de\u74df\u3f0b\u3f0c\u74e0\u3f0d\u3f0e\u3f0f\u74e1\u3f10\u3f11\u3f12\u3f13\u3f14\u74e2\u3f15\u74e3\u74e4\u74e5\u3f16\u74e6\u74e7\u3f17\u74e8\u74e9\u3f18\u3f19\u74ea\u74eb\u74ec\u74ed\u74ee\u74ef\u74f0\u74f1\u74f2\u3f1a\u3f1b\u74f3\u74f4\u74f5\u3f1c\u3f1d\u3f1e\u3f1f\u3f20\u74f6\u74f7\u74f8\u3f21\u3f22\u3f23\u3f24\u3f25\u3f26\u74f9\u74fa\u74fb\u74fc\u3f27\u3f28\u3f29\u3f2a\u74fd\u74fe\u74ff\u7500\u7501\u3f2b\u3f2c\u3f2d\u3f2e\u3f2f\u3f30\u3f31\u7502\u7503\u7504\u7505\u7506\u3f32\u3f33\u3f34\u3f35\u3f36\u3f37\u7507\u7508\u7509\u3f38\u3f39\u3f3a\u750a\u750b\u750c\u750d\u750e\u3f3b\u3f3c\u3f3d\u3f3e\u750f\u7510\u7511\u7512\u3f3f\u3f40\u3f41\u3f42\u3f43\u3f44\u3f45\u7513\u7514\u7515\u3f46\u3f47\u7516\u3f48\u3f49\u7517\u3f4a\u3f4b\u3f4c\u7518\u7519\u751a\u3f4d\u751b\u751c\u751d\u751e\u3f4e\u3f4f\u3f50\u3f51\u751f\u7520\u7521\u7522\u7523\u7524\u7525\u7526\u3f52\u7527\u3f53\u3f54\u7528\u7529\u752a\u752b\u752c\u752d\u752e\u752f\u7530\u7531\u7532\u7533\u7534\u7535\u7536\u7537\u7538\u7539\u753a\u753c\u3f55\u3f56\u3f57\u753b\u753d\u753e\u753f\u7540\u7541\u7542\u7543\u7544\u7545\u7546\u7547\u7548\u7549\u754a\u754b\u754c\u754d\u754e\u754f\u7550\u7551\u7552\u7553\u3f58\u3f59\u3f5a\u7554\u7555\u7556\u7557\u7558\u7559\u755a\u755b\u755c\u755d\u755e\u755f\u3f5b\u3f5c\u7561\u7562\u7563\u7564\u7565\u7566\u7567\u7569\u7570\u3f5d\u3f5e\u756a\u756b\u756c\u756d\u756e\u756f\u7572\u7573\u7574\u3f5f\u3f60\u7575\u7576\u7577\u7578\u7579\u757a\u3f61\u3f62\u3f63\u3f64\u7571\u757b\u757c\u757d\u3f65\u757e\u757f\u3f66\u3f67\u3f68\u7580\u7581\u7582\u3f69\u7583\u7584\u3f6a\u7585\u3f6b\u3f6c\u7586\u7587\u3f6d\u7588\u3f6e\u7589\u758a\u3f6f\u758b\u758c\u758d\u758e\u758f\u3f70\u7590\u7591\u7592\u7593\u7594\u7595\u7596\u7597\u3f71\u3f72\u7598\u7599\u759a\u759b\u759c\u759d\u759e\u759f\u75a0\u3f73\u3f74\u3f75\u3f76\u75a1\u75a2\u75a3\u75a4\u75a5\u75a6\u75a7\u75a8\u75a9\u75aa\u75ab\u75ac\u75ad\u75ae\u75af\u75ba\u3f77\u3f78\u3f79\u3f7a\u3f7b\u3f7c\u75b0\u75b1\u75b2\u75b3\u75b4\u75b5\u75b6\u75b7\u75b8\u75b9\u75bb\u75bc\u75bd\u75be\u75bf\u75c0\u75c1\u75c2\u75c3\u75c4\u75c5\u75c6\u75c7\u75c8\u75c9\u3f7d\u3f7e\u3f7f\u3f80\u3f81\u3f82\u3f83\u3f84\u3f85\u3f86\u3f87\u3f88\u3f89\u75ca\u75cb\u75cc\u75cd\u75ce\u75cf\u75d0\u75d1\u75d2\u75d3\u75d4\u75d5\u75d6\u3f8a\u3f8b\u3f8c\u3f8d\u3f8e\u3f8f\u3f90\u3f91\u3f92\u75d7\u75d8\u75d9\u75da\u75db\u75dc\u75dd\u75de\u75df\u75e0\u75e1\u75e2\u75e3\u75e4\u75e5\u75e6\u75e7\u75e8\u75e9\u75ea\u75eb\u3f93\u3f94\u3f95\u3f96\u3f97\u3f98\u3f99\u3f9a\u3f9b\u3f9c\u3f9d\u3f9e\u3f9f\u3fa0\u3fa1\u75ec\u75ed\u75ee\u75ef\u75f0\u75f1\u75f2\u75f3\u75f4\u75f5\u75f6\u75f7\u75f8\u75f9\u75fa\u75fb\u75fc\u75fd\u75fe\u75ff\u7600\u7601\u7602\u7603\u7604\u7605\u7606\u3fa2\u3fa3\u3fa4\u3fa5\u3fa6\u3fa7\u3fa8\u3fa9\u7607\u7608\u7609\u760a\u760b\u760c\u760d\u760e\u760f\u7610\u7611\u7612\u7613\u7614\u7615\u7616\u7617\u7618\u761f\u7627\u3faa\u3fab\u3fac\u3fad\u3fae\u3faf\u3fb0\u3fb1\u3fb2\u3fb3\u3fb4\u3fb5\u7619\u761a\u761b\u761c\u761d\u761e\u7620\u7621\u7622\u7623\u7624\u7625\u7626\u7628\u7629\u762a\u762b\u3fb6\u3fb7\u3fb8\u3fb9\u3fba\u3fbb\u3fbc\u3fbd\u3fbe\u3fbf\u3fc0\u3fc1\u762c\u762d\u762e\u762f\u7630\u7631\u7632\u7633\u7634\u7635\u7636\u7637\u7638\u7639\u763a\u763b\u763c\u763d\u763e\u763f\u764a\u3fc2\u3fc3\u3fc4\u3fc5\u3fc6\u3fc7\u3fc8\u7640\u7641\u7642\u7643\u7644\u7645\u7646\u7647\u7648\u7649\u764b\u764c\u764d\u764e\u3fc9\u3fca\u764f\u7650\u7651\u7652\u7653\u7654\u7655\u7656\u7657\u7658\u7659\u765a\u765b\u765c\u765d\u765e\u3fcb\u3fcc\u3fcd\u3fce\u3fcf\u3fd0\u765f\u7660\u7661\u7663\u3fd1\u3fd2\u3fd3\u7662\u7664\u7665\u7666\u3fd4\u7667\u7668\u7669\u766a\u766b\u3fd5\u3fd6\u3fd7\u766c\u766d\u766e\u3fd8\u766f\u7670\u3fd9\u7671\u7672\u3fda\u3fdb\u7673\u7674\u3fdc\u7675\u7676\u7677\u7678\u7679\u767a\u767b\u767c\u767d\u767e\u767f\u7680\u7681\u7682\u7683\u3fdd\u7684\u7685\u7686\u7687\u7688\u3fde\u7560\u7689\u768a\u768b\u768c\u768d\u3fdf\u768e\u768f\u7690\u7691\u3fe0\u3fe1\u7692\u7693\u7694\u7695\u7696\u7697\u7698\u7699\u3fe2\u3fe3\u3fe4\u769a\u769b\u769c\u769d\u769e\u3fe5\u769f\u76a0\u76a1\u3fe6\u76a2\u76a3\u76a4\u76a5\u76a6\u76a7\u76a8\u76a9\u3fe7\u76aa\u76ab\u76ac\u3fe8\u76ad\u3fe9\u76ae\u76af\u3fea\u3feb\u3fec\u76b0\u76b1\u3fed\u3fee\u76b2\u3fef\u3ff0\u76b3\u76b4\u3ff1\u76b5\u9fbe\u3ff2\u3ff3\u76b6\u76b7\u76b8\u76b9\u3ff4\u3ff5\u76ba\u3ff6\u3ff7\u76bb\u3ff8\u76bc\u76bd\u3ff9\u76be\u3ffa\u76bf\u76c0\u76c1\u76c2\u3ffb\u76c3\u76c4\u76c5\u76c6\u76c7\u76c8\u3ffc\u3ffd\u76c9\u76ca\u76cb\u76cc\u76cd\u76ce\u76cf\u76d0\u76d1\u3ffe\u3fff\u4000\u76d2\u76d3\u76d4\u76d5\u76d6\u76d7\u76d8\u76db\u4001\u4002\u76d9\u76da\u76dc\u4003\u76dd\u76de\u76df\u4004\u4005\u76e0\u76e1\u76e2\u76e3\u4006\u76e4\u76e5\u76e6\u76e7\u4007\u4008\u76e8\u76e9\u76ea\u4009\u76eb\u76ec\u400a\u76ed\u400b\u400c\u400d\u76ee\u76ef\u400e\u400f\u76f0\u76f1\u76f2\u76f3\u76f4\u76f5\u4010\u4011\u4012\u4013\u4014\u76f6\u76f7\u76f8\u76f9\u76fa\u76fb\u76fc\u76fd\u76fe\u76ff\u7700\u7701\u7702\u7703\u7704\u7705\u7706\u7707\u7708\u7709\u770a\u770b\u770c\u770d\u4015\u4016\u4017\u4018\u4019\u401a\u401b\u401c\u401d\u401e\u770e\u770f\u7710\u7711\u7712\u7713\u7714\u7715\u7716\u7717\u7718\u7719\u771a\u771b\u771c\u771d\u771e\u771f\u7720\u7721\u7722\u7723\u7724\u7725\u7726\u7727\u7728\u7729\u772a\u772b\u772c\u773f\u401f\u4020\u4021\u4022\u4023\u4024\u4025\u4026\u772d\u772e\u772f\u7730\u7731\u7732\u7733\u7734\u7735\u7736\u7737\u7738\u7739\u773a\u773b\u773c\u773d\u773e\u7741\u4027\u4028\u4029\u402a\u402b\u402c\u402d\u402e\u7740\u7742\u7743\u7744\u7745\u7746\u7747\u7748\u7749\u774a\u774b\u774c\u774d\u774e\u774f\u7750\u7751\u9fc3\u402f\u4030\u4031\u4032\u4033\u4034\u4035\u4036\u4037\u4038\u4039\u403a\u403b\u403c\u403d\u403e\u403f\u4040\u7752\u7753\u7754\u7755\u7756\u7757\u7758\u7759\u775a\u775b\u775c\u775d\u775e\u775f\u7760\u7762\u7763\u7764\u7765\u7766\u7767\u7768\u7769\u776a\u776b\u776c\u776d\u4041\u4042\u4043\u4044\u4045\u4046\u4047\u4048\u4049\u7761\u776e\u776f\u7770\u7771\u7772\u7773\u7774\u7775\u7776\u7777\u7778\u7779\u777a\u777b\u777c\u777d\u777e\u777f\u7780\u7781\u7782\u7783\u7784\u7785\u7786\u404a\u404b\u404c\u404d\u404e\u404f\u4050\u4051\u4052\u4053\u4054\u4055\u4056\u7787\u7788\u7789\u778a\u778b\u778c\u778d\u778e\u778f\u7790\u7791\u7792\u7793\u4057\u4058\u4059\u405a\u405b\u405c\u405d\u405e\u405f\u4060\u4061\u7794\u7795\u7796\u7797\u7798\u7799\u779a\u779b\u779c\u779d\u779e\u779f\u77a0\u77a1\u77a2\u77a3\u4062\u4063\u4064\u4065\u4066\u4067\u4068\u4069\u406a\u77a4\u77a5\u77a6\u77a7\u77a8\u77a9\u77aa\u77ab\u77ac\u77ad\u77ae\u77af\u77b0\u77b1\u77b2\u77b3\u77b4\u77b5\u77b6\u77b7\u406b\u406c\u406d\u406e\u406f\u4070\u4071\u4072\u4073\u77b8\u77b9\u77ba\u77bb\u77bc\u77bd\u77be\u77bf\u77c0\u77c1\u77c2\u4074\u4075\u4076\u4077\u4078\u4079\u407a\u77c3\u77c4\u77c5\u77c6\u77c7\u77c8\u77c9\u77ca\u77cb\u77cc\u77cd\u77ce\u77cf\u407b\u407c\u407d\u407e\u77d0\u77d1\u77d2\u77d3\u407f\u4080\u4081\u77d4\u4082\u4083\u77d5\u77d7\u77d8\u77d9\u4084\u77d6\u77da\u4085\u77db\u4086\u77dc\u4087\u77dd\u4088\u77de\u77df\u77e0\u4089\u408a\u408b\u408c\u408d\u408e\u77e1\u77e2\u77e3\u77e4\u77e5\u77e6\u77e7\u77e8\u77e9\u408f\u4090\u77ea\u77eb\u4091\u4092\u77ec\u77ed\u4093\u77ee\u4094\u4095\u77ef\u77f0\u77f1\u77f2\u77f3\u4096\u77f4\u77f5\u77f6\u4097\u77f7\u77f8\u77f9\u77fa\u77fb\u77fc\u77fd\u77fe\u77ff\u7800\u7801\u4098\u4099\u6cf5\u7802\u7803\u7804\u7805\u7806\u7807\u7808\u7809\u780a\u780b\u780c\u780d\u780e\u780f\u7810\u7811\u7812\u7813\u7814\u7815\u7816\u7817\u7818\u7819\u781a\u781b\u781c\u409a\u409b\u409c\u409d\u409e\u781d\u781e\u781f\u7820\u7821\u7822\u7823\u7824\u7825\u7826\u7827\u7828\u7829\u782a\u782b\u782c\u782d\u782e\u782f\u7830\u7831\u7832\u7833\u7834\u7835\u7836\u7837\u7838\u7839\u783a\u783b\u783c\u783d\u783e\u783f\u7840\u7841\u9fec\u409f\u40a0\u40a1\u40a2\u40a3\u40a4\u40a5\u40a6\u40a7\u40a8\u7842\u7843\u7844\u7845\u7846\u7847\u7848\u7849\u784a\u784b\u784c\u784d\u784e\u784f\u7850\u7851\u7852\u7853\u7854\u7855\u7856\u7857\u7858\u7859\u785a\u785b\u9fce\u40a9\u40aa\u40ab\u40ac\u40ad\u40ae\u40af\u785c\u785d\u785e\u785f\u7860\u7861\u7862\u7863\u7864\u7865\u7866\u7867\u7868\u7869\u786a\u786b\u786c\u786d\u786e\u786f\u7870\u7871\u7872\u7873\u7874\u7875\u7876\u7877\u40b0\u40b1\u40b2\u40b3\u40b4\u40b5\u40b6\u7878\u7879\u787a\u787b\u787c\u787d\u787f\u7880\u7881\u7882\u7883\u7884\u7885\u7886\u7887\u7888\u7889\u788a\u788b\u788c\u788d\u788e\u788f\u7890\u7891\u7892\u7893\u7894\u7895\u7896\u7897\u7898\u7899\u789a\u789b\u789c\u78b0\u40b7\u40b8\u40b9\u40ba\u40bb\u40bc\u40bd\u40be\u40bf\u40c0\u40c1\u40c2\u40c3\u40c4\u40c5\u40c6\u40c7\u787e\u789d\u789e\u789f\u78a0\u78a1\u78a2\u78a3\u78a4\u78a5\u78a6\u78a7\u78a8\u78a9\u78aa\u78ab\u78ac\u78ad\u78ae\u78af\u78b1\u78b2\u78b3\u78b4\u78b5\u78b6\u78b7\u78b8\u78b9\u78c1\u40c8\u40c9\u40ca\u40cb\u40cc\u40cd\u40ce\u40cf\u40d0\u78ba\u78bb\u78bc\u78bd\u78be\u78bf\u78c0\u78c2\u78c3\u78c4\u78c5\u78c6\u78c7\u78c8\u78c9\u78ca\u78cb\u78cc\u78cd\u78ce\u78cf\u78d0\u78d1\u78d2\u78d3\u78d4\u78d5\u78d6\u78d7\u78d8\u78d9\u78dc\u78e4\u40d1\u40d2\u40d3\u40d4\u40d5\u40d6\u40d7\u78da\u78db\u78dd\u78de\u78df\u78e0\u78e1\u78e2\u78e3\u78e5\u78e6\u78e7\u78e8\u78e9\u78ea\u78eb\u78ec\u78ed\u78ee\u40d8\u40d9\u40da\u40db\u40dc\u40dd\u40de\u40e0\u78ef\u78f0\u78f1\u78f2\u78f3\u78f4\u78f5\u78f6\u78f7\u78f8\u78f9\u78fa\u78fb\u78fc\u78fd\u78fe\u78ff\u7900\u7901\u7902\u7903\u7904\u7905\u40df\u40e1\u40e2\u40e3\u40e4\u40e5\u40e6\u40e7\u40e8\u7906\u7907\u7908\u7909\u790a\u790b\u790c\u790d\u790e\u790f\u7910\u7911\u7912\u7913\u7914\u7915\u7916\u40e9\u40ea\u40eb\u40ec\u40ed\u40ee\u40ef\u7917\u7918\u7919\u791a\u791b\u791c\u791d\u791e\u791f\u7920\u7921\u40f0\u7922\u7923\u7924\u7925\u7926\u7927\u7928\u7929\u792a\u792b\u792c\u40f1\u40f2\u40f3\u792d\u792e\u792f\u7930\u7931\u7932\u7933\u7934\u40f4\u40f5\u40f6\u40f7\u7935\u40f8\u40f9\u7936\u7937\u7938\u40fa\u7939\u40fb\u793a\u793b\u793c\u793d\u40fc\u793e\u793f\u7940\u7941\u7942\u7943\u7944\u7945\u7946\u7947\u7948\u7949\u794a\u794b\u794c\u794d\u794e\u89c6\u9fc6\u40fd\u40fe\u40ff\u4100\u794f\u7950\u7951\u7952\u7953\u7954\u7955\u7956\u7957\u7958\u7959\u795a\u795b\u795c\u795d\u795e\u795f\u7960\u7961\u7962\u4101\u4102\u4103\u7963\u7964\u7965\u7967\u7968\u7969\u796a\u796b\u796c\u796d\u796e\u796f\u4104\u4105\u4106\u7966\u7970\u7971\u7972\u7973\u7974\u7975\u7976\u7977\u7978\u7984\u8996\u4107\u4108\u4109\u410a\u7979\u797a\u797b\u797c\u797d\u797e\u797f\u7980\u7981\u7982\u7983\u7985\u7986\u410b\u410c\u410d\u410e\u410f\u4110\u4111\u4112\u7987\u7988\u7989\u798a\u798b\u798c\u798d\u798e\u798f\u7990\u7991\u7992\u7993\u7994\u7995\u7996\u7997\u7998\u7999\u4113\u4114\u4115\u4116\u4117\u799a\u799b\u799c\u799d\u799e\u799f\u79a0\u79a1\u79a2\u79a3\u4118\u4119\u79a4\u79a5\u79a6\u411a\u411b\u411c\u411d\u411e\u79a7\u79a8\u79a9\u79aa\u79ab\u411f\u79ac\u79ad\u79ae\u79af\u9fc5\u4120\u4121\u79b0\u79b1\u4122\u79b2\u4123\u4124\u79b3\u79b4\u79b5\u79b6\u79b7\u4125\u79b8\u79b9\u79ba\u79bb\u79bc\u79bd\u842c\u79be\u79bf\u79c0\u79c1\u79c2\u79c3\u4126\u4127\u79c4\u79c5\u79c6\u79c7\u79c8\u79c9\u79ca\u4128\u4129\u412a\u412b\u412c\u412d\u79cb\u79cc\u79cd\u79ce\u79cf\u79d0\u79d1\u79d2\u79d3\u79d4\u79d5\u79d6\u79d7\u412e\u412f\u4130\u4131\u4132\u4133\u4134\u4135\u79d8\u79d9\u79da\u79db\u79dc\u79dd\u79de\u79df\u79e0\u79e1\u79e2\u79e3\u79e4\u79e5\u79e6\u79e7\u79e8\u79e9\u79ea\u79eb\u79ec\u79ed\u79ee\u79ef\u79f0\u4136\u4137\u4138\u4139\u79f1\u79f2\u79f3\u79f4\u79f5\u79f6\u79f7\u79f8\u79f9\u79fa\u79fb\u79fc\u79fd\u79fe\u7a06\u413a\u413b\u413c\u413d\u413e\u413f\u4140\u4141\u4142\u4143\u4144\u4145\u4146\u4147\u4148\u4149\u414a\u79ff\u7a00\u7a01\u7a02\u7a03\u7a04\u7a05\u7a07\u7a08\u7a09\u7a0a\u7a0b\u7a0c\u7a0d\u7a0e\u9fdf\u414b\u414c\u414d\u414e\u414f\u4150\u4151\u4152\u4153\u7a0f\u7a10\u7a11\u7a12\u7a13\u7a14\u7a15\u7a16\u7a17\u7a18\u7a19\u7a1a\u7a1b\u7a1c\u7a1d\u7a1e\u7a1f\u7a20\u7a21\u7a22\u7a23\u7a24\u7a25\u4154\u4155\u4156\u4157\u4158\u4159\u415a\u415b\u415c\u415d\u415e\u415f\u7a26\u7a27\u7a28\u7a29\u7a2a\u7a2b\u7a2c\u7a2d\u7a2e\u7a2f\u7a30\u7a31\u7a32\u7a33\u7a4a\u4160\u4161\u4162\u4163\u4164\u4165\u4166\u4167\u4168\u4169\u416a\u416b\u7a34\u7a35\u7a36\u7a37\u7a38\u7a39\u7a3a\u7a3b\u7a3c\u7a3d\u7a3e\u7a3f\u7a40\u7a41\u7a42\u7a43\u416c\u416d\u416e\u416f\u4170\u4171\u4172\u4173\u4174\u4175\u4176\u7a44\u7a45\u7a46\u7a47\u7a48\u7a4b\u7a4c\u7a4d\u7a4e\u7a4f\u7a50\u7a51\u7a52\u4177\u4178\u4179\u417a\u417b\u417c\u417d\u7a49\u7a53\u7a54\u7a55\u7a56\u7a57\u7a58\u7a59\u7a5a\u7a5b\u7a5c\u7a5d\u7a5e\u417e\u417f\u4180\u7a5f\u7a60\u7a61\u7a62\u7a63\u4181\u4182\u4183\u4184\u4185\u4186\u4187\u7a64\u7a65\u7a66\u7a67\u7a68\u7a69\u7a6a\u7a6b\u7a6c\u7a6d\u7a6e\u7a6f\u4188\u4189\u9fe0\u418a\u418b\u418c\u418d\u7a70\u7a73\u418e\u418f\u7a71\u7a72\u4190\u7a74\u7a75\u7a76\u7a77\u4191\u7a78\u7a79\u7a7a\u7a7b\u4192\u7a7c\u7a7d\u7a7e\u7a7f\u7a80\u7a81\u7a82\u7a83\u4193\u4194\u4195\u4196\u7a84\u7a85\u7a86\u7a87\u7a88\u7a89\u7a8a\u7a8b\u7a8c\u7a8d\u7a8e\u4197\u4198\u4199\u7a8f\u7a90\u7a91\u7a92\u7a93\u7a94\u7a95\u419a\u419b\u419c\u419d\u419e\u419f\u41a0\u7a96\u7a97\u7a98\u7a99\u7a9a\u7a9b\u7a9c\u7a9d\u41a1\u41a2\u41a3\u41a4\u41a5\u7a9e\u7a9f\u7aa0\u7aa1\u7aa2\u7aa3\u7aa4\u7aa5\u7aa6\u7aa7\u41a6\u41a7\u41a8\u41a9\u7aa8\u7aa9\u7aaa\u7aab\u7aac\u7aad\u41aa\u41ab\u7aae\u7aaf\u7ab0\u7ab1\u7ab2\u7ab3\u7ab4\u41ac\u41ad\u41ae\u7ab5\u7ab6\u7ab7\u7ab8\u7ab9\u7aba\u7abb\u7abc\u7abd\u41af\u41b0\u41b1\u41b2\u41b3\u41b4\u7abe\u7abf\u7ac0\u7ac1\u7ac2\u7ac3\u41b5\u41b6\u41b7\u41b8\u41b9\u41ba\u41bb\u7ac4\u7ac5\u41bc\u41bd\u7ac6\u41be\u41bf\u7ac7\u41c0\u7ac8\u7ac9\u7aca\u41c1\u7acb\u41c2\u7acc\u7acd\u7ace\u7acf\u41c3\u41c4\u7ad0\u7ad1\u7ad2\u7ad3\u7ad4\u7ad5\u7ad6\u7ad7\u41c5\u41c6\u7ad8\u7ad9\u7ada\u7adb\u7adc\u7add\u7ade\u41c7\u41c8\u41c9\u41ca\u7ae1\u41cb\u7ae2\u7ae3\u7ae4\u7ae5\u7ae6\u7ae7\u41cc\u41cd\u7ae8\u7ae9\u7aea\u7aeb\u41ce\u41cf\u41d0\u41d1\u7aec\u7aed\u7aef\u7af0\u7aee\u7af1\u41d2\u7af2\u7af3\u7af4\u41d3\u7af5\u41d4\u41d5\u7af6\u7af7\u7af8\u7af9\u7afa\u7afb\u7afc\u7afd\u7afe\u7aff\u7b00\u7b01\u7b02\u7b03\u41d6\u7b05\u7b06\u7b07\u7b08\u7b09\u7b0a\u7b0b\u7b0c\u7b0d\u7b0e\u7b0f\u7b10\u7b11\u7b12\u7b13\u7b14\u7b15\u41d7\u41d8\u41d9\u41da\u41db\u41dc\u41dd\u7b16\u7b17\u7b18\u7b19\u7b1a\u7b1b\u7b1c\u7b1d\u7b1e\u7b1f\u7b20\u7b21\u7b22\u7b23\u7b24\u7b25\u7b26\u7b27\u7b28\u7b29\u7b2a\u7b2b\u7b2c\u7b2d\u7b2e\u7b2f\u7b30\u7b31\u7b32\u7b33\u7b34\u7b35\u7b36\u7b37\u7b38\u7b39\u7b3a\u7b3b\u7b3c\u7b3d\u7b3e\u41de\u41df\u41e0\u41e1\u41e2\u41e3\u41e4\u41e5\u41e6\u7b04\u7b3f\u7b40\u7b41\u7b42\u7b43\u7b44\u7b45\u7b46\u7b47\u7b48\u7b49\u7b4a\u7b4b\u7b4c\u7b4d\u7b4e\u7b4f\u7b50\u7b51\u7b52\u7b53\u7b54\u7b55\u7b56\u7b57\u7b58\u7b59\u7b5a\u7b5b\u7b5c\u7b5d\u41e7\u41e8\u41e9\u41ea\u41eb\u41ec\u41ed\u41ee\u41ef\u41f0\u41f1\u41f2\u41f3\u41f4\u7b5e\u7b5f\u7b60\u7b61\u7b62\u7b63\u7b64\u7b65\u7b66\u7b67\u7b68\u7b69\u7b6a\u7b6b\u7b6c\u7b6d\u7b6e\u7b6f\u7b70\u7b71\u7b72\u7b73\u7b74\u7b75\u7b76\u7b77\u7b78\u7b79\u7b7a\u7b7b\u7b7c\u7b7d\u7b7e\u7b7f\u7b80\u7bc0\u41f5\u41f6\u41f7\u41f8\u41f9\u41fa\u41fb\u41fc\u41fd\u41fe\u41ff\u4200\u7b81\u7b82\u7b83\u7b84\u7b85\u7b86\u7b87\u7b88\u7b89\u7b8a\u7b8b\u7b8c\u7b8d\u7b8e\u7b8f\u7b90\u7b91\u7b92\u7b93\u7b94\u7b95\u7b96\u7b97\u7b98\u7b99\u7b9a\u7b9b\u7b9c\u7b9d\u7b9e\u7b9f\u7ba0\u7ba1\u7ba2\u7ba3\u7ba4\u7ba5\u7ba6\u7ba7\u7ba8\u7ba9\u7baa\u7bab\u4201\u4202\u4203\u4204\u4205\u4206\u4207\u4208\u4209\u420a\u420b\u420c\u420d\u7bac\u7bad\u7bae\u7baf\u7bb0\u7bb1\u7bb2\u7bb3\u7bb4\u7bb5\u7bb6\u7bb7\u7bb8\u7bb9\u7bba\u7bbb\u7bbc\u7bbd\u7bbe\u7bbf\u7bc1\u7bc2\u7bc3\u7bc4\u7bc5\u7bc6\u7bc7\u7bc8\u7bca\u7bcb\u7bcc\u7bcd\u7bce\u7bcf\u7bd0\u7bd1\u7bd2\u7bd3\u420e\u420f\u4210\u4211\u4212\u4213\u4214\u4215\u4216\u4217\u4218\u4219\u421a\u421b\u421c\u421d\u421e\u421f\u4220\u4221\u4222\u4223\u4224\u4225\u4226\u4227\u4228\u4229\u7bc9\u7bd4\u7bd5\u7bd6\u7bd7\u7bd8\u7bd9\u7bda\u7bdb\u7bdc\u7bdd\u7bde\u7bdf\u7be0\u7be1\u7be2\u7be3\u7be4\u7be5\u7be6\u7be7\u7be8\u7be9\u7bea\u7beb\u7bec\u7bed\u7bee\u7bef\u7c11\u7c15\u422a\u422b\u422c\u422d\u422e\u422f\u4230\u4231\u4232\u4233\u4234\u4235\u4236\u4237\u7bf0\u7bf1\u7bf2\u7bf3\u7bf4\u7bf5\u7bf6\u7bf7\u7bf8\u7bf9\u7bfa\u7bfb\u7bfc\u7bfd\u7bfe\u7bff\u7c00\u7c01\u7c02\u7c03\u7c04\u7c05\u7c06\u7c07\u7c08\u7c09\u7c0a\u7c0b\u7c0c\u7c0d\u7c0e\u7c0f\u7c10\u7c12\u7c13\u7c14\u7c16\u7c17\u4238\u4239\u423a\u423b\u423c\u423d\u423e\u423f\u4240\u4241\u4242\u4243\u4244\u4245\u4246\u4247\u7c18\u7c19\u7c1a\u7c1b\u7c1c\u7c1d\u7c1e\u7c1f\u7c20\u7c21\u7c22\u7c23\u7c24\u7c25\u7c26\u7c27\u7c28\u7c29\u7c2a\u7c2b\u7c2c\u7c2d\u7c2e\u7c2f\u7c30\u7c31\u7c32\u4248\u4249\u424a\u424b\u424c\u424d\u424e\u4250\u4251\u4252\u4253\u4254\u4255\u4256\u4257\u4258\u4259\u425a\u4263\u7c33\u7c34\u7c35\u7c36\u7c37\u7c38\u7c39\u7c3a\u7c3b\u7c3c\u7c3d\u7c3e\u7c3f\u7c40\u7c41\u7c42\u424f\u425b\u425c\u425d\u425e\u425f\u4260\u4261\u4262\u4264\u7c43\u7c44\u7c45\u7c46\u7c47\u7c48\u7c49\u7c4a\u7c4b\u7c4c\u7c4d\u7c4e\u7c4f\u7c55\u7c56\u4265\u7c50\u7c51\u7c52\u7c53\u7c54\u4266\u4267\u4268\u4269\u426a\u426b\u426c\u426d\u7c57\u7c58\u7c59\u7c5a\u7c5b\u7c5c\u7c5d\u7c5e\u7c5f\u7c60\u7c61\u426e\u426f\u4270\u4271\u7c62\u7c63\u7c64\u7c65\u7c66\u7c67\u7c68\u4272\u4273\u4274\u7c6a\u4276\u7c69\u7c6b\u7c6c\u7c6d\u7c6e\u7c6f\u7c70\u4277\u4278\u7c71\u4279\u7c72\u7c73\u7c74\u7c75\u7c76\u5a04\u7c77\u7c78\u7c79\u7c7a\u7c7b\u7c7c\u7c7d\u7c7e\u7c7f\u7c80\u7c81\u7c82\u427a\u7c83\u7c84\u7c85\u7c86\u7c87\u7c88\u7c89\u7c8a\u7c8b\u7c8c\u7c8d\u7c8e\u7c8f\u7c90\u7c91\u427b\u427c\u7568\u7c92\u7c93\u7c94\u7c95\u7c96\u7c97\u7c98\u7c99\u7c9a\u7c9b\u7c9c\u7c9d\u7ca3\u427d\u427e\u427f\u4280\u7c9e\u7c9f\u7ca0\u7ca1\u7ca2\u7ca4\u7ca5\u7ca6\u7ca7\u7ca8\u7ca9\u7caa\u7cab\u7cac\u7cad\u4281\u4282\u4283\u4284\u4285\u4286\u7cae\u7caf\u7cb0\u7cb1\u7cb2\u7cb3\u7cb4\u7cb5\u7cc0\u4287\u4288\u4289\u428a\u428b\u428c\u7cb6\u7cb7\u7cb8\u7cb9\u7cba\u7cbb\u7cbc\u7cbd\u7cbe\u7cbf\u7cc1\u428d\u428e\u428f\u4290\u4291\u4292\u7cc2\u7cc3\u7cc4\u7cc5\u7cc6\u7cc7\u7cc8\u7cc9\u7cca\u7ccb\u7ccc\u7ccd\u7cce\u4293\u4294\u4295\u4296\u4297\u4298\u4299\u7ccf\u7cd0\u7cd1\u7cd2\u7cd3\u7cd4\u7cd5\u7cd6\u7cd7\u7cd8\u9fef\u429a\u429b\u4a00\u7cd9\u7cda\u7cdb\u7cdc\u7cdd\u7cde\u7cdf\u7ce0\u7ce1\u7ce2\u7ce8\u429c\u429d\u429e\u429f\u42a0\u42a1\u42a2\u7ce3\u7ce4\u7ce5\u7ce6\u7ce7\u42a3\u42a4\u42a5\u42a6\u42a7\u42a8\u42a9\u7ce9\u7cea\u7ceb\u7cec\u7ced\u42aa\u42ab\u42ac\u7cee\u7cef\u7cf0\u42ad\u42ae\u7cf2\u42af\u7cf1\u7cf3\u7cf4\u42b0\u7cf5\u42b1\u42b2\u7cf6\u42b3\u42b4\u7cf7\u7cf8\u7cf9\u7cfa\u7cfb\u7cfc\u7cfd\u7cfe\u7cff\u42b5\u7d00\u7d01\u7d02\u7d03\u7d04\u7d05\u7d06\u7d07\u7d08\u7d09\u42b6\u42b7\u42b8\u42b9\u7d0a\u7d0b\u7d0c\u7d0d\u7d0e\u7d0f\u7d10\u7d11\u7d12\u7d13\u7d14\u7d15\u7d16\u7d17\u7d18\u7d19\u7d1a\u7d1b\u7d1c\u7d1d\u7d1e\u7d1f\u7d20\u7d21\u7d22\u7d23\u7d24\u7d25\u7d26\u7d27\u42ba\u42bb\u42bc\u42bd\u42be\u42bf\u42c0\u42c1\u42c2\u42c3\u42c4\u42c5\u42c6\u42c7\u7d28\u7d29\u7d2a\u7d2b\u7d2c\u7d2d\u7d2e\u7d2f\u7d30\u7d31\u7d32\u7d33\u7d34\u7d35\u7d36\u7d37\u7d38\u7d39\u7d3a\u7d3b\u7d3c\u7d3d\u7d3e\u7d3f\u7d40\u7d41\u7d42\u7d43\u7d44\u7d45\u7d46\u7d47\u7d48\u7d49\u7d4a\u7d4b\u7d4c\u42c8\u42c9\u42ca\u42cb\u42cc\u42cd\u42ce\u42cf\u42d0\u42d1\u42d2\u42d3\u42d4\u7d4d\u7d4e\u7d4f\u7d50\u7d51\u7d52\u7d53\u7d54\u7d55\u7d56\u7d57\u7d58\u7d59\u7d5a\u7d5c\u7d5d\u7d5e\u7d5f\u7d60\u7d61\u7d62\u7d63\u7d64\u7d65\u7d66\u7d67\u7d68\u7d69\u7d6a\u7d6b\u7d6c\u7d6d\u7d6e\u7d6f\u7d70\u7d71\u7d72\u7d73\u7d74\u7d75\u7d76\u7d77\u42d5\u42d6\u42d7\u42d8\u42d9\u42da\u42db\u42dc\u42dd\u42de\u7d5b\u7d78\u7d79\u7d7a\u7d7b\u7d7c\u7d7d\u7d7e\u7d7f\u7d80\u7d81\u7d82\u7d83\u7d84\u7d85\u7d86\u7d87\u7d88\u7d89\u7d8a\u7d8b\u7d8c\u7d8d\u7d8e\u7d8f\u7d90\u7d91\u7d92\u7d93\u7d94\u7d95\u7d96\u7d97\u7d98\u7d99\u7d9a\u7d9b\u7dd0\u42df\u42e0\u42e1\u42e2\u42e3\u42e4\u42e5\u42e6\u7d9c\u7d9d\u7d9e\u7d9f\u7da0\u7da1\u7da2\u7da3\u7da4\u7da5\u7da6\u7da7\u7da8\u7da9\u7daa\u7dab\u7dac\u7dad\u7dae\u7daf\u7db0\u7db1\u7db2\u7db3\u7db4\u7db5\u7db6\u7db7\u7db8\u7db9\u7dba\u7dbb\u7dbc\u7dbd\u7dbe\u7dbf\u7dc0\u7dc1\u7dc2\u7dc3\u7dc4\u7dc5\u7dc6\u7dc7\u7dc8\u7dc9\u7dca\u7dcb\u7dcc\u7dcd\u7dce\u7dcf\u7dd1\u7dd2\u7dd4\u7dd5\u42e7\u42e8\u42e9\u42ea\u42eb\u42ec\u42ed\u42ee\u42ef\u42f0\u42f1\u42f2\u7dd3\u7dd6\u7dd7\u7dd8\u7dd9\u7dda\u7ddb\u7ddc\u7ddd\u7dde\u7ddf\u7de0\u7de1\u7de2\u7de3\u7de4\u7de5\u7de6\u7de7\u7de8\u7de9\u7dea\u7deb\u7dec\u7ded\u7dee\u7def\u7df0\u7df1\u7df2\u7df3\u7df4\u7df5\u7df6\u7df7\u7df8\u7df9\u7dfa\u7dfb\u7dfc\u7dfd\u7dfe\u7dff\u7e00\u7e01\u7e02\u7e03\u7e04\u7e05\u7e06\u7e07\u42f3\u42f4\u42f5\u42f6\u42f7\u42f8\u42f9\u42fa\u42fb\u42fc\u42fd\u42fe\u42ff\u4300\u4301\u4302\u4303\u4304\u7e08\u7e09\u7e0a\u7e0b\u7e0c\u7e0d\u7e0e\u7e0f\u7e10\u7e11\u7e12\u7e13\u7e14\u7e15\u7e16\u7e17\u7e18\u7e19\u7e1a\u7e1b\u7e1c\u7e1d\u7e1e\u7e1f\u7e20\u7e21\u7e22\u7e23\u7e24\u7e25\u7e26\u7e27\u7e28\u4305\u4306\u4307\u4308\u4309\u430a\u430b\u7e29\u7e2a\u7e2b\u7e2c\u7e2d\u7e2e\u7e2f\u7e30\u7e31\u7e32\u7e33\u7e34\u7e35\u7e36\u7e37\u7e38\u7e39\u7e3a\u7e3b\u7e3c\u7e3d\u7e3e\u7e3f\u7e40\u7e41\u7e42\u7e43\u7e44\u7e45\u7e46\u7e47\u7e49\u7e4a\u7e4c\u7e4d\u430c\u430d\u430e\u430f\u4310\u4311\u4312\u4313\u4314\u4315\u7e48\u7e4e\u7e4f\u7e50\u7e51\u7e52\u7e53\u7e54\u7e55\u7e56\u7e57\u7e58\u7e59\u7e5a\u7e5b\u7e5c\u7e5d\u7e5e\u7e5f\u7e60\u7e61\u7e62\u7e63\u7e64\u7e65\u7e67\u7e71\u4316\u4317\u4318\u4319\u431a\u431b\u7e4b\u7e66\u7e68\u7e69\u7e6a\u7e6b\u7e6c\u7e6d\u7e6e\u7e6f\u7e70\u7e72\u7e73\u7e74\u7e75\u7e76\u7e77\u7e78\u7e79\u7e7a\u431c\u431d\u431e\u431f\u4320\u4321\u4322\u7e7b\u7e7c\u7e7d\u7e7e\u7e7f\u7e80\u7e81\u7e82\u7e83\u4323\u4324\u4325\u4326\u4327\u7e84\u7e85\u7e86\u7e87\u7e88\u7e89\u7e8a\u7e8b\u7e8c\u7e8d\u7e8e\u7e8f\u7e90\u4328\u4329\u7e91\u7e92\u432a\u432b\u432c\u432d\u7e93\u7e94\u7e95\u7e96\u432e\u7e97\u432f\u4330\u4331\u4332\u7e98\u7e99\u7e9a\u7e9b\u4333\u4334\u7e9c\u7e9d\u4335\u7e9e\u7e9f\u7ea0\u7ea1\u7ea2\u7ea3\u7ea4\u7ea5\u7ea6\u7ea7\u7ea8\u7ea9\u7eaa\u7eab\u7eb6\u4336\u7eac\u7ead\u7eae\u7eaf\u7eb0\u7eb1\u7eb2\u7eb3\u7eb4\u7eb5\u7eb7\u7eb8\u7eb9\u7eba\u7ebb\u7ebc\u7ebd\u7ebe\u4338\u7ebf\u7ec0\u7ec1\u7ec2\u7ec3\u7ec4\u7ec5\u7ec6\u7ec7\u7ec8\u7ec9\u7eca\u7ecb\u7ecc\u7ecd\u7ece\u7ecf\u7ed0\u4337\u4339\u7ed1\u7ed2\u7ed3\u7ed4\u7ed5\u7ed6\u7ed7\u7ed8\u7ed9\u7eda\u7edb\u7edc\u7edd\u7ede\u7edf\u433a\u433b\u7ee0\u7ee1\u7ee2\u7ee3\u7ee4\u7ee5\u7ee6\u7ee7\u7ee8\u433c\u7ee9\u7eea\u7eeb\u7eec\u7eed\u7eee\u7eef\u7ef0\u7ef1\u7ef2\u7ef3\u7ef4\u7ef5\u7ef6\u7ef7\u7ef8\u7ef9\u7efa\u7efb\u7efc\u7efd\u7efe\u7eff\u7f00\u7f01\u433d\u7f02\u7f03\u7f04\u7f05\u7f06\u7f07\u7f08\u7f09\u7f0a\u7f0b\u7f0c\u7f0d\u7f0e\u7f0f\u7f10\u7f11\u7f12\u7f13\u7f14\u7f15\u7f16\u7f17\u7f18\u433e\u433f\u7f19\u7f1a\u7f1b\u7f1c\u7f1d\u7f1e\u7f1f\u7f20\u7f21\u7f22\u7f23\u7f24\u4340\u7f25\u7f26\u7f27\u7f28\u7f29\u7f2a\u7f2b\u7f2c\u7f2d\u7f2e\u7f2f\u7f30\u7f31\u7f32\u7f33\u7f34\u4341\u7f35\u7f36\u7f37\u7f38\u4342\u7f39\u7f3a\u7f3c\u4343\u7f3b\u7f3d\u4344\u4345\u4346\u4347\u4348\u4349\u7f3e\u7f3f\u7f40\u434a\u7f41\u7f42\u434b\u434c\u7f43\u434d\u7f44\u7f45\u7f46\u7f47\u7f48\u7f49\u7f4a\u7f4b\u7f4c\u7f4d\u7f4e\u7f4f\u434e\u7f50\u7f51\u7f52\u7f53\u434f\u7f54\u7f55\u7f56\u7f57\u4350\u4351\u7f58\u7f59\u7f5a\u4352\u4353\u4354\u7f5b\u7f5c\u7f5d\u7f5e\u7f5f\u7f60\u7f61\u7f62\u4355\u4356\u4357\u7f63\u4358\u7f64\u7f65\u7f66\u4359\u435a\u7f67\u7f68\u7f69\u7f6a\u7f6b\u7f6c\u7f6d\u7f6e\u7f72\u435b\u435c\u435d\u435e\u7f6f\u7f70\u7f71\u7f73\u7f74\u435f\u7f75\u7f76\u7f77\u7f78\u7f79\u7f7a\u7f7b\u7f7c\u4360\u4361\u7f7d\u7f7e\u7f7f\u7f80\u7f81\u4362\u7f82\u4363\u7f83\u7f84\u7f85\u7f86\u4364\u4365\u7f87\u7f88\u7f89\u4366\u9fe1\u7f8a\u7f8b\u7f8c\u7f8d\u7f8e\u7f8f\u7f90\u7f91\u7f92\u7f93\u7f94\u7f96\u7f97\u7f98\u7f99\u4367\u4368\u4369\u7f95\u7f9a\u7f9b\u7f9c\u7f9d\u7f9e\u7f9f\u436a\u436b\u436c\u436d\u7fa0\u7fa1\u7fa2\u436e\u436f\u4370\u7fa3\u7fa4\u7fa5\u7fa6\u7fa7\u7fa8\u7fa9\u7faa\u4371\u7fab\u4372\u4373\u4374\u4375\u4376\u7fac\u7fad\u7fae\u7faf\u7fb0\u4377\u7fb1\u7fb2\u4378\u4379\u437a\u7fb3\u7fb4\u7fb5\u437b\u437c\u7fb6\u7fb7\u7fb8\u7fb9\u7fba\u7fbb\u7fbc\u437d\u7fbd\u7fbe\u7fbf\u7fc0\u7fc1\u7fc2\u7fc3\u7fc4\u7fc5\u7fc6\u437e\u437f\u7fc7\u7fc8\u7fc9\u7fca\u7fcb\u7fcc\u7fcd\u7fce\u7fcf\u7fd0\u7fd1\u7fd2\u4380\u4381\u4382\u4383\u4384\u4385\u4386\u7fd3\u7fd4\u7fd5\u7fd6\u7fd7\u7fd8\u7fd9\u7fda\u4387\u4388\u4389\u438a\u7fdb\u7fdc\u7fdd\u438b\u438c\u438d\u438e\u7fde\u7fdf\u7fe0\u7fe1\u7fe2\u7fe3\u7fe4\u438f\u4390\u4391\u4392\u4393\u7fe5\u7fe6\u7fe7\u7fe8\u7fe9\u7fea\u7feb\u7fec\u7fed\u7fee\u7fef\u7ff0\u7ff1\u4394\u4395\u7ff2\u7ff3\u7ff4\u7ff5\u7ff6\u7ffc\u7ff7\u7ff8\u7ff9\u7ffa\u7ffb\u4396\u4397\u4398\u7ffd\u7ffe\u7fff\u8000\u4399\u439a\u8001\u8002\u8003\u439b\u8004\u8005\u8006\u8007\u8008\u8009\u800a\u800b\u439c\u439d\u439e\u800c\u800d\u800e\u800f\u8010\u8011\u439f\u43a0\u43a1\u8012\u8013\u8014\u43a2\u8015\u8016\u8017\u8018\u8019\u801a\u801b\u801c\u801d\u801e\u801f\u43a3\u8020\u8021\u8022\u43a4\u43a5\u8023\u8024\u8025\u43a6\u43a7\u43a8\u43a9\u43aa\u8026\u8027\u43ab\u43ac\u8028\u8029\u802a\u802b\u802c\u43ad\u43ae\u43af\u802d\u802e\u43b0\u802f\u8030\u43b1\u8031\u8032\u8033\u8034\u43b2\u8035\u8036\u8037\u8038\u8039\u803a\u803b\u803c\u803d\u803e\u803f\u8040\u8041\u8042\u43b3\u43b4\u8043\u8044\u8045\u8046\u8047\u8048\u8049\u804a\u804b\u804c\u804d\u43b5\u43b6\u804e\u804f\u8050\u8051\u8052\u8053\u8054\u8060\u43b7\u8055\u8056\u8057\u8058\u43b8\u43b9\u8059\u805a\u805b\u805c\u805d\u805e\u805f\u8061\u8062\u8063\u9fe2\u43ba\u43bb\u43bc\u43bd\u43be\u8064\u8065\u8066\u8067\u8068\u8069\u806a\u806b\u9fe3\u43bf\u43c0\u806c\u806d\u43c1\u43c2\u43c3\u806f\u8070\u8071\u8072\u8073\u43c4\u43c5\u43c6\u43c7\u806e\u8074\u8075\u8076\u8077\u9fe4\u43c8\u8078\u8079\u807a\u807b\u807c\u43c9\u807d\u807e\u43ca\u807f\u8080\u8081\u8082\u8083\u8084\u8085\u8086\u43cb\u8087\u8088\u8089\u808a\u808b\u808c\u808d\u808e\u808f\u43cc\u43cd\u8090\u8091\u8092\u8093\u8094\u8095\u8096\u8097\u8098\u8099\u809a\u809b\u809c\u809d\u809e\u809f\u80a0\u43ce\u43cf\u80a1\u80a2\u80a3\u80a4\u80a5\u80a6\u80a7\u80a8\u80a9\u80aa\u80ab\u80ac\u80ad\u80ae\u80af\u80b0\u80b1\u80b2\u80b3\u80b4\u80b5\u80b6\u80b7\u80b8\u80b9\u80ba\u80bb\u80bc\u80bd\u80be\u80bf\u80c0\u80c1\u43d0\u43d1\u43d2\u43d3\u43d4\u43d5\u43d6\u43d7\u43d8\u43d9\u43da\u43db\u43dc\u43dd\u6711\u80c2\u80c3\u80c4\u80c5\u80c6\u80c7\u80c8\u80c9\u80ca\u80cb\u80cc\u80cd\u80ce\u80cf\u80d0\u80d1\u80d2\u80d3\u80d4\u80d5\u80d6\u80d7\u80d8\u80d9\u80da\u80db\u80dc\u80dd\u80de\u80df\u80e0\u80e1\u80e2\u80e3\u80e4\u80e5\u80e6\u80e7\u80e8\u80e9\u80ea\u80eb\u80ec\u8109\u43de\u43df\u43e0\u43e1\u43e2\u43e3\u43e4\u43e5\u80ed\u80ee\u80ef\u80f0\u80f1\u80f2\u80f3\u80f4\u80f5\u80f6\u80f7\u80f8\u80f9\u80fa\u80fb\u80fc\u80fd\u80fe\u80ff\u8100\u8101\u8102\u8103\u8104\u8105\u8106\u8107\u8108\u810a\u810b\u810c\u810d\u810e\u810f\u8110\u8111\u8112\u8113\u8114\u43e6\u43e7\u43e8\u43e9\u43ea\u43eb\u43ec\u43ed\u43ee\u8115\u8116\u8117\u8118\u8119\u811a\u811b\u811c\u811d\u811e\u811f\u8120\u8121\u8122\u8123\u8124\u8125\u8126\u8127\u8128\u8129\u812a\u812b\u812c\u812d\u812e\u812f\u8130\u8131\u8132\u8133\u8134\u8135\u8136\u8137\u8138\u43ef\u43f0\u43f1\u43f2\u43f3\u43f4\u43f5\u43f6\u43f7\u43f8\u43f9\u43fa\u43fb\u8139\u813a\u813b\u813c\u813d\u813e\u813f\u8140\u8141\u8142\u8143\u8144\u8145\u8146\u8147\u8148\u8149\u814a\u814b\u814c\u814d\u814e\u814f\u8150\u8151\u8152\u8153\u8154\u8155\u8156\u8157\u8158\u8159\u815a\u3b39\u43fc\u43fd\u43fe\u43ff\u4400\u4401\u4402\u4403\u4404\u4405\u4406\u4407\u4408\u4409\u440a\u440b\u440c\u5e50\u6721\u815b\u815c\u815d\u815e\u815f\u8160\u8161\u8162\u8163\u8164\u8165\u8166\u8167\u8168\u8169\u816a\u816b\u816c\u816d\u816e\u816f\u8170\u8171\u8172\u8173\u8174\u8175\u8176\u8177\u8178\u8179\u817a\u817b\u817c\u817d\u817e\u440d\u440e\u440f\u4410\u4411\u4412\u4413\u4414\u4415\u4416\u4417\u4418\u4419\u441a\u441b\u817f\u8180\u8181\u8182\u8183\u8184\u8185\u8186\u8187\u8188\u8189\u818a\u818b\u818c\u818d\u818e\u818f\u8190\u8191\u441c\u441d\u441e\u441f\u4420\u4421\u4422\u4423\u4424\u4425\u4426\u4427\u4428\u4429\u442a\u8192\u8193\u8194\u8195\u8196\u8197\u8198\u8199\u819a\u819b\u819c\u819d\u819e\u819f\u81a0\u81a1\u81a2\u81a3\u442b\u442c\u442d\u442e\u442f\u4430\u4431\u4432\u4433\u4434\u6725\u81a5\u81a6\u81a7\u81a8\u81a9\u81aa\u81ab\u81ac\u81ad\u81ae\u81af\u81b0\u81b1\u81b2\u81b3\u81b4\u81b5\u81b6\u4435\u4436\u4437\u4438\u4439\u443a\u443b\u443c\u443d\u81b7\u81b8\u81b9\u81ba\u81bb\u81bc\u81bd\u81be\u81bf\u81c0\u81c1\u81c2\u81c3\u81c4\u81c5\u81c6\u81c7\u81c8\u81c9\u81ca\u81cb\u81cc\u443e\u443f\u4440\u4441\u81cd\u81ce\u81cf\u81d0\u81d1\u81d2\u81d3\u4442\u4443\u4444\u4445\u81d4\u81d5\u81d7\u81d8\u4446\u4447\u4448\u81d6\u81d9\u81da\u81db\u81dc\u4449\u444a\u81dd\u352e\u444b\u444c\u444d\u444e\u81de\u81df\u444f\u81e0\u81e1\u81e2\u81e3\u81e4\u81e5\u4450\u81e6\u81e7\u81e8\u81e9\u4451\u81ea\u81eb\u81ec\u81ed\u81ee\u81ef\u81f0\u81f1\u81f2\u81f3\u81f4\u4452\u81f5\u81f6\u81f7\u81f8\u81f9\u4453\u81fa\u81fb\u81fc\u81fd\u81fe\u81ff\u8200\u8201\u4454\u8202\u4455\u8203\u8204\u8205\u8207\u4456\u8208\u4457\u8206\u8209\u4458\u820a\u820b\u820c\u820d\u820e\u820f\u8210\u4459\u445a\u8211\u445b\u8212\u8213\u8214\u8215\u8216\u8217\u445c\u8218\u8219\u821a\u821b\u821c\u821d\u821e\u445d\u445e\u445f\u821f\u8220\u4460\u8221\u8222\u8223\u8224\u4461\u4462\u4463\u8225\u8226\u8227\u8228\u8229\u822a\u822b\u822c\u822d\u822e\u822f\u8230\u8231\u4464\u4465\u8232\u8233\u8234\u8235\u8236\u8237\u8238\u8239\u823a\u823b\u4466\u4467\u4468\u4469\u823c\u823d\u823e\u823f\u446a\u446b\u446c\u446d\u446e\u8240\u8241\u8242\u8243\u8244\u8245\u8246\u8247\u8248\u8249\u446f\u4470\u824a\u824b\u824c\u824d\u4471\u4472\u4473\u4474\u4475\u4476\u4477\u4478\u824e\u824f\u8250\u8251\u8252\u8253\u8254\u4479\u447a\u447b\u8255\u8256\u8257\u8258\u8259\u447c\u447d\u447e\u825a\u825b\u825c\u825d\u447f\u4480\u4481\u4482\u4483\u4484\u4485\u825e\u825f\u8260\u4486\u4487\u4488\u8261\u8262\u8263\u8264\u8265\u8266\u8267\u8268\u8269\u4489\u826a\u826b\u826c\u826d\u826e\u826f\u8270\u8271\u9fe5\u8272\u448a\u8273\u8274\u448b\u8275\u448c\u448d\u448e\u448f\u8276\u4490\u8277\u8278\u8279\u4491\u827a\u9fb7\u827b\u827c\u827d\u827e\u827f\u8280\u8281\u8282\u4492\u4493\u4494\u8283\u8284\u8285\u8286\u8287\u8288\u8289\u828a\u828b\u828c\u828d\u828e\u828f\u8290\u8291\u8292\u8293\u8294\u8295\u8296\u8297\u4495\u4496\u4497\u4498\u4499\u8298\u8299\u829a\u829b\u829c\u829d\u829e\u829f\u82a0\u82a1\u82a2\u82a3\u82a4\u82a5\u82a6\u82a7\u82a8\u82a9\u82aa\u82ab\u82ac\u82ad\u82ae\u82af\u82b0\u82b1\u82b2\u82b3\u82b4\u82b5\u82b6\u82b7\u82b8\u82b9\u82ba\u82bb\u82bc\u82bd\u82be\u82c0\u82c1\u82c2\u82c3\u82c4\u82c5\u82c6\u82c7\u82c8\u82c9\u82ca\u82cb\u82cc\u82cd\u82ce\u82cf\u833e\u449a\u449b\u449c\u449d\u449e\u449f\u44a0\u44a1\u44a2\u44a3\u44a4\u44a5\u82bf\u82d0\u82d1\u82d2\u82d3\u82d4\u82d5\u82d6\u82d7\u82d8\u82d9\u82da\u82db\u82dc\u82dd\u82de\u82df\u82e0\u82e1\u82e2\u82e3\u82e4\u82e5\u82e6\u82e7\u82e8\u82e9\u82ea\u82eb\u82ec\u82ed\u82ee\u82ef\u82f0\u82f1\u82f2\u82f3\u82f4\u82f5\u82f6\u82f7\u82f8\u82f9\u82fa\u82fb\u82fc\u82fd\u82fe\u82ff\u8300\u8301\u8302\u8303\u8304\u8305\u8306\u8307\u8308\u8309\u830a\u830b\u830c\u830d\u830e\u830f\u8310\u8311\u8313\u8314\u8315\u44a6\u44a7\u44a8\u44a9\u44aa\u44ab\u44ac\u44ad\u44ae\u44af\u8312\u8316\u8317\u8318\u8319\u831a\u831b\u831c\u831e\u831f\u8320\u8321\u8322\u8324\u8325\u8326\u8327\u8328\u8329\u832a\u832b\u832c\u832d\u832e\u832f\u8330\u8331\u8332\u8333\u8334\u8335\u8336\u8337\u8338\u8339\u833a\u833b\u833c\u833d\u833f\u8340\u8341\u8342\u8343\u8344\u8345\u8346\u8347\u8348\u8349\u834a\u834b\u834c\u834d\u834e\u834f\u8350\u8351\u8352\u8354\u8355\u8356\u8357\u8358\u8359\u835a\u835b\u835c\u835d\u835e\u835f\u8360\u8361\u8362\u8363\u8364\u8365\u8366\u8367\u8368\u8369\u836a\u836b\u836c\u836d\u836e\u836f\u9fd2\u44b0\u44b1\u44b2\u44b3\u44b4\u44b5\u44b6\u44b7\u44b8\u44b9\u44ba\u44bb\u44bc\u44bd\u44be\u44bf\u44c0\u44c1\u831d\u8323\u8370\u8371\u8372\u8373\u8374\u8375\u8376\u8377\u8378\u8379\u837a\u837b\u837c\u837d\u837e\u837f\u8380\u8381\u8382\u8383\u8384\u8385\u8386\u8387\u8388\u8389\u838a\u838b\u838c\u838d\u838e\u838f\u8390\u8391\u8392\u8393\u8394\u8395\u8396\u8397\u8398\u8399\u839a\u839b\u839c\u839d\u839e\u839f\u83a0\u83a1\u83a2\u83a3\u83a4\u83a5\u83a6\u83a7\u83a8\u83a9\u83aa\u83ab\u83ac\u83ae\u83af\u83b0\u83b1\u83b2\u83b3\u83b4\u83b5\u83b6\u83b7\u83b8\u83b9\u83ba\u83bb\u83bc\u83bd\u9fca\u44c2\u44c3\u44c4\u44c5\u44c6\u44c7\u44c8\u44c9\u44ca\u44cb\u44cc\u44cd\u44ce\u44cf\u44d0\u44d1\u44d2\u44d3\u44d4\u44d6\u8353\u83be\u83bf\u83c0\u83c1\u83c2\u83c3\u83c4\u83c5\u83c6\u83c7\u83c8\u83c9\u83ca\u83cb\u83cc\u83cd\u83ce\u83cf\u83d0\u83d1\u83d2\u83d3\u83d4\u83d5\u83d6\u83d7\u83d8\u83d9\u83da\u83db\u83dc\u83dd\u83de\u83df\u83e0\u83e1\u83e2\u83e3\u83e4\u83e5\u83e6\u83e7\u83e8\u83e9\u83ea\u83eb\u83ec\u83ed\u83ee\u83ef\u83f0\u83f1\u83f2\u83f3\u83f4\u83f5\u83f6\u83f7\u83f8\u83f9\u83fa\u83fb\u83fc\u83fd\u83fe\u83ff\u8400\u8401\u8402\u8403\u8404\u8405\u8406\u8407\u8408\u8409\u840a\u840b\u840c\u840d\u840e\u840f\u8410\u8411\u8412\u8413\u8414\u8415\u8416\u8417\u8418\u8419\u841a\u841b\u841c\u841d\u841e\u841f\u8420\u8421\u8422\u8423\u8424\u8425\u8426\u8427\u8428\u8457\u9fbf\u44d5\u44d7\u44d8\u44d9\u44da\u44db\u44dc\u44dd\u44de\u44df\u44e0\u44e1\u44e2\u44e3\u44e4\u44e5\u44e6\u44e7\u44e8\u44e9\u44ea\u44eb\u44ec\u44ed\u83ad\u8429\u842a\u842b\u842d\u842e\u842f\u8430\u8431\u8432\u8433\u8434\u8435\u8436\u8437\u8438\u8439\u843a\u843b\u843c\u843d\u843e\u843f\u8440\u8441\u8442\u8443\u8444\u8445\u8446\u8447\u8448\u8449\u844a\u844b\u844c\u844d\u844e\u844f\u8450\u8451\u8452\u8453\u8454\u8455\u8456\u8458\u8459\u845a\u845b\u845c\u845d\u845e\u845f\u8460\u8461\u8462\u8463\u8464\u8465\u8466\u8467\u8468\u8469\u846a\u846b\u846c\u846d\u846e\u846f\u8470\u8471\u8472\u8473\u8474\u8475\u8476\u8477\u8478\u8479\u847a\u847b\u847c\u847d\u847e\u847f\u8480\u8481\u8482\u8483\u8484\u8485\u8486\u8487\u8488\u8489\u848a\u848b\u848c\u848d\u848e\u848f\u9fd3\u44ee\u44ef\u44f0\u44f1\u44f2\u44f3\u44f4\u44f5\u44f6\u44f7\u44f8\u44f9\u44fa\u44fb\u8490\u8491\u8492\u8493\u8494\u8495\u8496\u8497\u8498\u8499\u849a\u849b\u849c\u849d\u849e\u849f\u84a0\u84a1\u84a2\u84a3\u84a4\u84a5\u84a6\u84a7\u84a8\u84a9\u84aa\u84ab\u84ac\u84ad\u84ae\u84af\u84b0\u84b1\u84b2\u84b3\u84b4\u84b5\u84b6\u84b7\u84b9\u84ba\u84bb\u84bc\u84bd\u84be\u84bf\u84c0\u84c1\u84c2\u84c3\u84c4\u84c5\u84c6\u84c7\u84c8\u84c9\u84ca\u84cb\u84cc\u84cd\u84ce\u84cf\u84d0\u84d1\u84d2\u84d3\u84d4\u84d5\u84d6\u84d7\u84d8\u84d9\u84da\u84db\u84dc\u84dd\u84de\u84df\u84e0\u84e1\u84e2\u84e3\u84e4\u84e6\u44fc\u44fd\u44fe\u44ff\u4500\u4501\u4502\u4503\u4504\u4505\u4506\u4507\u4508\u4509\u450a\u450b\u450c\u450d\u84e5\u84e7\u84e8\u84e9\u84ea\u84eb\u84ec\u84ed\u84ee\u84ef\u84f0\u84f1\u84f2\u84f3\u84f4\u84f5\u84f6\u84f7\u84f8\u84f9\u84fa\u84fb\u84fc\u84fd\u84fe\u84ff\u8500\u8501\u8502\u8503\u8504\u8505\u8506\u8507\u8508\u8509\u850a\u850b\u850c\u850d\u850e\u850f\u8510\u8511\u8512\u8513\u8514\u8515\u8516\u8517\u8518\u8519\u851a\u851b\u851c\u851d\u851e\u851f\u8520\u8521\u8522\u8523\u8524\u8525\u8526\u8527\u8528\u8529\u852a\u852b\u852c\u852d\u852e\u852f\u8530\u8531\u8532\u8533\u8534\u8535\u8536\u8537\u8538\u8539\u853a\u853b\u853c\u450e\u450f\u4510\u4511\u4512\u4513\u4514\u4515\u4516\u4517\u4518\u4519\u451a\u451b\u451c\u451d\u451e\u451f\u4520\u4521\u4522\u4523\u4524\u4525\u4526\u4527\u4528\u4529\u452a\u452b\u452c\u853d\u853e\u853f\u8540\u8541\u8542\u8543\u8544\u8545\u8546\u8547\u8548\u8549\u854a\u854b\u854c\u854d\u854e\u854f\u8550\u8551\u8552\u8553\u8554\u8555\u8556\u8557\u8558\u8559\u855a\u855b\u855c\u855d\u855e\u855f\u8560\u8561\u8562\u8563\u8564\u8565\u8566\u8567\u8568\u8569\u856a\u856b\u856c\u856d\u856e\u856f\u8570\u8571\u8572\u8573\u8574\u8575\u452d\u452e\u452f\u4530\u4531\u4532\u4533\u4534\u4535\u4536\u4537\u4538\u4539\u453a\u453b\u453c\u453d\u453e\u453f\u4540\u4541\u4542\u4543\u4544\u8576\u8577\u8578\u8579\u857a\u857b\u857c\u857d\u857e\u857f\u8580\u8581\u8582\u8583\u8584\u8585\u8586\u8587\u8588\u8589\u858a\u858b\u858c\u858d\u858e\u858f\u8590\u8591\u8592\u8593\u8594\u8595\u8596\u8597\u8598\u8599\u859a\u859b\u859c\u859d\u859e\u859f\u85a0\u85a1\u85a2\u85a3\u85a4\u85a5\u85a6\u85a7\u85a8\u85aa\u85ab\u85ac\u85ae\u9fa9\ufa1f\u4545\u4546\u4547\u4548\u4549\u454a\u454b\u454c\u454d\u454e\u454f\u4550\u4551\u85a9\u85ad\u85af\u85b0\u85b1\u85b2\u85b3\u85b4\u85b5\u85b6\u85b7\u85b8\u85b9\u85ba\u85bb\u85bc\u85bd\u85be\u85bf\u85c0\u85c1\u85c2\u85c3\u85c4\u85c5\u85c6\u85c7\u85c8\u85c9\u85ca\u85cb\u85cc\u85cd\u85ce\u85cf\u85d0\u85d1\u85d2\u85d3\u8624\u4552\u4553\u4554\u4555\u4556\u4557\u4558\u4559\u455a\u455b\u455c\u455d\u85d4\u85d5\u85d6\u85d7\u85d8\u85d9\u85da\u85db\u85dc\u85dd\u85de\u85df\u85e0\u85e1\u85e2\u85e3\u85e4\u85e5\u85e6\u85e7\u85e8\u85e9\u85ea\u85eb\u85ec\u85ed\u85ef\u85f0\u85f1\u85f2\u85f3\u85f4\u85f5\u455e\u455f\u4560\u4561\u4562\u4563\u4564\u4565\u85ee\u85f6\u85f7\u85f8\u85f9\u85fa\u85fb\u85fc\u85fd\u85fe\u85ff\u8600\u8601\u8602\u8603\u8604\u8605\u8606\u8607\u8608\u8609\u860a\u860b\u860c\u860d\u860e\u860f\u8610\u8611\u8613\u8614\u8622\u4566\u4567\u4568\u4569\u456a\u456b\u456c\u456d\u456e\u456f\u4570\u4571\u4572\u8612\u8615\u8616\u8617\u8618\u8619\u861a\u861b\u861c\u861d\u861e\u861f\u8620\u8621\u8623\u8625\u8626\u8627\u8628\u8629\u862a\u862b\u862c\u862d\u862e\u862f\u8630\u4573\u4574\u4575\u4576\u4577\u8632\u8633\u8634\u8635\u8636\u8637\u9fc0\u9fe6\u4578\u4579\u457a\u8631\u8638\u8639\u863a\u863b\u863c\u863d\u863e\u863f\u8640\u8641\u457b\u457c\u457d\u8642\u8643\u8644\u8645\u457e\u8646\u8647\u8648\u8649\u457f\u4580\u4581\u864a\u4582\u4583\u4584\u4585\u4586\u864b\u864c\u4587\u864d\u864e\u864f\u4588\u5f6a\u8650\u4589\u8651\u8653\u8654\u458a\u458b\u458c\u458d\u8655\u8656\u8657\u8658\u8659\u865a\u458e\u458f\u865b\u865c\u865d\u4590\u4591\u865e\u865f\u4592\u4593\u4594\u4595\u4596\u8660\u8661\u8662\u8663\u4597\u8664\u8665\u8666\u4598\u8667\u8668\u4599\u8669\u459a\u459b\u459c\u866a\u866b\u866c\u459d\u866d\u866e\u866f\u8670\u8671\u8672\u8673\u8674\u8675\u8676\u8677\u8678\u8679\u867a\u867b\u867c\u867d\u867e\u867f\u8680\u8681\u8682\u8683\u459e\u459f\u45a0\u8684\u8685\u8686\u8687\u8689\u868a\u868b\u868c\u868d\u868e\u868f\u8690\u8691\u8692\u8693\u8694\u8695\u8696\u8697\u8698\u8699\u869a\u869b\u869c\u869d\u869e\u869f\u86a0\u86a1\u86a2\u86a3\u86a4\u86a5\u86a6\u86a7\u86a8\u86a9\u86aa\u86ac\u45a1\u45a2\u45a3\u86ab\u86ad\u86ae\u86af\u86b0\u86b1\u86b2\u86b3\u86b4\u86b5\u86b6\u86b7\u86b8\u86b9\u86ba\u86bb\u86bc\u86bd\u86be\u86bf\u86c0\u86c1\u86c2\u86c3\u86c4\u86c5\u86c6\u86c7\u86c8\u86c9\u86ca\u86cb\u86cc\u86cd\u86ce\u86cf\ufa21\u45a4\u45a5\u45a6\u45a7\u45a8\u45a9\u45aa\u45ab\u45ac\u8688\u86d0\u86d1\u86d2\u86d3\u86d4\u86d5\u86d7\u86d8\u86d9\u86da\u86db\u86dc\u86dd\u86de\u86df\u86e0\u86e1\u86e2\u86e3\u86e4\u86e5\u86e6\u86e7\u86e8\u86e9\u86ea\u86eb\u86ec\u86ed\u86ee\u86ef\u86f0\u86f1\u86f2\u86f3\u86f4\u45ad\u45ae\u45af\u45b0\u45b1\u45b2\u45b3\u45b4\u45b5\u86d6\u86f5\u86f6\u86f7\u86f8\u86f9\u86fa\u86fb\u86fc\u86fd\u86fe\u86ff\u8700\u8701\u8702\u8703\u8704\u8705\u8706\u8707\u8708\u8709\u870a\u870b\u870c\u870d\u870e\u870f\u8710\u8711\u8712\u8713\u8714\u8715\u8716\u8717\u872b\u8746\u45b6\u45b7\u45b8\u45b9\u45ba\u45bb\u45bc\u45bd\u45be\u8718\u8719\u871a\u871b\u871c\u871d\u871e\u871f\u8720\u8721\u8722\u8723\u8724\u8725\u8726\u8727\u8728\u8729\u872a\u872c\u872d\u872e\u872f\u8730\u8731\u8732\u8733\u8734\u8735\u8736\u8737\u8738\u8739\u873a\u873b\u873c\u873d\u873e\u873f\u8740\u8741\u8742\u8743\u8744\u8745\u8747\u8748\u8749\u874a\u874b\u45bf\u45c0\u45c1\u45c2\u45c3\u45c4\u45c5\u45c6\u45c7\u45c8\u45c9\u45ca\u45d5\u874c\u874d\u874e\u874f\u8750\u8751\u8752\u8753\u8754\u8755\u8756\u8757\u8758\u8759\u875a\u875b\u875c\u875d\u875e\u875f\u8760\u8761\u8762\u8763\u8764\u8765\u8766\u8767\u8768\u8769\u876a\u876b\u876c\u876d\u876e\u876f\u8770\u8771\u8772\u8773\u8774\u8775\u8776\u8777\u8778\u877a\u877b\u877c\u877d\u877e\u877f\u8780\u87e1\u45cb\u45cc\u45cd\u45ce\u45cf\u45d0\u45d1\u45d2\u45d3\u45d4\u45d6\u8779\u8781\u8782\u8783\u8784\u8785\u8786\u8787\u8788\u8789\u878a\u878b\u878c\u878d\u878e\u878f\u8790\u8791\u8792\u8793\u8794\u8795\u8796\u8797\u8798\u8799\u879a\u879b\u879c\u879d\u879e\u879f\u87a0\u87a1\u87a2\u87a3\u87a4\u87a5\u87a6\u87a7\u87a8\u87a9\u87d7\u45d7\u45d8\u45d9\u45da\u45db\u45dc\u45dd\u45de\u87aa\u87ab\u87ac\u87ad\u87ae\u87af\u87b0\u87b1\u87b2\u87b3\u87b4\u87b5\u87b6\u87b7\u87b8\u87b9\u87ba\u87bb\u87bc\u87bd\u87be\u87bf\u87c0\u87c1\u87c2\u87c3\u87c4\u87c5\u87c6\u87c7\u87c8\u87c9\u87ca\u87cb\u87cc\u87cd\u87ce\u87cf\u87d0\u87d1\u87d2\u8801\u45df\u45e0\u45e1\u45e2\u45e3\u45e4\u45e5\u45e6\u45e7\u45e8\u45e9\u45ea\u45eb\u45ec\u45ed\u45ee\u87d3\u87d4\u87d5\u87d6\u87d8\u87d9\u87da\u87db\u87dc\u87dd\u87de\u87df\u87e0\u87e2\u87e3\u87e4\u87e5\u87e6\u87e7\u87e8\u87e9\u87ea\u87eb\u87ec\u87ed\u87ee\u87ef\u87f0\u87f1\u87f2\u87f3\u87f4\u87f5\u880e\u45ef\u45f0\u45f1\u45f2\u45f3\u87f6\u87f7\u87f8\u87f9\u87fa\u87fb\u87fc\u87fd\u87fe\u87ff\u8800\u8802\u8803\u8804\u8805\u8806\u8807\u8808\u8809\u880a\u880b\u880c\u880d\u880f\u45f4\u45f5\u45f6\u45f7\u45f8\u45f9\u45fa\u45fb\u8810\u8811\u8812\u8813\u8814\u8815\u8816\u8817\u8818\u8819\u45fc\u45fd\u45fe\u45ff\u4600\u4601\u881a\u881b\u881c\u881d\u881e\u881f\u8820\u8821\u8822\u8823\u8824\u8834\u4602\u4603\u4604\u8825\u8826\u8827\u8828\u8829\u882a\u882b\u882c\u4605\u4606\u4607\u4608\u4609\u882d\u882e\u882f\u8830\u8831\u8832\u8833\u460a\u460b\u460c\u8835\u8836\u8837\u8838\u8839\u883a\u883b\u460d\u883c\u883d\u883e\u883f\u460e\u8840\u8841\u8842\u460f\u8843\u8844\u4610\u8845\u4611\u8846\u8847\u8848\u8849\u4612\u4613\u4614\u884a\u884b\u884c\u884d\u884e\u884f\u4615\u8850\u8851\u8852\u8853\u8854\u8855\u8856\u8857\u8858\u4616\u8859\u885a\u885b\u885c\u885d\u885e\u885f\u8860\u8861\u4617\u4618\u4619\u8862\u8863\u8864\u8865\u461a\u461b\u8866\u8867\u8868\u8869\u886a\u886b\u886c\u461c\u461d\u461e\u886d\u886e\u886f\u8870\u8871\u8872\u8873\u8874\u8875\u8876\u8877\u8878\u8879\u887a\u887b\u887c\u887d\u887e\u887f\u8880\u8881\u8882\u8883\u8884\u8885\u8886\u8887\u461f\u4620\u4621\u8888\u8889\u888a\u888b\u888c\u888d\u888e\u888f\u8890\u8891\u8892\u8893\u8894\u8895\u8896\u8897\u8898\u8899\u889a\u889b\u889c\u889d\u889e\u889f\u88a0\u88a1\u88a2\u88a3\u88a4\u88a5\u88a6\u88a7\u88a8\u88a9\u88aa\u88ab\u88ac\u88ad\u88ae\u88b0\u4622\u4623\u4624\u4625\u4626\u4627\u88af\u88b1\u88b2\u88b3\u88b4\u88b5\u88b6\u88b7\u88b8\u88b9\u88ba\u88bb\u88bc\u88bd\u88be\u88bf\u88c0\u88c1\u88c2\u88c3\u88c4\u88c5\u88c6\u88c7\u88c8\u88c9\u4628\u4629\u462a\u462b\u462c\u462d\u462e\u88ca\u88cb\u88cc\u88cd\u88ce\u88cf\u88d0\u88d1\u88d2\u88d3\u88d4\u88d5\u88d6\u88d7\u88d8\u88d9\u88da\u88db\u88dc\u88dd\u88de\u88df\u88e0\u88e1\u88e2\u88e3\u88e4\u88e5\u462f\u4630\u4631\u4632\u88e7\u88e8\u88e9\u88ea\u88eb\u88ec\u88ed\u88ee\u88ef\u88f0\u88f1\u88f2\u88f3\u88f4\u88f5\u88f6\u88f7\u88f8\u88f9\u88fa\u88fb\u88fc\u88fd\u88fe\u88ff\u8900\u8901\u8902\u8903\u8904\u4633\u4634\u4635\u4636\u4637\u4638\u4639\u463a\u463b\u463c\u463d\u463e\u463f\u4640\u4641\u4642\u88e6\u8905\u8906\u8907\u8908\u8909\u890a\u890b\u890c\u890d\u890e\u890f\u8910\u8911\u8912\u8913\u8914\u8915\u8916\u8917\u8918\u8919\u891a\u891b\u891c\u891d\u4643\u4644\u4645\u4646\u4647\u4648\u4649\u464a\u464b\u464c\u464d\u891e\u891f\u8920\u8921\u8922\u8923\u8924\u8925\u8926\u8927\u8928\u8929\u892a\u892b\u892c\u892d\u892e\u892f\u8930\u8931\u8932\u8934\u464e\u464f\u4650\u4651\u4652\u4653\u8933\u8935\u8936\u8937\u8938\u8939\u893a\u893b\u893c\u893d\u893e\u893f\u8940\u8941\u8942\u8943\u8944\u8945\u8954\u4654\u4655\u4656\u4657\u4658\u4659\u465a\u465b\u465c\u465d\u8946\u8947\u8948\u8949\u894a\u894b\u894c\u894d\u894e\u894f\u8950\u8951\u8952\u8953\u8955\u9fcb\u465e\u465f\u4660\u4661\u4662\u4663\u8956\u8957\u8958\u8959\u895a\u895b\u895c\u895d\u895e\u895f\u8960\u8961\u8962\u4664\u4665\u8963\u8964\u8965\u8966\u8967\u8968\u4666\u4667\u4668\u4669\u8969\u896a\u896b\u896c\u896d\u896e\u466a\u896f\u8970\u8971\u8972\u466b\u8973\u8974\u897d\u466c\u466d\u8975\u8976\u8977\u466e\u466f\u8978\u8979\u897a\u897b\u897c\u4670\u4671\u897e\u897f\u8980\u8981\u4672\u4673\u8982\u4674\u8983\u8984\u4675\u4676\u8985\u8986\u8987\u8988\u8989\u898a\u898b\u898c\u8999\u898d\u898e\u4677\u4678\u898f\u8990\u8991\u8992\u8993\u8994\u4679\u467a\u467b\u8995\u8997\u8998\u899a\u467c\u467d\u467e\u467f\u899b\u899c\u4680\u4681\u899d\u899e\u899f\u89a0\u89a1\u4682\u4683\u89a2\u89a3\u89a4\u89a5\u4684\u4685\u89a6\u89a7\u89a8\u89a9\u89aa\u4686\u4687\u4688\u4689\u89ab\u89ac\u89ad\u89ae\u89af\u468a\u468b\u468c\u89b0\u89b1\u89b2\u89b3\u468d\u468e\u89b4\u89b5\u89b6\u89b7\u89b8\u468f\u4690\u4691\u4692\u4693\u89b9\u89ba\u89bb\u89bc\u89bd\u4694\u89be\u89bf\u89c0\u4695\u4696\u89c1\u89c2\u89c3\u89c4\u89c5\u89c7\u89c8\u89c9\u89ca\u89cb\u89cc\u89cd\u89ce\u89cf\u89d0\u89d1\u89d2\u89d3\u89d4\u89d5\u89d6\u89d7\u89d8\u89d9\u4697\u89da\u89db\u89dd\u89de\u89dc\u89df\u89e0\u89e1\u89e2\u89e3\u89e4\u89e5\u89e6\u89e7\u4698\u4699\u469a\u89e8\u89e9\u89ea\u89eb\u469b\u89ec\u89ed\u89ee\u89ef\u469c\u469d\u469e\u469f\u46a0\u89f0\u89f1\u46a1\u46a2\u46a3\u46a4\u89f2\u89f3\u46a5\u46a6\u89f4\u9d64\u46a7\u89f5\u89f6\u46a8\u46a9\u46ac\u89f7\u89f8\u89f9\u46aa\u46ab\u89fa\u89fb\u89fc\u89fd\u89fe\u89ff\u46ad\u8a00\u8a01\u8a02\u8a03\u8a04\u8a05\u8a06\u8a07\u8a08\u46ae\u46af\u46b0\u8a09\u8a0a\u8a0b\u8a0c\u8a0d\u8a0e\u8a0f\u8a10\u8a11\u8a12\u8a13\u8a14\u8a15\u8a16\u8a17\u8a18\u8a19\u8a1a\u46b1\u46b2\u8a1b\u8a1c\u8a1d\u8a1e\u8a1f\u8a20\u8a21\u8a22\u8a23\u8a24\u8a25\u8a26\u8a27\u8a28\u8a29\u8a2a\u8a2b\u8a2c\u8a2d\u8a2e\u8a2f\u8a30\u8a31\u8a32\u8a33\u46b3\u46b4\u46b5\u46b6\u46b7\u46b8\u46b9\u46ba\u46bb\u46bc\u46bd\u46be\u46bf\u46c0\u46c1\u46c2\u46c3\u8a34\u8a35\u8a36\u8a37\u8a38\u8a39\u8a3a\u8a3b\u8a3c\u8a3d\u8a3e\u8a3f\u8a40\u8a41\u8a42\u8a43\u8a44\u8a45\u8a46\u8a47\u8a48\u8a49\u8a4a\u8a4b\u8a4c\u8a4d\u8a4e\u8a4f\u8a50\u8a51\u8a52\u8a53\u8a54\u8a55\u8a56\u8a57\u8a58\u8a59\u8a5a\u8a5b\u8a5c\u8a5d\u8a5e\u8a5f\u8a60\u46c4\u46c5\u46c6\u46c7\u46c8\u46c9\u46ca\u46cb\u46cc\u46cd\u46ce\u46cf\u46d0\u46d1\u46d2\u46d3\u8a61\u8a62\u8a63\u8a64\u8a65\u8a66\u8a67\u8a68\u8a69\u8a6a\u8a6b\u8a6c\u8a6d\u8a6e\u8a6f\u8a70\u8a71\u8a72\u8a73\u8a74\u8a75\u8a76\u8a77\u8a78\u8a79\u8a7a\u8a7b\u8a7c\u8a7d\u8a7e\u8a7f\u8a80\u8a81\u8a82\u8a83\u8a84\u8a85\u8a86\u8a87\u8a88\u8a89\u8a8a\u8aa0\u9fc1\u46d4\u46d5\u46d6\u46d7\u46d8\u46d9\u46da\u46db\u46dc\u8a8b\u8a8c\u8a8d\u8a8e\u8a8f\u8a90\u8a91\u8a92\u8a93\u8a94\u8a95\u8a96\u8a97\u8a98\u8a99\u8a9a\u8a9b\u8a9c\u8a9d\u8a9e\u8a9f\u8aa1\u8aa2\u8aa3\u8aa4\u8aa5\u8aa6\u8aa7\u8aa8\u8aa9\u8aaa\u8aab\u8aac\u8aad\u8aae\u46dd\u46de\u46df\u46e0\u46e1\u46e2\u46e3\u46e4\u46e5\u46e6\u46e7\u46e8\u8aaf\u8ab0\u8ab1\u8ab2\u8ab3\u8ab4\u8ab5\u8ab6\u8ab7\u8ab8\u8ab9\u8aba\u8abb\u8abc\u8abd\u8abe\u8abf\u8ac0\u8ac1\u8ac2\u8ac3\u8ac4\u8ac5\u8ac6\u8ac7\u8ac8\u8ac9\u8aca\u8acb\u8acc\u8acd\u8ace\u8acf\u8ad0\u8ad1\u8ad2\u8ad3\u8ad4\u8ad5\u8ad6\u8ad7\u8ad8\u8ad9\u8ada\u8ae9\u46e9\u46ea\u46eb\u46ec\u46ed\u46ee\u46ef\u46f0\u46f1\u46f2\u46f3\u46f4\u46f5\u46f6\u46f7\u46f8\u8adb\u8adc\u8add\u8ade\u8adf\u8ae0\u8ae1\u8ae2\u8ae3\u8ae4\u8ae5\u8ae6\u8ae7\u8ae8\u8aea\u8aeb\u8aec\u8aed\u8aee\u8aef\u8af0\u8af1\u8af2\u8af3\u8af4\u8af5\u8af6\u8af7\u8af8\u8af9\u8afa\u8afb\u8afc\u8afd\u8afe\u8aff\u8b00\u8b01\u8b02\u8b03\u46f9\u46fa\u46fb\u46fc\u46fd\u8b04\u8b05\u8b06\u8b07\u8b08\u8b09\u8b0a\u8b0b\u8b0c\u8b0d\u8b0e\u8b0f\u8b10\u8b11\u8b12\u8b13\u8b14\u8b15\u8b16\u8b17\u8b18\u8b19\u8b1a\u8b1b\u8b1c\u8b1d\u8b1e\u8b1f\u8b20\u8b21\u8b22\u46fe\u46ff\u4700\u4701\u4702\u8b23\u8b24\u8b25\u8b26\u8b27\u8b28\u8b29\u8b2a\u8b2b\u8b2c\u8b2d\u8b2e\u8b2f\u8b30\u8b31\u8b32\u8b33\u8b34\u8b35\u8b36\u8b37\u8b38\u8b39\u8b3a\u8b3b\u8b3c\u8b3d\u8b3e\u4703\u4704\u4705\u4706\u4707\u4708\u4709\u470a\u8b3f\u8b40\u8b41\u8b42\u8b43\u8b44\u8b45\u8b46\u8b47\u8b48\u8b49\u8b4a\u8b4b\u8b4c\u8b4e\u8b4f\u8b50\u8b51\u8b52\u8b53\u8b54\u8b55\u8b56\u8b57\u8b58\u8b59\u8b5a\u8b5b\u8b5c\u9fbb\u470b\u470c\u470d\u470e\u470f\u4710\u4711\u8b4d\u8b5d\u8b5e\u8b5f\u8b60\u8b61\u8b62\u8b63\u8b64\u8b65\u8b66\u8b67\u8b68\u8b69\u8b6a\u8b6b\u8b6c\u8b6d\u8b6e\u8b6f\u8b70\u8b71\u8b72\u4712\u4713\u4714\u4715\u4716\u4717\u4718\u8b73\u8b74\u8b75\u8b76\u8b77\u8b78\u8b79\u8b7a\u8b7b\u8b7c\u8b7d\u4719\u471a\u471b\u471c\u471d\u471e\u8b7e\u8b7f\u8b80\u8b81\u8b82\u8b83\u8b84\u8b85\u471f\u4720\u4721\u8b86\u8b87\u8b88\u8b89\u8b8a\u8b8b\u8b8c\u8b8d\u8b8e\u8b8f\u8b90\u4722\u8b91\u8b92\u8b93\u8b94\u8b95\u8b96\u8b97\u8b98\u8b99\u8b9a\u8b9b\u8b9c\u8b9d\u8b9e\u8b9f\u8ba0\u8ba1\u8ba2\u8ba3\u8ba4\u8ba5\u8ba6\u8ba7\u8ba8\u8ba9\u8baa\u8bab\u8bac\u8bad\u8bae\u8baf\u8bb0\u8bb1\u8bb2\u8bb3\u8bb4\u8bb5\u8bb6\u8bb7\u8bb8\u8bb9\u8bba\u8bbb\u8bbc\u8bbd\u8bbe\u8bbf\u8bc0\u4723\u8bc1\u8bc2\u8bc3\u8bc4\u8bc5\u8bc6\u8bc7\u8bc8\u8bc9\u8bca\u8bcb\u8bcc\u8bcd\u8bce\u8bcf\u8bd0\u8bd1\u8bd2\u8bd3\u8bd4\u8bd5\u8bd6\u8bd7\u8bd8\u8bd9\u8bda\u8bdb\u8bdc\u8bdd\u8bde\u8bdf\u8be0\u8be1\u8be2\u8be3\u8be4\u8be5\u8be6\u8be7\u8be8\u8be9\u4724\u8bea\u8beb\u8bec\u8bed\u8bee\u8bef\u8bf0\u8bf1\u8bf2\u8bf3\u8bf4\u8bf5\u8bf6\u8bf7\u8bf8\u8bf9\u8bfa\u8bfb\u8bfc\u8bfd\u8bfe\u8bff\u8c00\u8c01\u8c02\u8c03\u8c04\u8c05\u8c06\u8c07\u8c08\u8c09\u8c0a\u4725\u8c0b\u8c0c\u8c0d\u8c0e\u8c0f\u8c10\u8c11\u8c12\u8c13\u8c14\u8c15\u8c16\u8c17\u8c18\u8c19\u8c1a\u8c1b\u8c1c\u8c1d\u8c1e\u8c1f\u8c20\u8c21\u8c22\u8c23\u8c24\u8c25\u8c26\u8c27\u4726\u4727\u8c28\u8c29\u8c2a\u8c2b\u8c2c\u4728\u8c2d\u8c2e\u8c2f\u8c30\u8c31\u8c32\u8c33\u8c34\u8c35\u4729\u8c36\u8c37\u472a\u8c38\u472b\u8c39\u8c3a\u8c3b\u472c\u472d\u8c3c\u8c3d\u472e\u8c3e\u472f\u8c3f\u8c40\u8c41\u4730\u8c42\u4731\u8c43\u8c44\u4732\u8c45\u8c46\u4733\u8c47\u8c48\u8c49\u4734\u4735\u8c4a\u8c4b\u4736\u9ff2\u4737\u4738\u4739\u8c4c\u8c4d\u8c4e\u473a\u473b\u473c\u473d\u473e\u473f\u8c4f\u4740\u4741\u4742\u8c50\u8c51\u4743\u8c52\u4744\u8c53\u8c54\u8c55\u8c56\u8c57\u4745\u8c58\u8c59\u8c5a\u8c5b\u8c5c\u8c5d\u4746\u4747\u8c5e\u8c5f\u8c60\u8c61\u4748\u8c62\u8c63\u8c64\u8c65\u8c66\u4749\u8c67\u8c68\u8c69\u8c6a\u474a\u474b\u474c\u8c6b\u8c6c\u8c6d\u8c6e\u474d\u474e\u8c6f\u8c70\u8c71\u8c72\u8c73\u8c74\u8c75\u474f\u8c77\u4750\u4751\u8c76\u4752\u4753\u4754\u4755\u8c78\u8c79\u8c7a\u8c7b\u8c7c\u8c7d\u4756\u4757\u4758\u4759\u8c7e\u8c7f\u8c80\u8c81\u8c82\u8c83\u475a\u475b\u8c84\u8c85\u8c86\u8c87\u8c88\u8c89\u8c8a\u8c8b\u8c8c\u8c8d\u475c\u8c8e\u8c8f\u475d\u475e\u8c90\u8c91\u8c92\u8c93\u475f\u8c94\u8c95\u8c96\u4760\u8c97\u8c98\u8c99\u4761\u4762\u8c9a\u4763\u4764\u4765\u4766\u8c9b\u8c9c\u8c9d\u8c9e\u8c9f\u8ca0\u8ca1\u8ca2\u8ca3\u8ca4\u8ca5\u8ca6\u8ca7\u8ca8\u8ca9\u8caa\u8cab\u8cac\u8cad\u8cae\u4767\u4768\u8caf\u8cb0\u8cb1\u8cb2\u8cb3\u8cb4\u8cb5\u8cb6\u8cb7\u8cb8\u8cb9\u8cba\u8cbb\u8cbc\u8cbd\u8cbe\u8cbf\u8cc0\u8cc1\u4769\u476a\u476b\u476c\u476d\u476e\u476f\u8cc2\u8cc3\u8cc4\u8cc5\u8cc6\u8cc7\u8cc8\u8cc9\u8cca\u8ccb\u8ccc\u8ccd\u8cce\u4770\u4771\u4772\u8ccf\u8cd0\u8cd1\u8cd2\u8cd3\u8cd4\u8cd5\u8cd6\u8cd7\u8cd8\u3562\u4773\u4774\u4775\u8cd9\u8cda\u8cdb\u8cdc\u8cdd\u8cde\u8cdf\u8ce0\u8ce1\u8ce2\u8ce3\u8ce4\u8ce5\u8ce6\u8ce7\u8ce8\u8ce9\u8cea\u8ceb\u8cec\u4776\u4777\u4778\u4779\u477a\u477b\u477c\u477d\u477e\u477f\u8ced\u8cee\u8cef\u8cf0\u8cf1\u8cf2\u8cf3\u8cf4\u8cf5\u4780\u4781\u4782\u4783\u4784\u8cf6\u8cf7\u8cf8\u8cf9\u8cfa\u8cfb\u8cfc\u8cfd\u4785\u4786\u8cfe\u8cff\u8d00\u8d02\u8d03\u8d04\u8d05\u4787\u8d06\u8d07\u8d08\u8d09\u8d0a\u8d0b\u8d0c\u4788\u8d0d\u8d0e\u8d0f\u4789\u8d10\u8d11\u8d12\u8d13\u8d14\u8d15\u8d16\u8d17\u8d18\u478a\u8d19\u8d1a\u478b\u8d1b\u8d1c\u8d1d\u8d1e\u8d1f\u8d20\u8d21\u8d22\u8d23\u8d24\u8d25\u8d26\u8d27\u8d28\u8d29\u8d2a\u8d2b\u8d2c\u8d2d\u8d2e\u8d2f\u8d30\u8d31\u8d32\u8d33\u8d34\u8d35\u8d36\u8d37\u8d38\u8d39\u8d3a\u8d3b\u8d3c\u8d3d\u8d3e\u8d3f\u8d40\u8d41\u8d42\u8d43\u8d44\u8d45\u8d46\u478c\u8d47\u8d48\u8d49\u8d4a\u8d4b\u8d4c\u8d4d\u8d4e\u8d4f\u8d50\u8d51\u8d52\u8d53\u8d54\u8d55\u478d\u478e\u8d56\u8d57\u478f\u4790\u8d58\u8d59\u8d5a\u8d5b\u8d5c\u8d5d\u8d5e\u8d5f\u8d60\u8d61\u8d62\u8d63\u8d64\u4791\u8d65\u8d66\u8d67\u8d68\u8d69\u8d6a\u4792\u8d6b\u4793\u4794\u8d6c\u8d6d\u8d6e\u8d6f\u4795\u8d70\u8d71\u8d72\u8d73\u8d74\u8d75\u9faa\u8d76\u8d77\u8d78\u4796\u4797\u4798\u8d79\u8d7a\u8d7b\u8d7c\u8d7d\u8d7e\u8d7f\ufa23\u4799\u479a\u479b\u479c\u8d80\u8d81\u8d82\u8d83\u8d84\u8d85\u8d86\u8d87\u8d88\u8d89\u8d8a\u8d8b\u479d\u479e\u479f\u47a0\u47a1\u47a2\u47a3\u47a4\u8d8c\u8d8d\u8d8e\u8d8f\u8d90\u8d91\u8d92\u8d93\u8d94\u47a5\u47a6\u47a7\u47a8\u47a9\u47aa\u8d95\u8d96\u8d97\u8d98\u8d99\u8d9a\u47ab\u47ac\u47ad\u47ae\u47af\u47b0\u47b1\u47b2\u8d9b\u8d9c\u8d9d\u8d9e\u8d9f\u8da0\u8da1\u8da2\u8da3\u8da4\u47b3\u47b4\u47b5\u47b6\u47b7\u47b8\u8da5\u8da6\u8da7\u47b9\u47ba\u47bb\u47bc\u8da8\u47bd\u47be\u47bf\u47c0\u47c1\u47c2\u47c3\u47c4\u47c5\u47c6\u8da9\u8daa\u8dab\u8dac\u8dad\u47c7\u8dae\u47c8\u47c9\u47ca\u47cb\u8daf\u8db0\u47cc\u47cd\u47ce\u47cf\u8db1\u47d0\u47d1\u47d2\u8db2\u8db3\u8db4\u47d3\u47d4\u8db5\u8db6\u8db7\u8db8\u47d5\u47d6\u8db9\u8dba\u8dbb\u8dbd\u8dbe\u8dbf\u8dc0\u8dc1\u8dc2\u8dc3\u8dc4\u47d7\u47d8\u47d9\u47da\u47db\u47dc\u47dd\u47de\u8dc5\u8dc6\u8dc7\u8dc8\u8dc9\u8dca\u8dcb\u8dcc\u8dcd\u8dce\u8dcf\u8dd0\u8dd1\u8dd2\u8dd3\u8dd4\u8dd5\u8dd6\u8dd7\u8dd8\u8dd9\u8dda\u8ddb\u8ddc\u8ddd\u8dde\u8df5\u47df\u47e0\u47e1\u47e2\u47e3\u47e4\u47e5\u47e6\u47e7\u47e8\u47e9\u47ea\u47eb\u47ec\u47ed\u8dbc\u8ddf\u8de0\u8de1\u8de2\u8de3\u8de4\u8de5\u8de6\u8de7\u8de8\u8de9\u8dea\u8deb\u8dec\u8ded\u8dee\u8def\u8df0\u8df1\u8df2\u8df3\u8df4\u8df6\u8df7\u8df8\u8df9\u8dfa\u8dfb\u47ee\u47ef\u47f0\u47f1\u47f2\u47f3\u8dfc\u8dfd\u8dfe\u8dff\u8e00\u8e01\u8e02\u8e03\u8e04\u8e05\u8e06\u8e07\u8e08\u8e09\u8e0a\u8e0b\u8e0c\u8e0d\u8e0e\u47f4\u47f5\u47f6\u47f7\u47f8\u47f9\u47fa\u47fb\u47fd\u8e0f\u8e10\u8e11\u8e12\u8e13\u8e14\u8e15\u8e16\u8e17\u8e18\u8e19\u8e1a\u8e1b\u8e1c\u8e1d\u8e1e\u8e1f\u8e20\u8e21\u8e22\u8e23\u8e24\u8e25\u8e26\u8e27\u8e28\u8e29\u8e2a\u8e2c\u8e2d\u8e2e\u8e2f\u8e3a\u47fc\u47fe\u47ff\u4800\u4801\u4802\u4803\u4804\u4805\u4806\u4807\u4808\u4809\u480a\u480b\u480c\u8e2b\u8e30\u8e31\u8e32\u8e33\u8e34\u8e35\u8e36\u8e37\u8e38\u8e39\u8e3b\u8e3c\u8e3d\u8e3e\u8e3f\u8e40\u8e41\u8e42\u8e43\u8e44\u8e45\u480d\u480e\u480f\u4810\u4811\u4812\u4813\u4814\u4815\u4816\u8e46\u8e47\u8e48\u8e49\u8e4a\u8e4b\u8e4c\u8e4d\u8e4e\u8e4f\u8e50\u8e51\u8e52\u8e53\u4817\u4818\u4819\u481a\u481b\u481c\u481d\u8e54\u8e55\u8e56\u8e57\u8e58\u8e59\u8e5a\u8e5b\u8e5c\u8e5d\u8e5e\u8e5f\u8e60\u8e61\u8e62\u8e63\u8e64\u8e65\u8e66\u8e67\u8e6e\u8e80\u481e\u481f\u4820\u4821\u4822\u8e68\u8e69\u8e6a\u8e6b\u8e6c\u8e6d\u8e6f\u8e70\u8e71\u8e72\u8e73\u8e74\u8e75\u8e76\u8e77\u8e78\u8e79\u8e7a\u8e7b\u8e7c\u8e7d\u8e7e\u8e7f\u4823\u4824\u4825\u4826\u4827\u8e81\u8e82\u8e83\u8e84\u8e85\u8e86\u8e87\u8e88\u8e89\u4828\u4829\u8e8a\u8e8b\u8e8c\u8e8d\u8e8e\u8e8f\u482a\u482b\u8e90\u8e91\u8e92\u8e93\u8e94\u8e95\u8e96\u482c\u8e97\u8e98\u8e99\u8e9a\u8e9b\u8e9c\u482d\u482e\u8e9d\u8e9e\u8e9f\u8ea0\u482f\u8ea1\u8ea2\u8ea3\u8ea4\u8ea5\u4830\u8ea6\u8ea7\u8ea9\u8eaa\u8ea8\u4831\u8eab\u8eac\u8ead\u8eae\u8eaf\u8eb0\u4832\u4833\u4834\u4835\u4836\u8eb1\u8eb2\u4837\u4838\u4839\u483a\u8eb3\u8eb4\u8eb5\u8eb6\u8eb7\u8eb8\u8eb9\u8eba\u8ebb\u8ebc\u483b\u8ebd\u8ebe\u483c\u8ebf\u483d\u483e\u8ec0\u8ec1\u8ec2\u8ec3\u8ec4\u8ec5\u483f\u8ec6\u4840\u8ec7\u4841\u8ec8\u8ec9\u8eca\u8ecb\u8ecc\u8ecd\u4842\u4843\u4844\u8ece\u8ecf\u8ed0\u8ed1\u8ed2\u8ed3\u8ed4\u8ed5\u4845\u8ed6\u8ed7\u8ed8\u8ed9\u8eda\u8edb\u8edc\u8edd\u8ede\u8edf\u8ee0\u8ee1\u8ee2\u8ee3\u4846\u4847\u4848\u4849\u484a\u484b\u484c\u484d\u484e\u8ee4\u8ee5\u8ee6\u8ee7\u8ee8\u8ee9\u8eea\u8eeb\u8eec\u8eee\u8eef\u8ef0\u8ef1\u8ef2\u8ef3\u8ef4\u8ef5\u8ef6\u8ef7\u8ef8\u8ef9\u8efa\u8efb\u8efc\u8efd\u9fab\u484f\u4850\u4851\u4852\u8eed\u8efe\u8eff\u8f00\u8f01\u8f02\u8f03\u8f04\u8f05\u8f06\u8f07\u8f08\u8f09\u8f0a\u8f0b\u8f0c\u4853\u4854\u4855\u4856\u4857\u8f0d\u8f0e\u8f0f\u8f10\u8f11\u8f12\u8f13\u8f14\u8f15\u4858\u4859\u485a\u485b\u8f16\u8f17\u8f18\u8f19\u8f1a\u8f1b\u8f1c\u8f1d\u8f1e\u8f1f\u8f20\u8f21\u8f22\u8f23\u8f24\u8f25\u8f26\u8f27\u8f28\u8f29\u8f2a\u8f2b\u8f2c\u485c\u485d\u485e\u485f\u8f2d\u8f2e\u8f2f\u8f30\u8f31\u8f32\u8f33\u8f34\u8f35\u8f36\u8f37\u8f38\u8f39\u8f3a\u8f3b\u8f3c\u4860\u4861\u4862\u4863\u4864\u8f3d\u8f3e\u8f3f\u8f40\u8f41\u8f42\u8f43\u8f44\u8f45\u4865\u4866\u4867\u4868\u4869\u486a\u8f46\u8f47\u8f48\u8f49\u8f4a\u8f4b\u8f4c\u9fc2\u486b\u486c\u486d\u486e\u486f\u4870\u4871\u8f4d\u8f4e\u8f4f\u8f50\u8f51\u8f52\u8f53\u8f54\u4872\u4873\u4874\u4875\u8f55\u8f56\u8f57\u8f58\u8f59\u8f5a\u4876\u8f5b\u8f5c\u8f5d\u8f5e\u8f5f\u4877\u4878\u4879\u8f60\u8f61\u8f62\u487a\u487b\u8f63\u8f64\u487c\u487d\u8f65\u487e\u487f\u8f66\u8f67\u8f68\u8f69\u8f6a\u8f6b\u4880\u8f6c\u8f6d\u8f6e\u8f6f\u8f70\u4881\u8f71\u8f72\u8f73\u8f74\u8f75\u8f76\u8f77\u8f78\u8f79\u8f7a\u8f7b\u4882\u8f7c\u8f7d\u8f7e\u8f7f\u8f80\u8f81\u8f82\u8f83\u8f84\u8f85\u8f86\u8f87\u8f88\u8f89\u8f8a\u8f8b\u8f8c\u8f8d\u8f8e\u8f8f\u8f90\u8f91\u8f92\u8f93\u8f94\u8f95\u8f96\u8f97\u8f98\u8f99\u8f9a\u8f9b\u8f9c\u8f9d\u8f9e\u8f9f\u8fa0\u8fa1\u8fa2\u8fa3\u8fa4\u4883\u8fa5\u8fa6\u8fa7\u8fa8\u8fa9\u8faa\u8fab\u8fac\u8fad\u8fae\u4884\u8faf\u8fb0\u8fb1\u8fb2\u4885\u4886\u8fb3\u4887\u8fb4\u4888\u4889\u8fb5\u8fb6\u8fb7\u8fb8\u8fb9\u8fba\u8fbb\u8fbc\u8fbd\u5de1\u8fbe\u8fbf\u8fc0\u8fc1\u8fc2\u8fc3\u8fc4\u8fc5\u8fc6\u8fc7\u8fc8\u8fc9\u488a\u488b\u8fca\u8fcb\u8fcc\u8fcd\u8fce\u8fcf\u8fd0\u8fd1\u8fd2\u8fd3\u8fd4\u8fd5\u8fd6\u8fd7\u8fd8\u8fd9\u8fda\u8fdb\u8fdc\u8fdd\u8fde\u8fdf\u8fec\ufa24\u488c\u488d\u488e\u488f\u8fe0\u8fe1\u8fe2\u8fe3\u8fe4\u8fe5\u8fe6\u8fe7\u8fe8\u8fe9\u8fea\u8feb\u8fed\u8fee\u8fef\u8ff0\u8ff1\u8ff2\u8ff3\u4890\u4891\u8ff4\u8ff5\u8ff6\u8ff7\u8ff8\u8ff9\u8ffa\u8ffb\u8ffc\u8ffd\u8ffe\u8fff\u9000\u9001\u9002\u9003\u9004\u9005\u9006\u9007\u9008\u9009\u900a\u4892\u4893\u4894\u4895\u4896\u4897\u4898\u900b\u900c\u900d\u900e\u900f\u9010\u9011\u9012\u9013\u9014\u9015\u9016\u9017\u9018\u9019\u901a\u901b\u901c\u901d\u901e\u901f\u9020\u9021\u9022\u9023\u9024\u9025\u9026\u9027\u4899\u489a\u489b\u9028\u9029\u902a\u902b\u902c\u902d\u902e\u902f\u9030\u9031\u9032\u9033\u9034\u9035\u9036\u9037\u9038\u9039\u903a\u903b\u489c\u489d\u489e\u489f\u48a0\u903c\u903d\u903e\u903f\u9040\u9041\u9042\u9043\u9044\u9045\u9046\u9047\u9048\u9049\u904a\u904b\u904c\u904d\u904e\u904f\u9050\u9051\u9052\u9053\u9054\u9055\u9056\u9057\u48a1\u9058\u9059\u905a\u905b\u905c\u905d\u905e\u905f\u9060\u9061\u9062\u9063\u9064\u9065\u48a2\u48a3\u48a4\u48a5\u9066\u9067\u9068\u9069\u906a\u906b\u906c\u906d\u906e\u906f\u9070\u9071\u9073\u48a6\u48a7\u48a8\u48a9\u9072\u9074\u9075\u9076\u9077\u9078\u9079\u907a\u907b\u907c\u9086\u48aa\u48ab\u48ac\u48ad\u907d\u907e\u907f\u9080\u9081\u9082\u9083\u9084\u9085\u9089\u48ae\u9087\u9088\u48af\u48b0\u908a\u908b\u908c\u48b1\u908d\u908e\u908f\u9090\u48b2\u9091\u9092\u9093\u48b3\u9094\u9095\u9096\u9097\u9098\u9099\u909a\u909b\u909c\u909d\u48b4\u909e\u909f\u90a0\u90a1\u90a2\u90a3\u90a4\u90a5\u90a6\u90a7\u90a8\u90a9\u90aa\u90ab\u90ac\u48b5\u48b6\u48b7\u90ad\u90ae\u90af\u90b0\u90b1\u90b2\u90b3\u90b4\u90b5\u90b6\u90b7\u90b8\u90b9\u90ba\u90bb\u48b8\u48b9\u48ba\u90bc\u90bd\u90be\u90bf\u90c0\u90c1\u90c2\u90c3\u90c4\u90c5\u90c6\u90c7\u90c8\u90c9\u90ca\u90cb\u90cc\u90cd\u90ce\u90cf\u90d0\u90d1\u90d3\u48bb\u48bc\u48bd\u48be\u48bf\u48c0\u48c1\u48c2\u90d2\u90d4\u90d5\u90d6\u90d7\u90d8\u90d9\u90da\u90db\u90dc\u90dd\u90de\u90df\u90e0\u90e1\u90e2\u90e3\u90e4\u90e5\u90e6\u90e7\u48c3\u48c4\u48c5\u48c6\u48c7\u48c8\u48c9\u90e8\u90e9\u90ea\u90eb\u90ec\u90ed\u90ee\u90ef\u90f0\u90f1\u90f2\u90f3\u90f4\u90f5\u90f6\u90f7\u90f8\u48ca\u48cb\u48cc\u48cd\u48ce\u48cf\u90f9\u90fa\u90fb\u90fc\u90fd\u90fe\u90ff\u9100\u9101\u9102\u9103\u9104\u9105\u9106\u9107\u9108\u9109\u910a\u48d0\u48d1\u48d2\u910b\u910c\u910d\u910e\u910f\u9110\u9111\u9112\u9113\u9114\u9115\u9116\u9117\u48d3\u48d4\u48d5\u48d6\u48d7\u9118\u9119\u911a\u911b\u911c\u911d\u911e\u911f\u9120\u9121\u9122\u9123\u9124\u9125\u48d8\u48d9\u48da\u48db\u48dc\u9126\u9127\u9128\u9129\u912a\u912b\u912c\u912d\u912e\u912f\u9130\u9131\u9132\u48dd\u48de\u48df\u48e0\u9133\u9134\u9135\u9136\u9137\u48e1\u9138\u9139\u48e2\u913a\u913b\u913c\u913d\u913e\u913f\u9140\u9142\u9141\u9143\u9144\u9145\u9146\u9147\u9148\u48e3\u48e4\u9149\u914a\u914b\u48e5\u48e6\u914c\u914d\u914e\u914f\u9150\u9151\u9152\u48e7\u48e8\u9153\u9154\u9155\u9156\u9157\u9158\u9159\u915a\u915b\u915c\u915d\u915e\u48e9\u48ea\u48eb\u48ec\u48ed\u915f\u9160\u9161\u9162\u9163\u9164\u9165\u48ee\u48ef\u48f0\u48f1\u48f2\u48f3\u9166\u9167\u9168\u9169\u916a\u916b\u916c\u916d\u916e\u916f\u9170\u9171\u48f4\u48f5\u48f6\u48f7\u48f8\u48f9\u9172\u9173\u9174\u9175\u9176\u9177\u9178\u9179\u917a\u917b\u917c\u917d\u917e\u917f\u48fa\u48fb\u9180\u9181\u9182\u9183\u9184\u9185\u9186\u9187\u9188\u9189\u918a\u918b\u918c\u48fc\u48fd\u48fe\u48ff\u918d\u918e\u918f\u9190\u9191\u9192\u9193\u9194\u9195\u9196\u9197\u4900\u4901\u4902\u4903\u4904\u4905\u4906\u4907\u4908\u9198\u9199\u919a\u919b\u919c\u919d\u919e\u919f\u91a0\u91a1\u91a2\u91a3\u91a4\u4909\u490a\u490b\u490c\u91a5\u91a6\u91a7\u91a8\u91a9\u91aa\u91ab\u91ac\u490d\u91ad\u91ae\u91af\u91b0\u91b1\u490e\u490f\u4910\u4911\u4912\u91b2\u91b3\u91b4\u91b5\u91b6\u91b7\u91b8\u91b9\u91ba\u91bb\u4913\u4914\u4915\u4916\u91bc\u91bd\u91be\u91bf\u91c0\u4917\u4918\u91c1\u91c2\u91c3\u91c4\u91c5\u4919\u91c6\u91c7\u91c8\u91c9\u91ca\u91cb\u91cc\u91cd\u91ce\u491a\u91cf\u91d0\u91d1\u91d2\u91d3\u91d4\u91d5\u91d6\u91d7\u91d8\u91d9\u91da\u91db\u91dc\u91dd\u91de\u91df\u91e0\u91e1\u91e2\u491b\u91e3\u91e4\u91e5\u91e6\u91e7\u91e8\u91e9\u91ea\u91eb\u91ec\u91ed\u91ee\u91ef\u91f0\u91f1\u91f2\u91f3\u91f4\u91f5\u91f6\u91f7\u91f8\u91f9\u91fa\u91fb\u91fc\u491c\u91fd\u91fe\u91ff\u9200\u9201\u9202\u9203\u9204\u9205\u9206\u9207\u9208\u9209\u920a\u920b\u920c\u920d\u920e\u920f\u9210\u9211\u9212\u9213\u9214\u9215\u9216\u9217\u9218\u9219\u921a\u921b\u921c\u921d\u921e\u921f\u9220\u9221\u9222\u9223\u9224\u9225\u9226\u9227\u9228\u9229\u922a\u922b\u922c\u491d\u491e\u491f\u4920\u922d\u922e\u922f\u9230\u9231\u9232\u9233\u9234\u9235\u9236\u9237\u9238\u9239\u923a\u923b\u923c\u923d\u923e\u923f\u9240\u9241\u9242\u9243\u9244\u9245\u9246\u9247\u9248\u9249\u924a\u924b\u924c\u924d\u924e\u924f\u9250\u9251\u9252\u9253\u9254\u9255\u9256\u9257\u9258\u9259\u925a\u925b\u925c\u925d\u925e\u925f\u9260\u9261\u9262\u9263\u9264\u9265\u9266\u9267\u9268\u9269\u926a\u926b\u926c\u926d\u926e\u926f\u9270\u9271\u9272\u9273\u9274\u928f\u9faf\u4921\u4922\u4923\u9275\u9276\u9277\u9278\u9279\u927a\u927b\u927d\u927e\u927f\u9280\u9281\u9282\u9283\u9284\u9285\u9286\u9287\u9288\u9289\u928a\u928b\u928c\u928d\u928e\u9290\u9291\u9292\u9293\u9294\u9295\u9296\u9297\u9298\u9299\u929a\u929b\u929c\u929d\u929e\u929f\u92a0\u92a1\u92a2\u92a3\u92a4\u92a5\u92a6\u92a7\u92a8\u92a9\u92aa\u92ab\u92ac\u92ad\u92ae\u92af\u92b0\u92b1\u4924\u4925\u4926\u4927\u4928\u4929\u492a\u92b2\u92b3\u92b4\u92b5\u92b6\u92b7\u92b8\u92b9\u92ba\u92bb\u92bc\u92bd\u92be\u92bf\u92c0\u92c1\u92c2\u92c3\u92c4\u92c5\u92c6\u92c7\u92c8\u92c9\u92ca\u92cb\u92cc\u92cd\u92ce\u92cf\u92d0\u92d1\u92d2\u92d3\u92d4\u92d5\u92d6\u92d7\u92d8\u92d9\u92da\u92db\u92dc\u92dd\u92de\u92df\u92e0\u92e1\u92e2\u92e3\u92e4\u92e5\u92e6\u92e7\u92e8\u92e9\u92ea\u92eb\u92ec\u92ed\u92ee\u92ef\u92f0\u92f1\u92f2\u92f3\u92f4\u92f5\u92f6\ufa27\u492b\u492c\u492d\u492e\u492f\u4930\u4931\u4932\u927c\u92f7\u92f8\u92f9\u92fa\u92fb\u92fc\u92fd\u92fe\u92ff\u9300\u9301\u9302\u9303\u9304\u9305\u9306\u9307\u9308\u9309\u930a\u930b\u930c\u930d\u930e\u930f\u9310\u9311\u9312\u9313\u9314\u9315\u9316\u9317\u9318\u9319\u931a\u931b\u931c\u931d\u931e\u931f\u9320\u9321\u9322\u9323\u9324\u9325\u9326\u9327\u9329\u932a\u932b\u932c\u932d\u932e\u932f\u9330\u9331\u9332\u9333\u9334\u9335\u9336\u9337\u9338\u9339\u933a\u933b\u933c\u933d\u933e\u933f\u9340\u9341\u9342\u9343\u9344\u9345\u9346\u9348\u9fac\ufa28\u4933\u4934\u4935\u4936\u9328\u9347\u9349\u934a\u934b\u934c\u934d\u934e\u934f\u9350\u9351\u9352\u9353\u9354\u9355\u9356\u9357\u9358\u9359\u935a\u935b\u935c\u935d\u935e\u935f\u9360\u9361\u9362\u9363\u9364\u9365\u9366\u9367\u9368\u9369\u936a\u936b\u936c\u936d\u936e\u936f\u9370\u9371\u9372\u9373\u9374\u9375\u9376\u9377\u9378\u9379\u937a\u937b\u937c\u937d\u937e\u937f\u9380\u9381\u9382\u9383\u9384\u9385\u9386\u9387\u4937\u4938\u4939\u493a\u493b\u493c\u9388\u9389\u938a\u938b\u938c\u938d\u938e\u938f\u9390\u9391\u9392\u9393\u9394\u9395\u9396\u9397\u9398\u9399\u939a\u939b\u939c\u939d\u939e\u939f\u93a0\u93a1\u93a2\u93a3\u93a4\u93a5\u93a6\u93a7\u93a8\u93aa\u93ab\u93ac\u93ad\u93ae\u93af\u93b0\u93b1\u93b2\u93b3\u93b4\u93b5\u93b6\u93b7\u93b8\u93b9\u93ba\u93bb\u93bc\u93bd\u93be\u93bf\u9fb2\u493d\u493e\u493f\u4940\u4941\u4942\u4943\u4944\u4945\u4946\u4947\u93a9\u93c0\u93c1\u93c2\u93c3\u93c4\u93c5\u93c6\u93c7\u93c8\u93c9\u93ca\u93cb\u93cc\u93cd\u93ce\u93cf\u93d0\u93d1\u93d2\u93d3\u93d4\u93d5\u93d6\u93d7\u93d8\u93d9\u93da\u93db\u93dc\u93dd\u93de\u93df\u93e0\u93e1\u93e2\u93e3\u93e4\u93e5\u93e6\u93e7\u93e8\u93e9\u93ea\u93eb\u93ec\u93ed\u93ee\u93ef\u93f0\u93f1\u93f2\u93f9\u4948\u4949\u494a\u494b\u494c\u494d\u494e\u494f\u4950\u4951\u4952\u4953\u93f3\u93f5\u93f6\u93f7\u93f8\u93fa\u93fb\u93fc\u93fd\u93fe\u93ff\u9400\u9401\u9402\u9403\u9404\u9405\u9406\u9407\u9408\u9409\u940a\u940b\u940c\u940d\u940e\u940f\u9410\u9411\u9412\u9413\u9414\u9415\u9416\u9417\u9418\u9419\u941a\u941b\u941c\u941d\u941e\u941f\u9420\u9421\u9422\u9423\u9424\u9425\u9426\u9427\u9428\u4954\u4955\u4956\u4957\u4958\u4959\u495a\u495b\u495c\u93f4\u9429\u942a\u942b\u942c\u942d\u942e\u942f\u9430\u9431\u9432\u9433\u9434\u9435\u9436\u9437\u9438\u9439\u943a\u943b\u943c\u943d\u943e\u943f\u9440\u9441\u495d\u495e\u495f\u4960\u4961\u4962\u4963\u4964\u4965\u4966\u9442\u9443\u9444\u9445\u9446\u9447\u9448\u9449\u944a\u944b\u944c\u944d\u944e\u944f\u9450\u9451\u9452\u9453\u9454\u9467\u4967\u4968\u4969\u496a\u496b\u496c\u496d\u496e\u9455\u9456\u9457\u9458\u9459\u945a\u945b\u945c\u945d\u945e\u945f\u9460\u9461\u9462\u9463\u9464\u9465\u9466\u496f\u4970\u4971\u4972\u4973\u4974\u9468\u9469\u946a\u946b\u946c\u4975\u4976\u4977\u946d\u946e\u946f\u9470\u9471\u9472\u9473\u4978\u9474\u9475\u9476\u9477\u9478\u9479\u947a\u947b\u947c\u947d\u947e\u947f\u9480\u9481\u9482\u9483\u9484\u4979\u9485\u9486\u9487\u9488\u9489\u948a\u948b\u948c\u948d\u948e\u948f\u9490\u9491\u9492\u9493\u9494\u9495\u9496\u9497\u9498\u9499\u949a\u949b\u949c\u949d\u949e\u949f\u94a0\u94a1\u94a2\u94a3\u94a4\u94a5\u94a6\u94a7\u94a8\u94a9\u94aa\u94ab\u94ac\u94ad\u94ae\u94af\u497a\u497b\u497c\u94b0\u94b1\u94b2\u94b3\u94b4\u94b5\u94b6\u94b7\u94b8\u94b9\u94ba\u94bb\u94bc\u94bd\u94be\u94bf\u94c0\u94c1\u94c2\u94c3\u94c4\u94c5\u94c6\u94c7\u94c8\u94c9\u94ca\u94cb\u94cc\u94cd\u94ce\u9fed\u497d\u497e\u497f\u94cf\u94d0\u94d1\u94d2\u94d3\u94d4\u94d5\u94d6\u94d7\u94d8\u94d9\u94da\u94db\u94dc\u94dd\u94de\u94df\u94e0\u94e1\u94e2\u94e3\u94e4\u94e5\u94e6\u94e7\u94e8\u94e9\u94ea\u94eb\u94ec\u94ed\u94ee\u94ef\u94f0\u94f1\u94f2\u94f3\u94f4\u94f5\u94f6\u94f7\u4980\u94f8\u94f9\u94fa\u94fb\u94fc\u94fd\u94fe\u94ff\u9500\u9501\u9502\u9503\u9504\u9505\u9506\u9507\u9508\u9509\u950a\u950b\u950c\u950d\u950e\u950f\u9510\u9511\u9512\u9513\u9514\u9515\u9fcf\u4981\u9516\u9517\u9518\u9519\u951a\u951b\u951c\u951d\u951e\u951f\u9520\u9521\u9522\u9523\u9524\u9525\u9526\u9527\u9528\u9529\u952a\u952b\u952c\u952d\u952e\u952f\u9530\u9531\u9532\u9533\u9534\u9535\u9536\u9537\u9538\u9539\u953a\u953b\u953c\u953d\u953e\u953f\u9540\u9541\u9542\u9543\u9544\u9545\u9546\u9547\u9548\u9549\u954a\u954b\u954c\u954d\u954e\u954f\u9550\u9551\u9552\u9553\u9554\u9555\u9fd4\u4982\u9556\u9557\u9558\u9559\u955a\u955b\u955c\u955d\u955e\u955f\u9560\u9561\u9562\u9563\u9564\u9565\u9566\u9567\u9568\u9569\u956a\u956b\u4983\u4984\u4985\u956c\u956d\u956e\u956f\u9570\u9571\u9572\u9573\u9574\u9575\u9576\u4986\u9577\u9578\u9579\u4987\u957a\u957b\u4988\u4989\u498a\u957c\u498b\u957d\u957e\u957f\u9580\u9581\u9582\u9583\u9584\u9585\u9586\u9587\u9588\u9589\u958a\u498c\u498d\u958b\u958c\u958d\u958e\u958f\u9590\u9591\u9592\u9593\u9594\u9595\u9596\u9597\u498e\u498f\u4990\u4991\u9598\u9599\u959a\u959b\u959c\u959d\u959e\u959f\u95a0\u4992\u4993\u4994\u95a1\u95a2\u95a3\u95a4\u95a5\u95a6\u95a7\u95a8\u95a9\u95aa\u4995\u4996\u4997\u4998\u4999\u499a\u499b\u95ab\u95ac\u95ad\u95ae\u95af\u95b0\u95b1\u95b2\u95b3\u95b4\u499c\u499d\u499e\u499f\u95b5\u95b6\u95b8\u95b9\u95ba\u95bb\u95bc\u95bd\u95be\u95bf\u95c0\u95c1\u95c2\u49a0\u49a1\u49a2\u49a3\u49a4\u49a5\u49a6\u49a7\u49a8\u95b7\u95c3\u95c4\u95c5\u95c6\u95c7\u95c8\u95c9\u95ca\u95cb\u95cc\u95cd\u95ce\u95cf\u49a9\u49aa\u49ab\u49ac\u49ad\u49ae\u49af\u95d0\u95d1\u95d2\u95d3\u95d4\u95d5\u95d6\u95d7\u95d8\u95d9\u95da\u95db\u95dc\u95dd\u49b0\u95de\u95df\u95e0\u95e1\u49b1\u95e2\u95e3\u95e4\u95e5\u95e6\u49b2\u49b3\u49b4\u95e7\u49b5\u95e8\u95e9\u95ea\u95eb\u95ec\u95ed\u95ee\u95ef\u95f0\u95f1\u95f2\u95f3\u95f4\u95f5\u95f6\u95f7\u95f8\u95f9\u95fa\u95fb\u95fc\u95fd\u95fe\u95ff\u9600\u9601\u9602\u49b6\u9603\u9604\u9605\u9606\u49b7\u9607\u9608\u9609\u960a\u960b\u960c\u960d\u960e\u960f\u9610\u9611\u9612\u9613\u9614\u9615\u49b8\u9616\u9617\u9618\u9619\u961a\u961b\u961c\u961d\u961e\u961f\u49b9\u49ba\u9620\u9621\u9622\u9623\u9624\u49bb\u9625\u9626\u9627\u9628\u9629\u962a\u962b\u962c\u962d\u962e\u962f\u9630\u9631\u9632\u9633\u9634\u9635\u9636\u49bc\u49bd\u49be\u49bf\u49c0\u9637\u9638\u9639\u963a\u963b\u963c\u963d\u963e\u963f\u9640\u9641\u9642\u9643\u9644\u9645\u9646\u9647\u9648\u9649\u49c1\u49c2\u49c3\u964a\u964b\u964c\u964d\u964e\u964f\u9650\u9651\u9652\u9653\u9654\u9655\u49c4\u49c5\u49c6\u49c7\u49c8\u9656\u9657\u9658\u9659\u965b\u965c\u965d\u965e\u965f\u9660\u9661\u9662\u9663\u9664\u9665\u9666\u9667\u9668\u9669\u49c9\u49ca\u49cb\u49cc\u49cd\u49ce\u49cf\u965a\u966a\u966b\u966c\u966d\u966e\u966f\u9670\u9671\u9672\u9673\u9674\u9675\u9676\u9677\u9678\u9679\u967a\u49d0\u49d1\u49d2\u49d3\u49d4\u49d5\u49d6\u967b\u967c\u967d\u967e\u967f\u9680\u9681\u9682\u9683\u9684\u9685\u9686\u9687\u9688\u9689\u968a\u968b\u968c\u968d\u968e\u968f\u9690\u49d7\u49d8\u49d9\u9691\u9692\u9693\u9694\u9695\u9696\u9697\u9698\u9699\ufa29\u49da\u49db\u49dc\u49dd\u49de\u49df\u969a\u969b\u969c\u969d\u969e\u969f\u96a0\u96a1\u49e0\u49e1\u49e2\u49e3\u96a2\u96a3\u96a4\u96a5\u49e4\u49e5\u49e6\u49e9\u96a6\u96a7\u96a8\u96a9\u96aa\u96ab\u49e7\u49e8\u49ea\u96ac\u96ad\u96ae\u96af\u96b0\u96b1\u96b2\u49eb\u49ec\u49ed\u96b3\u96b4\u49ee\u96b5\u49ef\u49f0\u9fe7\u96b6\u96b7\u96b8\u96b9\u96ba\u96bb\u96bc\u96bd\u96be\u49f1\u96bf\u96c0\u49f2\u49f3\u96c1\u96c2\u96c3\u96c4\u96c5\u96c6\u96c7\u96c8\u49f4\u49f5\u49f6\u96c9\u96ca\u96cb\u96cc\u96cd\u96ce\u96cf\u49f7\u49f8\u49f9\u49fa\u96d0\u96d1\u96d2\u49fb\u96d3\u49fc\u49fd\u96d4\u96d5\u49fe\u49ff\u96d6\u4a01\u4a02\u96d7\u96d8\u96d9\u96da\u96db\u96dc\u96dd\u96de\u96df\u96e0\u4a03\u96e1\u96e2\u96e3\u4a04\u4a05\u4a06\u4a07\u96e4\u4a08\u96e5\u96e6\u4a09\u96e7\u4a0a\u96e8\u96e9\u96ea\u96eb\u4a0b\u96ec\u96ed\u96ee\u96ef\u96f0\u96f1\u96f2\u96f3\u4a0c\u4a0d\u96f4\u96f5\u96f6\u96f7\u96f8\u96f9\u96fa\u96fb\u96fc\u96fd\u96fe\u4a0e\u96ff\u9700\u9701\u4a0f\u4a10\u4a11\u4a12\u4a13\u4a14\u4a15\u4a16\u9702\u9703\u9704\u9705\u9706\u9707\u9708\u9709\u970a\u4a17\u4a18\u4a19\u970b\u970c\u970d\u970e\u970f\u9710\u9711\u9712\u9713\u9714\u9715\u9716\u9717\u4a1a\u4a1b\u4a1c\u4a1d\u9718\u9719\u971a\u971b\u971c\u971d\u971e\u971f\u9720\u4a1e\u4a1f\u4a20\u4a21\u4a22\u4a23\u4a24\u9721\u9722\u9723\u9724\u9725\u4a25\u4a26\u4a27\u4a28\u4a29\u4a2a\u9726\u9727\u9728\u9729\u972a\u972b\u972c\u972d\u4a2b\u4a2c\u4a2d\u4a2e\u4a2f\u972e\u972f\u9730\u9731\u9732\u9733\u9734\u4a30\u4a31\u9735\u9736\u9737\u9738\u9739\u973a\u973b\u973c\u973d\u973e\u973f\u9740\u4a32\u4a33\u4a34\u9741\u4a35\u4a36\u9742\u9743\u9744\u9745\u9746\u9747\u9748\u4a37\u9749\u4a38\u974a\u974b\u974c\u974d\u974e\u9fe8\u4a39\u974f\u4a3a\u9750\u4a3b\u9751\u9752\u9753\u9754\u9755\u9756\u9757\u9758\u9759\u975a\u975b\u975c\u975d\u4a3c\u975e\u4a3d\u4a3e\u975f\u4a3f\u9760\u4a40\u9761\u4a41\u9762\u9763\u4a42\u4a43\u4a44\u9764\u4a45\u4a46\u4a47\u9765\u9766\u4a48\u4a49\u4a4a\u4a4b\u9767\u4a4c\u4a4d\u4a4e\u9768\u4a4f\u9769\u976a\u976b\u976c\u976d\u976e\u976f\u9770\u9771\u4a50\u4a51\u4a52\u9772\u9773\u9774\u9775\u9776\u9777\u9778\u9779\u4a53\u4a54\u4a55\u4a56\u4a57\u4a58\u977a\u977b\u977c\u977d\u977e\u977f\u9780\u9781\u9782\u9783\u9784\u9785\u9786\u4a59\u4a5a\u4a5b\u4a5c\u4a5d\u4a5e\u9787\u9788\u9789\u978a\u978b\u978c\u978d\u978e\u978f\u9790\u9791\u9792\u4a5f\u9793\u9794\u9795\u9796\u9797\u9798\u9799\u4a60\u4a61\u4a62\u4a63\u4a64\u4a65\u4a66\u4a67\u979a\u979b\u979c\u979d\u979e\u979f\u97a0\u97a1\u4a68\u4a69\u4a6a\u4a6b\u4a6c\u4a6d\u4a6e\u97a2\u97a3\u97a4\u97a5\u97a6\u97a7\u97a8\u97a9\u97aa\u97ab\u97ac\u97ad\u97ae\u97af\u97b0\u4a6f\u4a70\u4a71\u4a72\u4a73\u4a74\u4a75\u97b1\u97b2\u97b3\u97b4\u97b5\u97b6\u97b7\u4a76\u4a77\u4a78\u4a79\u4a7a\u4a7b\u97b8\u97b9\u97ba\u97bb\u4a7c\u4a7d\u4a7e\u97bc\u97bd\u97be\u97bf\u4a7f\u4a80\u4a81\u4a82\u4a83\u4a84\u97c0\u97c1\u97c2\u97c3\u4a85\u4a86\u97c4\u97c5\u4a87\u4a88\u97c6\u97c7\u97c8\u4a89\u4a8a\u4a8b\u97c9\u4a8c\u4a8d\u4a8e\u97ca\u97cb\u97cc\u4a8f\u97cd\u97ce\u4a90\u4a91\u4a92\u4a93\u97cf\u97d0\u97d1\u97d2\u4a94\u97d3\u97d4\u97d5\u4a95\u97d6\u97d7\u97d8\u97d9\u97da\u4a96\u4a97\u4a98\u97dc\u97dd\u97de\u97df\u4a99\u4a9a\u97db\u97e0\u97e1\u97e2\u4a9b\u97e3\u4a9c\u4a9d\u97e4\u97e5\u97e6\u97e7\u97e8\u97e9\u97ea\u97eb\u97ec\u97ed\u97ee\u4a9e\u97ef\u4a9f\u97f0\u97f1\u4aa0\u97f2\u4aa1\u4aa2\u4aa3\u4aa4\u4aa5\u97f3\u7adf\u7ae0\u4aa6\u4aa7\u4aa8\u97f4\u97f5\u4aa9\u97f6\u97f7\u4aaa\u97f8\u4aab\u4aac\u97f9\u97fa\u4aad\u97fb\u97fc\u4aae\u4aaf\u97fd\u97fe\u97ff\u4ab0\u9800\u9801\u9802\u9803\u9804\u9805\u9806\u9807\u9808\u9809\u4ab1\u4ab2\u980a\u980b\u980c\u980d\u980e\u980f\u9810\u9811\u9812\u9813\u9819\u4ab3\u4ab4\u4ab5\u9814\u9815\u9816\u9817\u9818\u981a\u4ab6\u4ab7\u4ab8\u4ab9\u4aba\u4abb\u4abc\u4abd\u4abe\u981b\u981c\u981d\u981e\u981f\u9820\u9821\u9822\u9823\u9826\u9827\u9828\u9829\u982a\u982b\u982c\u4abf\u4ac0\u4ac1\u4ac2\u9824\u9825\u982d\u982e\u982f\u9830\u9831\u9832\u9833\u9834\u9835\u9836\u9837\u9838\u9839\u983a\u983b\u983c\u983d\u4ac3\u4ac4\u4ac5\u4ac6\u4ac7\u4ac8\u4ac9\u4aca\u983f\u9840\u9841\u9842\u9843\u9844\u9845\u9846\u9847\u9848\u9849\u984a\u4acb\u4acc\u4acd\u4ace\u4acf\u4ad0\u4ad1\u4ad2\u4ad3\u983e\u984b\u984c\u984d\u984e\u984f\u9850\u9851\u9852\u9853\u9854\u9855\u4ad4\u4ad5\u4ad6\u4ad7\u4ad8\u4ad9\u4ada\u4adb\u4adc\u4add\u9856\u9857\u9858\u9859\u985a\u985b\u985c\u985d\u985e\u4ade\u4adf\u4ae0\u4ae1\u4ae2\u4ae3\u4ae4\u4ae5\u4ae6\u4ae7\u985f\u9860\u9861\u9862\u9863\u4ae8\u4ae9\u4aea\u4aeb\u9864\u9865\u9866\u9867\u9868\u4aec\u4aed\u4aee\u4aef\u4af0\u4af1\u9869\u986a\u986b\u4af2\u4af3\u4af4\u986c\u986d\u986e\u986f\u9870\u4af5\u4af6\u9871\u9872\u4af7\u9873\u9874\u9875\u9876\u9877\u9878\u9879\u987a\u987b\u987c\u987d\u987e\u987f\u9880\u9881\u9882\u9883\u9884\u9885\u9886\u9887\u9888\u9889\u988a\u988b\u988c\u988d\u988e\u988f\u9890\u9891\u9892\u9893\u9894\u9895\u9896\u9897\u9898\u9899\u989a\u989b\u989c\u989d\u989e\u989f\u98a0\u98a1\u98a2\u98a3\u98a4\u98a5\u98a6\u98a7\u98a8\u4af8\u98a9\u98aa\u4af9\u98ab\u98ac\u4afa\u4afb\u4afc\u4afd\u98ad\u98ae\u98af\u98b0\u98b1\u4afe\u4aff\u4b00\u4b01\u4b02\u4b03\u98b2\u98b3\u4b04\u4b05\u98b4\u98b5\u4b06\u4b07\u4b08\u4b09\u4b0a\u98b6\u98b7\u4b0b\u4b0c\u4b0d\u4b0e\u4b0f\u4b10\u98b8\u98b9\u98ba\u4b11\u4b12\u4b13\u4b14\u4b15\u4b16\u4b17\u98bb\u98bc\u98bd\u98be\u98bf\u98c0\u4b18\u4b19\u4b1a\u98c1\u98c2\u98c3\u98c4\u4b1b\u4b1c\u98c5\u98c6\u98c7\u98c8\u98c9\u98ca\u4b1d\u98cb\u4b1e\u4b1f\u98cc\u98cd\u98ce\u98cf\u98d0\u98d1\u98d2\u98d3\u98d4\u98d6\u98d5\u98d7\u98d8\u98d9\u98da\u98db\u4b20\u4b21\u98dc\u98dd\u98de\u98df\u98e0\u98e1\u98e2\u98e3\u98e4\u4b22\u4dbf\u98e5\u98e6\u98e7\u98e8\u4b23\u4b24\u4b25\u98e9\u98ea\u98eb\u98ec\u98ed\u98ee\u98ef\u98f0\u98f1\u98f2\u4b26\u4b27\u4b28\u4b29\u4b2a\u98f3\u98f4\u98f5\u98f6\u98f7\u98f8\u98f9\u98fb\u98fc\u98fd\u98fe\u98ff\u4b2b\u4b2c\u4b2d\u4b2e\u4b2f\u4b30\u4b31\u4b32\u4b33\u4b34\u98fa\u9900\u9901\u9902\u9903\u9904\u9905\u9906\u9907\u9908\u9909\u990a\u990b\u990c\u990d\u990e\u990f\u4b35\u4b36\u4b37\u4b38\u4b39\u4b3a\u4b3b\u9910\u9911\u9912\u9913\u9914\u9915\u9916\u9917\u9918\u9919\u991d\u4b3c\u4b3d\u4b3e\u4b3f\u4b40\u4b41\u4b42\u991a\u991b\u991c\u991e\u991f\u9920\u9921\u9922\u9923\u9924\u9925\u9926\u9927\u9928\u9929\u4b43\u4b44\u4b45\u4b46\u4b47\u992a\u992b\u992c\u992d\u992e\u992f\u9930\u9931\u9932\u9933\u9934\u9935\u9937\u4b48\u4b49\u4b4a\u4b4b\u4b4c\u4b4d\u4b4e\u4b4f\u9936\u9938\u9939\u993a\u993b\u993c\u993d\u993e\u993f\u9940\u9941\u9942\u9943\u4b50\u4b51\u4b52\u4b53\u4b54\u9944\u9945\u9946\u9947\u9948\u9949\u4b55\u4b56\u4b57\u994a\u994b\u994c\u994d\u994e\u994f\u9950\u9951\u9952\u9953\u4b58\u4b59\u4b5a\u4b5b\u4b5c\u9954\u9955\u9956\u9957\u9958\u9959\u4b5d\u4b5e\u4b5f\u4b60\u995a\u995b\u995c\u4275\u4b61\u4b62\u4b63\u4b64\u4b65\u995d\u995e\u995f\u4b66\u4b67\u4b68\u9960\u9961\u4b69\u9962\u9963\u9964\u9965\u9966\u9967\u9968\u9969\u996a\u996b\u996c\u996d\u996e\u996f\u9970\u9971\u9972\u9973\u9974\u9975\u9976\u9977\u9978\u9979\u997a\u997b\u997c\u997d\u997e\u997f\u9980\u9981\u9982\u9983\u9984\u9985\u9986\u9987\u9988\u998a\u998b\u9989\u998c\u998d\u998e\u998f\u9990\u9991\u9992\u9993\u9994\u4b6a\u9995\u9996\u9997\u4b6b\u4b6c\u9998\u4b6d\u4b6e\u9999\u999a\u999b\u999c\u999d\u4b6f\u999e\u999f\u99a0\u99a1\u99a2\u99a3\u4b70\u4b71\u4b72\u99a4\u99a5\u99a6\u99a7\u99a8\u99a9\u99aa\u99ab\u4b73\u99ac\u99ad\u99ae\u4b74\u99af\u99b0\u99b1\u99b2\u99b3\u99b4\u99b5\u4b75\u4b76\u99b6\u99b7\u99b8\u99b9\u99ba\u99bb\u99bc\u99bd\u99be\u99bf\u99c0\u99c1\u99c2\u99c3\u99c4\u99c5\u99c6\u99c7\u4b77\u4b78\u4b79\u4b7a\u4b7b\u4b7c\u4b7d\u4b7e\u99c8\u99c9\u99ca\u99cb\u99cc\u99cd\u99ce\u99cf\u99d0\u99d1\u99d2\u99d3\u99d4\u99d5\u99d6\u99d7\u99d8\u99d9\u99da\u99db\u99dc\u99dd\u99de\u99df\u99e0\u4b7f\u4b80\u4b81\u4b82\u4b83\u4b84\u4b85\u99e1\u99e2\u99e3\u99e4\u99e5\u99e6\u99e7\u99e8\u99e9\u99ea\u99eb\u99ec\u99ed\u99ee\u99ef\u99f0\u99f1\u99f2\u4b86\u4b87\u4b88\u4b89\u4b8a\u4b8b\u4b8c\u4b8d\u99f4\u99f5\u99f6\u99f7\u99f8\u99f9\u99fa\u99fb\u99fc\u99fd\u99fe\u99ff\u9a00\u9a01\u9a02\u9a03\u4b8e\u4b8f\u4b90\u4b91\u4b92\u99f3\u9a04\u9a05\u9a06\u9a07\u9a08\u9a09\u9a0a\u9a0b\u9a0c\u9a0d\u9a0e\u9a0f\u9a10\u9a11\u9a12\u9a13\u4b93\u4b94\u4b95\u4b96\u4b97\u4b98\u4b99\u4b9a\u4b9b\u9a14\u9a15\u9a16\u9a17\u9a18\u9a19\u9a1a\u9a1b\u9a1c\u9a1d\u9a1e\u9a1f\u9a20\u9a21\u9a22\u9a23\u9a24\u9a25\u9a26\u9a27\u9a28\u4b9c\u4b9d\u4b9e\u4b9f\u4ba0\u4ba1\u4ba2\u9a29\u9a2a\u9a2b\u9a2c\u9a2d\u9a2e\u9a2f\u9a30\u9a31\u9a32\u9a33\u9a34\u9a35\u9a36\u9a37\u9a38\u4ba3\u4ba4\u4ba5\u4ba6\u4ba7\u4ba8\u4ba9\u9a39\u9a3a\u9a3b\u9a3c\u9a3d\u9a3e\u9a3f\u9a40\u9a41\u9a42\u9a43\u9a44\u9a45\u9a46\u9a47\u9fad\u4baa\u4bab\u4bac\u4bad\u4bae\u4baf\u4bb0\u4bb1\u9a48\u9a49\u9a4a\u9a4b\u9a4c\u9a4d\u9a4e\u9a4f\u9a50\u9a51\u9a52\u9a53\u9a54\u9a55\u4bb2\u4bb3\u4bb4\u4bb5\u4bb6\u9a56\u9a57\u9a58\u9a59\u9a5a\u9a5b\u9a5c\u4bb7\u4bb8\u4bb9\u9a5d\u9a5e\u9a5f\u4bba\u4bbb\u4bbc\u4bbd\u9a60\u9a61\u9a62\u9a63\u4bbe\u9a64\u9a65\u9a66\u9a67\u9fae\u4bbf\u9a68\u9a69\u4bc0\u9a6a\u4bc1\u9a6b\u4bc2\u9a6c\u9a6d\u9a6e\u9a6f\u9a70\u9a71\u9a72\u9a73\u9a74\u9a75\u9a76\u9a77\u9a78\u9a79\u9a7a\u9a7b\u9a7c\u9a7d\u9a7e\u9a7f\u9a80\u9a81\u9a82\u9a83\u9a84\u9a85\u9a86\u9a87\u9a88\u9a89\u4bc3\u9a8a\u9a8b\u9a8c\u9a8d\u9a8e\u9a8f\u4bc4\u9a90\u9a91\u9a92\u9a93\u9a94\u9a95\u9a96\u9a97\u9a98\u9a99\u9a9a\u9a9b\u9a9c\u9a9d\u9a9e\u9a9f\u4bc5\u9aa0\u9aa1\u9aa2\u9aa3\u9aa4\u9aa5\u9aa6\u9aa7\u9aa8\u4bc6\u9aa9\u4bc7\u9aaa\u9aab\u9aac\u9aad\u9aae\u9aaf\u9ab0\u9ab1\u4bc8\u4bc9\u9ab2\u9ab3\u9ab4\u9ab5\u9ab6\u9ab7\u4bca\u4bcb\u4bcc\u4bcd\u4bce\u9ab8\u9ab9\u9aba\u9abb\u9abc\u4bcf\u4bd0\u4bd1\u4bd2\u4bd3\u4bd4\u9abd\u9abe\u4bd5\u4bd6\u4bd7\u4bd8\u4bd9\u4bda\u9abf\u9ac0\u9ac1\u4bdb\u4bdc\u9ac2\u9ac3\u9ac4\u9ac5\u4bdd\u4bde\u4bdf\u4be0\u9ac6\u9ac7\u9ac8\u9ac9\u9aca\u9acb\u9acc\u4be1\u9acd\u9ace\u9acf\u4be2\u9ad0\u4be3\u9ad1\u9ad2\u9ad3\u9ad4\u4be4\u9ad5\u4be5\u9ad6\u4be6\u9ad7\u9ad8\u9ad9\u4be7\u4be8\u9ada\u4be9\u9adb\u9adc\u4bea\u9add\u4beb\u9ade\u4bec\u9adf\u9ae0\u4bed\u4bee\u9ae1\u9ae2\u9ae3\u9ae4\u9ae5\u9ae6\u9ae7\u9ae8\u9ae9\u9aea\u4bef\u4bf0\u4bf1\u4bf2\u4bf3\u4bf4\u9aeb\u9aec\u9aed\u9aee\u9aef\u9af0\u9af1\u9af2\u9af3\u9af4\u4bf5\u4bf6\u9af5\u9af6\u9af7\u9af8\u9af9\u9afa\u9afb\u9b07\u4bf7\u4bf8\u9afc\u9afd\u9afe\u9aff\u9b00\u9b01\u9b02\u4bf9\u4bfa\u4bfb\u4bfc\u9b03\u9b04\u9b05\u9b06\u9b08\u4bfd\u4bfe\u4bff\u4c00\u4c01\u4c02\u4c03\u9b09\u9b0a\u9b0b\u9b0c\u9b0d\u9b0e\u9b0f\u4c04\u4c05\u4c06\u4c07\u9b10\u9b11\u9b12\u9b13\u4c08\u4c09\u4c0a\u4c0b\u9b14\u9b15\u9b16\u9b17\u9b18\u9b1d\u4c0c\u4c0d\u9b19\u9b1a\u9b1b\u9b1c\u4c0e\u9b1e\u9b1f\u9b20\u9b21\u9b22\u4c0f\u4c10\u4c11\u4c12\u9b23\u4c13\u4c14\u4c15\u9b24\u4c16\u9b25\u9b26\u9b27\u9b28\u9b29\u9b2a\u4c17\u4c18\u9b2b\u9b2c\u9b2d\u9b2e\u9b2f\u9b30\u9b31\u9b32\u4c19\u4c1a\u4c1b\u9b33\u4c1c\u9b34\u9b35\u9b36\u9b37\u9b38\u9b39\u9b3a\u9b3b\u4c1d\u4c1e\u9b3c\u9b3d\u9b3e\u9b3f\u9b40\u9b41\u9b42\u4c1f\u9b43\u9b44\u9b45\u9b46\u4c20\u4c21\u9b47\u4c22\u4c23\u9b48\u9b49\u9b4a\u9b4b\u9b4c\u9b4d\u9b4e\u9b4f\u4c24\u4c25\u4c26\u4c27\u4c28\u4c29\u9b50\u4c2a\u9b51\u9b52\u9b53\u9b54\u9b55\u9b56\u4c2b\u4c2c\u4c2d\u4c2e\u9b57\u9b58\u9b59\u4c2f\u4c30\u4c31\u9b5a\u4c32\u9b5b\u9b5c\u9b5d\u9b5e\u4c33\u9b5f\u9b60\u9b61\u9b62\u4c34\u4c35\u4c36\u9b63\u9b64\u9b65\u9b66\u9b67\u9b68\u9b69\u9b6a\u9b6b\u9b6c\u9b6d\u9b6e\u9b6f\u9b70\u9b71\u9b72\u9b73\u9b74\u9b75\u9b76\u9b77\u9b78\u9b79\u4c37\u4c38\u4c39\u4c3a\u4c3b\u4c3c\u4c3d\u4c3e\u9b7a\u9b7b\u9b7c\u9b7d\u9b7e\u9b7f\u9b80\u9b81\u9b82\u9b83\u9b84\u9b85\u9b86\u9b87\u9b88\u9b89\u9b8a\u9b8b\u9b8c\u9b8d\u9b8e\u9b8f\u9b90\u9b91\u9b92\u9b93\u9b94\u9b95\u9b96\u9b97\u9b98\u9ba3\u4c3f\u4c40\u4c41\u4c42\u4c43\u4c44\u4c45\u4c46\u4c47\u4c48\u4c49\u9b99\u9b9a\u9b9b\u9b9c\u9b9d\u9b9e\u9b9f\u9ba0\u9ba1\u9ba2\u9ba4\u9ba5\u9ba6\u9ba7\u9ba8\u9ba9\u9baa\u9bab\u9bac\u9bad\u9bae\u9baf\u9bb0\u9bb1\u9bb2\u9bb3\u9bb4\u9bba\u4c4a\u4c4b\u4c4c\u4c4d\u4c4e\u9bb5\u9bb6\u9bb7\u9bb8\u9bb9\u9bbb\u9bbc\u9bbd\u9bbe\u9bbf\u9bc0\u9bc1\u9bc2\u9bc3\u9bc4\u9bc5\u9bc6\u9bc7\u9bc8\u9bc9\u9bca\u9bcb\u9bcc\u9bcd\u9bce\u9bcf\u9bd0\u9bd1\u9bd2\u9bd3\u9bfd\u4c4f\u4c50\u4c51\u4c52\u4c53\u4c54\u4c55\u4c56\u4c57\u4c58\u9bd4\u9bd5\u9bd6\u9bd7\u9bd8\u9bd9\u9bda\u9bdb\u9bdc\u9bdd\u9bde\u9bdf\u9be0\u9be1\u9be2\u9be3\u9be4\u9be5\u9be6\u9be7\u9be8\u9be9\u9bea\u9beb\u9bec\u9bed\u9bee\u9bef\u9bf0\u9bf1\u9bf2\u9bf3\u9bf4\u9bf5\u9bfb\u4c59\u4c5a\u4c5b\u4c5c\u4c5d\u4c5e\u4c5f\u4c60\u4c61\u4c62\u4c63\u4c64\u4c65\u4c66\u4c67\u4c68\u4c69\u4c6a\u9bf6\u9bf7\u9bf8\u9bf9\u9bfa\u9bfc\u9bfe\u9bff\u9c00\u9c01\u9c02\u9c03\u9c04\u9c05\u9c06\u9c07\u9c08\u9c09\u9c0a\u9c0b\u9c0c\u9c0d\u9c0e\u9c0f\u9c10\u9c11\u9c12\u9c13\u9c14\u9c15\u9c16\u9c17\u9c18\u9c19\u9c1a\u9c1b\u9c20\u4c6b\u4c6c\u4c6d\u4c6e\u4c6f\u4c70\u4c71\u4c72\u4c73\u4c74\u9c1c\u9c1d\u9c1e\u9c1f\u9c21\u9c22\u9c23\u9c24\u9c25\u9c26\u9c27\u9c28\u9c29\u9c2a\u9c2b\u9c2c\u9c2d\u9c2e\u9c2f\u9c30\u9fd0\u4c75\u4c76\u4c77\u4c78\u4c79\u4c7a\u4c7b\u4c7c\u4c7d\u9c31\u9c32\u9c33\u9c34\u9c35\u9c36\u9c37\u9c38\u9c39\u9c3a\u9c3b\u9c3c\u9c3d\u9c3e\u9c3f\u9c40\u9c41\u9c42\u9c43\u9c44\u9c45\u9c46\u9c47\u9c48\u9de0\u9ff3\u4c7e\u4c7f\u4c80\u4c81\u4c82\u4c83\u4c84\u4c85\u4c86\u4c87\u4c88\u9c49\u9c4a\u9c4b\u9c4c\u9c4d\u9c4e\u9c4f\u9c50\u9c51\u9c52\u9c53\u9c54\u9c55\u9c56\u9c57\u9c58\u9c59\u9c5a\u9c5b\u4c89\u4c8a\u4c8b\u4c8c\u4c8d\u4c8e\u4c8f\u9c5c\u9c5d\u9c5e\u9c5f\u9c60\u9c61\u9c62\u9c63\u9c64\u9c65\u9c66\u9c67\u9c69\u9c6a\u9c6b\u4c90\u4c91\u4c92\u4c93\u4c94\u4c95\u9c68\u9c6c\u9c6d\u9c6e\u9c6f\u9c70\u4c96\u4c97\u4c98\u9c71\u9c72\u9c73\u9c74\u9c75\u9c76\u4c99\u9c77\u9c78\u4c9a\u4c9b\u9c79\u9fe9\u9c7a\u9c7b\u4c9c\u9c7c\u9c7d\u9c7e\u9c7f\u9c80\u9c81\u9c82\u9c83\u9fd5\u4c9d\u9c84\u9c85\u9c86\u9c87\u9c88\u9c89\u9c8a\u9c8b\u9c8c\u9c8d\u9c8e\u9c8f\u9c90\u9ff4\u4c9e\u4c9f\u9c91\u9c92\u9c93\u9c94\u9c95\u9c96\u9c97\u9c98\u9c99\u9c9a\u9c9b\u9c9c\u9c9d\u9c9e\u9c9f\u9ca0\u9ca1\u9ca2\u9ca3\u9ca4\u9ca5\u9ca6\u9ca7\u9ca8\u9ca9\u9caa\u9cab\u9cac\u9ff5\u9ff6\u9cad\u9cae\u9caf\u9cb0\u9cb1\u9cb2\u9cb3\u9cb4\u9cb5\u9cb6\u9cb7\u9cb8\u9cb9\u9cba\u9cbb\u9cbc\u9cbd\u9cbf\u9cc0\u9cc1\u9cc2\u9cc3\u9cc4\u9cc5\u9cc6\u9cc7\u9cc8\u9cc9\u9cca\u9ccb\u9ff7\u4ca0\u4ca1\u9cbe\u9ccc\u9ccd\u9cce\u9ccf\u9cd0\u9cd1\u9cd2\u4ca2\u4ca3\u4ca4\u9cd3\u9cd4\u9cd5\u9cd6\u9cd7\u9cd8\u9cd9\u9cda\u9cdb\u9ff8\u9ff9\u9cdc\u9cdd\u9cde\u9cdf\u9ce0\u9ce1\u9ce2\u9ce3\u9ce4\u9ce5\u9ce6\u9ce7\u9ce8\u9ce9\u9cea\u9ceb\u9cec\u9ced\u9cee\u9cef\u9cf0\u4ca5\u9cf1\u9cf2\u9cf3\u9cf4\u9cf5\u9cf6\u4ca6\u4ca7\u4ca8\u4ca9\u4caa\u4cab\u9cf7\u9cf8\u9cf9\u9cfa\u9cfb\u9cfc\u9cfd\u9cfe\u9cff\u9d00\u9d01\u9d02\u9d03\u9d04\u9d05\u9d06\u9d07\u9d08\u9d09\u9d0b\u9d0c\u9d0d\u9d0e\u4cac\u4cad\u4cae\u4caf\u4cb0\u4cb1\u4cb2\u4cb3\u4cb4\u4cb5\u4cb6\u4cb7\u4cb8\u9d0a\u9d0f\u9d10\u9d11\u9d12\u9d13\u9d14\u9d15\u9d16\u9d17\u9d18\u9d19\u9d1a\u9d1b\u9d1c\u9d1d\u9d1e\u9d1f\u9d20\u9d21\u9d22\u9d23\u9d24\u9d25\u9d26\u9d27\u9d28\u9d29\u9d2a\u9d2b\u9d2c\u4cb9\u4cba\u4cbb\u4cbc\u4cbd\u4cbe\u4cbf\u4cc0\u4cc1\u4cc2\u4cc3\u4cc4\u4cc5\u4cc6\u4cc7\u4cc8\u4cc9\u4cca\u9d2d\u9d2e\u9d2f\u9d30\u9d31\u9d32\u9d33\u9d34\u9d35\u9d36\u9d37\u9d38\u9d39\u9d3a\u9d3b\u9d3c\u9d3d\u9d3e\u9d3f\u9d40\u9d41\u9d42\u9d43\u9d44\u9d45\u9d46\u9d47\u9d48\u9d49\u4ccb\u4ccc\u4ccd\u4cce\u4ccf\u4cd0\u4cd1\u4cd2\u4cd3\u4cd4\u9d4a\u9d4b\u9d4c\u9d4d\u9d4e\u9d4f\u9d50\u9d51\u9d52\u9d53\u9d54\u9d55\u9d56\u9d57\u9d58\u9d59\u9d5a\u9d5b\u9d5c\u9d5d\u9d5e\u9d5f\u9d60\u9d61\u9d62\u9d63\u9d65\u4cd5\u4cd6\u4cd7\u4cd8\u4cd9\u4cda\u4cdb\u4cdc\u9d66\u9d67\u9d68\u9d69\u9d6a\u9d6b\u9d6c\u9d6d\u9d6e\u9d6f\u9d70\u9d71\u9d72\u9d73\u9d74\u9d75\u9d76\u9d77\u9d78\u9d79\u9d7a\u9d7b\u9d7c\u9d7d\u9d7e\u9d7f\u9d80\u9d81\u9d82\u9d83\u9d84\u9d85\u9d86\u9d87\u9d88\u9d89\u9d8a\u9d8b\u9d8c\u9d8d\u9d8e\u9d8f\u9d91\u4cdd\u4cde\u4cdf\u4ce0\u4ce1\u4ce2\u4ce3\u4ce4\u4ce5\u9d90\u9d92\u9d93\u9d94\u9d95\u9d96\u9d97\u9d98\u9d99\u9d9a\u9d9b\u9d9c\u9d9d\u9d9e\u9d9f\u9da0\u9da1\u9da2\u9da3\u9da4\u9da5\u9da6\u9da7\u9da8\u9da9\u9daa\u9dab\u4ce6\u4ce7\u4ce8\u4ce9\u4cea\u4ceb\u4cec\u4ced\u4cee\u4cef\u4cf0\u9dac\u9dad\u9dae\u9daf\u9db0\u9db1\u9db2\u9db3\u9db4\u9db5\u9db6\u9db7\u9db8\u9db9\u9dba\u9dbb\u9dbc\u9dbd\u9dbe\u9dbf\u9dc0\u9dc1\u9dc2\u9dc3\u9dc4\u9dc5\u9dc6\u9dc7\u9dc8\u9dc9\u9dca\u9dcc\u9dcd\u9dce\u9dcf\u4cf1\u4cf2\u4cf3\u4cf4\u4cf5\u4cf6\u9dcb\u9dd0\u9dd1\u9dd2\u9dd3\u9dd4\u9dd5\u9dd6\u9dd7\u9dd8\u9dd9\u9dda\u9ddb\u9ddc\u9ddd\u9dde\u9ddf\u4cf7\u4cf8\u4cf9\u4cfa\u4cfb\u4cfc\u4cfd\u9de1\u9de2\u9de3\u9de4\u9de5\u9de6\u9de7\u9de8\u9de9\u9dea\u9deb\u9dec\u9ded\u9dee\u9def\u9df0\u9df1\u9df2\u9df3\u9df4\u9df5\u9df6\u9df7\u9df8\u9dfa\u9dfb\u9dfc\u4cfe\u4cff\u4d00\u4d01\u4d02\u4d03\u4d04\u4d05\u4d06\u9df9\u9dfd\u9dfe\u9dff\u9e00\u9e01\u9e02\u9e03\u9e04\u9e05\u9e06\u9e07\u9e08\u9e09\u9e0a\u4d07\u4d08\u4d09\u4d0a\u4d0b\u9e0b\u9e0c\u9e0d\u9e0e\u9e0f\u9e10\u9e11\u9e12\u4d0c\u4d0d\u9e13\u9e14\u4d0e\u9e15\u9e16\u9e17\u4d0f\u9e18\u9e19\u9e1a\u9e1b\u9e1c\u9e1d\u9e1e\u4d10\u4d11\u4d12\u9e1f\u9e20\u9e21\u9e22\u9e23\u9e24\u9e25\u9e26\u9e27\u9e28\u9e29\u4d13\u9e2a\u9e2b\u9e2c\u9e2d\u9e2e\u9e2f\u9e30\u9e31\u9e32\u9e33\u9e34\u9e35\u9e36\u9e37\u9e38\u9e39\u9e3a\u9e3b\u9e3c\u9e3d\u9e3e\u9e3f\u4d14\u4d15\u9e40\u9e41\u9e42\u9e43\u9e44\u9e45\u9e46\u9e47\u9e48\u9ffa\u9e49\u9e4a\u9e4b\u9e4c\u9e4d\u9e4e\u9e4f\u9e50\u9e51\u9e52\u9e53\u9e54\u4d16\u9e55\u9e56\u9e57\u9e59\u9e5a\u9e5b\u9e5c\u4d17\u9e58\u9e5d\u9e5e\u9e5f\u9e60\u9e61\u9e62\u9e63\u9e64\u4d18\u9e65\u9e66\u9e67\u9e68\u9e69\u9e6a\u9e6b\u9e6c\u9e6d\u9e6e\u9e6f\u9e70\u4d19\u9e71\u9e72\u9e73\u9e74\u9e75\u9e76\u4d1a\u9e77\u4d1b\u9e78\u9e79\u9e7a\u9e7b\u4d1c\u4d1d\u9e7c\u9e7d\u4d1e\u9e7e\u9e7f\u9e80\u9e81\u9e82\u4d1f\u9e83\u9e84\u4d20\u4d21\u4d22\u9e85\u9e86\u9e87\u9e88\u4d23\u4d24\u4d25\u9e89\u9e8a\u9e8b\u9e8c\u9e8d\u9e8e\u9e8f\u9e90\u4d26\u9e91\u9e92\u9e93\u9e94\u9e95\u9e96\u9e97\u4d27\u9e98\u9e99\u9e9a\u9e9b\u9e9c\u9e9d\u4d28\u9e9e\u4d29\u9e9f\u9ea0\u4d2a\u4d2b\u9ea1\u9ea2\u9ea3\u9ea4\u9ea5\u9ea7\u4d2c\u4d2d\u4d2e\u9ea8\u9ea9\u9eaa\u9eab\u4d2f\u4d30\u9eac\u9ead\u9eae\u4d31\u4d32\u4d33\u4d34\u9eaf\u9eb0\u4d35\u9eb1\u9eb2\u4d36\u4d37\u4d38\u9eb3\u9eb4\u4d39\u4d3a\u4d3b\u4d3c\u4d3d\u9eb5\u4d3e\u4d3f\u4d40\u9eb6\u4d41\u4d42\u4d45\u4d43\u4d44\u4d46\u9eb7\u9ea6\u9eb8\u9eb9\u9eba\u9ebb\u9ebc\u9ebd\u9ebe\u4d47\u9ebf\u9ec0\u9ec1\u4d48\u4d49\u9ec2\u9ec3\u9ec4\u9ec5\u9ec6\u4d4a\u9ec7\u9ec8\u9ec9\u9eca\u9ecb\u4d4b\u4d4c\u4d4d\u4d4e\u4d4f\u4d50\u9ecc\u9ecd\u9ece\u4d51\u4d52\u4d53\u9ecf\u4d54\u4d55\u4d56\u4d57\u4d58\u4d59\u9ed0\u4d5a\u4d5b\u4d5c\u9ed1\u9ed2\u4d5d\u4d5e\u58a8\u9ed3\u4d5f\u4d60\u9ed4\u9ed5\u9ed6\u9ed7\u9ed8\u4d61\u9eda\u9edb\u9edc\u9edd\u9ede\u4d62\u4d63\u9edf\u9ee0\u9ee1\u4d64\u4d65\u4d66\u4d67\u9ee2\u9ee3\u4d68\u4d69\u9ee4\u9ee5\u9ee6\u9ee7\u9ee8\u9ee9\u9eea\u4d6a\u4d6b\u4d6c\u9eeb\u9eec\u9eed\u9eee\u9eef\u4d6d\u4d6e\u4d6f\u9ef0\u9ef1\u9ef2\u9ef3\u9ef4\u4d70\u4d71\u9ef5\u4d72\u4d73\u4d74\u9ef6\u4d75\u9ef7\u9ef8\u9ef9\u9efa\u9efb\u9efc\u9efd\u9eff\u9f00\u9f01\u9f02\u4d76\u9f03\u9f04\u4d77\u9f05\u4d78\u9f06\u9f07\u4d79\u9f08\u9f09\u9f0a\u9efe\u9f0b\u9f0c\u9f0d\u9f0e\u9f0f\u9f10\u9f11\u9f12\u4d7a\u4d7b\u4d7c\u9f13\u9f14\u9f15\u9f16\u4d7d\u4d7e\u4d7f\u9f17\u4d80\u9f18\u9f19\u9f1a\u9f1b\u4d81\u9f1c\u9f1d\u9f1e\u9f1f\u9f20\u9f21\u4d82\u9f22\u9f23\u9f24\u4d83\u9f25\u9f26\u9f27\u9f28\u9f29\u9f2a\u9f2b\u9f2c\u9ffb\u9ffc\u4d84\u9f2d\u4d85\u9f2e\u9f2f\u9f30\u9f31\u4d86\u9f32\u9f33\u9f34\u9f35\u4d87\u9f36\u9f37\u9f38\u9f39\u4d88\u4d89\u9f3a\u9f3b\u9f3c\u9f3d\u9f3e\u9f3f\u4d8a\u4d8b\u9f40\u9f41\u4d8c\u4d8d\u4d8e\u4d8f\u9f42\u9f43\u9f44\u9f45\u9f46\u9f47\u9f48\u4d90\u4d91\u9f49\u9f4a\u9f4b\u4d92\u4d93\u9f4c\u9f4d\u9f4e\u9f4f\u9f50\u9f51\u9f52\u9f53\u9f54\u9f55\u4d94\u9f56\u9f57\u9f58\u4d95\u4d96\u9f59\u9f5a\u9f5b\u9f5c\u9f5d\u9f5e\u9f5f\u9f60\u9f61\u9f62\u9f63\u4d97\u4d98\u4d99\u9f64\u9f65\u9f66\u9f67\u9f68\u9f69\u4d9a\u4d9b\u9f6a\u9f6b\u9f6c\u4d9c\u4d9d\u9f6d\u9f6e\u9f6f\u9f70\u9f71\u4d9e\u4d9f\u9f72\u9f73\u9f74\u9f75\u9f76\u9f77\u4da0\u4da1\u4da2\u9f78\u9f79\u9f7a\u9f7b\u4da3\u4da4\u4da5\u4da6\u4da7\u9f7c\u9f7d\u4da8\u4da9\u4daa\u9f7e\u4dab\u9f7f\u9f80\u9f81\u9f82\u9f83\u9f84\u9f85\u9f86\u9f87\u9f88\u9f89\u9f8a\u9f8b\u9f8c\u9f8d\u9f8e\u9f8f\u9f91\u9f92\u9f93\u9f94\u9f95\u4dac\u9f96\u9f97\u9f98\u9f99\u4dad\u4dae\u9f9a\u9f9b\u9f9c\u4daf\u4db0\u9f9d\u4db1\u4db2\u9f9e\u9f9f\u9fa0\u9fa1\u4db3\u9fa2\u9fa3\u4db4\u9fa4\u9fa5\u4db5\u2f00\u3280\u3192\u319c\u3286\u3282\u3194\u32a4\u3196\u32a6\u3198\uf967\u319b\ufa70\u2f01\u2ea6\u32a5\u3197\uf905\u2f02\u2e80\uf95e\u2f03\u2f04\u319a\u2e84\u2e83\u2e82\u3288\uf91b\u2f05\uf9ba\u2f06\u3281\u3193\u3284\u2f07\uf977\u2f08\u319f\u2e85\uf9fd\uf9a8\u32ff\u32ad\u32a1\ufa73\uf92d\uf9b5\ufa30\uf965\uf9d4\uf9bb\ufa31\u329d\u2f09\ufa0c\ufa74\ufa32\u2f0a\ufa72\uf978\u2f0b\u3287\uf9d1\u3285\ufa75\u2f0c\u2e86\u2f0d\u32a2\u2f0e\ufa71\uf92e\uf979\uf955\uf954\ufa15\u2f0f\u2e87\u2f10\u2f11\u2e88\u2e89\ufa00\uf99c\uf9dd\uf9ff\uf9c7\uf98a\u2f12\uf99d\u3298\ufa76\ufa33\uf952\uf92f\ufa34\uf97f\u2f13\ufa77\u2f14\uf963\u2f15\u2f16\u32a9\uf9eb\u2f17\u3038\u3289\u3039\u303a\ufa35\u32af\u2f18\u2e8a\u2f19\u2e8b\u329e\uf91c\u2f1a\u2e81\u2f1b\uf96b\u2f1c\u2f1d\uf906\u32a8\u3294\uf9de\uf9ed\uf980\uf99e\u3244\ufa79\uf90b\ufa7a\ufa36\ufa78\ufa0d\ufa7b\ufa37\ufa38\u2f1e\u3283\u3195\uf9a9\u2f1f\u328f\u319e\ufa39\ufa10\ufa7c\uf96c\ufa3a\ufa7d\uf94a\uf942\u2f20\u2f21\u2f22\u2f23\u32b0\u2f24\u337d\u319d\ufa7e\uf90c\uf909\ufa7f\uf981\u2f25\u329b\ufa80\ufa81\u2f26\u32ab\u2f27\ufa04\u32aa\uf95f\uf9aa\uf9bc\u2f28\u2f29\u2e8c\u2e8d\u2e90\u2f2a\u2e8e\u2e8f\u2e91\u2f2b\uf9bd\uf94b\ufa3b\uf9df\ufa3c\u2f2c\u2f2d\uf9d5\uf921\uf9ab\u2f2e\u2f2f\u32a7\u2f30\u2e92\u2f31\u2f32\u337b\uf98e\u2e93\u2f33\u3245\u2f34\ufa01\uf9a2\uf928\ufa82\ufa0b\ufa83\uf982\u2f35\u2f36\uf943\u2f37\u2f38\u2f39\u2e95\u2e94\u2f3a\ufa84\u2f3b\uf9d8\uf966\ufa85\u2f3c\u2e97\u2e96\uf9a3\uf960\uf9ac\ufa6b\ufa3d\ufa86\uf9b9\ufa88\uf9d9\ufa87\ufa8a\ufa3e\ufa3f\ufa89\uf98f\ufa40\ufa8b\uf90d\uf990\u2f3d\uf9d2\ufa8c\u2f3e\u2f3f\u2e98\uf925\uf95b\ufa02\uf973\uf9a4\uf975\ufa8d\ufa8e\ufa8f\uf991\uf930\u2f40\u2f41\u2e99\ufa41\ufa90\uf969\u2f42\u3246\u2f43\uf9be\u2f44\u2f45\uf983\u2f46\u2e9b\ufa42\u2f47\u3290\u2e9c\u337e\uf9e0\u337c\ufa12\ufa91\uf9c5\ufa43\ufa06\uf98b\u2f48\uf901\u2f49\u328a\u2e9d\u3292\uf929\ufa92\ufa93\u2f4a\u328d\uf9e1\ufa94\uf9c8\uf9f4\uf9c9\uf9da\u3291\u337f\uf97a\ufa44\uf9e2\uf914\uf95c\uf9bf\uf94c\uf931\uf91d\u2f4b\u2f4c\u32a3\uf98c\ufa95\u2f4d\u2e9e\uf9a5\u2f4e\uf970\ufa96\u2f4f\u2e9f\u2f50\u2f51\u2f52\u2ea0\u2f53\u2f54\u328c\u2ea1\u2ea2\uf972\uf968\uf9e3\u329f\uf915\ufa05\uf9ca\ufa97\uf92a\ufa45\uf9f5\uf94d\uf9d6\ufa46\uf9cb\uf9ec\ufa99\uf904\ufa98\uf94e\ufa47\ufa9a\uf992\uf922\uf984\ufa9b\u2f55\u328b\u2ea3\uf9fb\uf99f\uf916\uf993\ufa48\ufa9c\uf9c0\uf9ee\uf932\uf91e\u2f56\ufa49\u2ea4\u2ea5\ufa9e\u2f57\u2f58\u2f59\u2f5a\u2f5b\u2f5c\u2ea7\uf946\u3295\u2f5d\u2ea8\ufa9f\uf9fa\uf92b\ufa16\ufaa0\uf9a7\u2f5e\uf961\uf9db\u2f5f\u2ea9\uf9ad\uf917\uf9e4\uf9cc\ufa4a\uf9ae\ufaa1\uf994\uf9ef\u2f60\u2f61\ufaa2\u2f62\u2f63\u2f64\u2f65\u3199\u329a\ufaa3\uf9cd\uf976\uf962\u2f66\u2eaa\u2f67\uf9e5\ufaa4\ufaa5\uf9c1\uf90e\u2f68\u2f69\u2f6a\u2f6b\ufa17\ufaa6\ufaa7\u32ac\uf933\u2f6c\u2eab\ufaa8\uf96d\ufaaa\ufaa9\ufa9d\u2f6d\u2f6e\u2f6f\uf9ce\uf93b\ufa4b\uf947\ufaab\uf964\uf985\u2f70\u2eac\u2ead\ufa18\ufa4c\u3293\ufa4e\ufa4d\ufa4f\ufa50\ufa51\u3297\ufa19\ufa1a\uf93c\ufa52\ufa53\ufa1b\uf9b6\u2f71\u2f72\uf995\u3299\uf956\ufa54\u2f73\ufa55\ufaac\uf9f7\u2f74\u2f75\u2eae\uf9f8\u3247\ufa56\ufaad\uf9a6\uf944\u2f76\ufaae\uf9f9\ufa1d\ufa03\uf97b\u2f77\u2eaf\uf9cf\uf96a\uf94f\ufaaf\uf93d\uf957\uf996\ufa57\ufab0\ufa58\uf950\ufa59\u2eb0\u2f78\ufab1\u2f79\u2eb2\u2eb5\u2eb1\u2eb3\u2eb4\ufa5a\uf9e6\uf90f\u2f7a\u2eb6\u2eb7\u2eb8\uf9af\ufa1e\u2f7b\uf934\u2f7c\u2eb9\ufa5b\ufab2\u2f7d\u2f7e\u2f7f\uf9b0\uf997\uf945\u2f80\u2ebb\u2eba\u2f81\u2ebc\uf953\uf926\u2f82\uf9f6\u2f83\ufa5c\u2f84\u2f85\u2ebd\u2f86\ufa6d\u2f87\u2f88\u2f89\uf97c\u2f8a\u2f8b\ufa5d\ufa5e\u2ebe\u2ebf\u2ec0\uf974\uf9fe\ufab3\uf93e\ufab4\uf958\uf918\uf96e\ufa5f\uf999\uf9c2\uf923\uf9f0\uf935\ufa20\uf91f\uf910\u2f8c\u2ec1\uf936\u2f8d\ufab5\uf911\uf927\u2f8e\ufa08\u2f8f\u2f90\u2ec2\uf9a0\uf9e7\uf9e8\uf912\ufa60\ufab6\uf924\u2f91\u2ec4\u2ec3\ufab7\ufa0a\u2f92\ufa61\ufab8\u2ec5\u2ec6\u2f93\u2ec7\u2f94\uf96f\uf9a1\ufab9\ufabb\uf97d\uf941\ufabe\ufa22\ufaba\uf95d\ufabd\ufa62\ufabc\ufa63\ufabf\uf9fc\uf95a\ufac0\u2ec8\u2f95\u2f96\uf900\u2f97\u2f98\u2f99\u3296\uf948\u32ae\uf903\ufa64\ufa65\ufac1\u2ec9\u2f9a\u2f9b\u2f9c\u2eca\uf937\u2f9d\uf902\u2f9e\uf998\uf9d7\ufac2\ufa07\uf98d\u2ecb\u2f9f\uf971\u2fa0\u2fa1\ufa66\u2ecc\u2ecd\u2ece\uf99a\ufa25\ufa67\u329c\ufac3\uf9c3\uf913\u2fa2\u2ecf\uf92c\ufa2e\ufa26\u2fa3\uf919\ufac4\uf9b7\u2fa4\uf9e9\u2fa5\uf97e\uf90a\u2fa6\u328e\uf9b1\ufac5\uf93f\uf99b\u2ed0\u2ed1\u2fa7\u2ed2\u2ed3\u2fa8\uf986\u2ed4\u2fa9\u2ed5\u2ed6\uf9c6\uf951\ufa09\uf959\uf9d3\ufac6\uf9dc\uf9f1\u2faa\ufa2f\uf9b8\u2fab\uf9ea\ufa68\ufac7\u2fac\u2ed7\uf9b2\uf949\uf938\uf9b3\u2fad\u2ed8\ufa1c\ufac8\u2fae\u2faf\u2fb0\u2fb1\ufac9\u2ed9\u2fb2\u2fb3\ufa69\ufaca\u2fb4\u32a0\ufacb\uf9b4\ufa6a\ufacc\uf9d0\u2eda\u2fb5\u2edb\u2fb6\u2edc\u2edd\u2fb7\u2edf\u2ede\ufa2a\ufa2b\ufa2c\u2ee0\u2fb8\u2ee1\u2fb9\u2fba\uf91a\uf987\u2ee2\u2fbb\u2ee3\u2fbc\u2fbd\ufacd\u2fbe\u2fbf\u2fc0\u2fc1\u2ee4\u2fc2\uf939\uf9f2\u2ee5\u2fc3\ufa2d\uf93a\uf920\u2ee6\u2fc4\u2ee7\uf940\u2fc5\uf988\uf9f3\u2fc6\u2ee8\u2fc7\u2fc8\u2ee9\u2fc9\uf989\u2fca\u2fcb\u2fcc\u2eea\u2fcd\u2fce\u2fcf\u2fd0\ufad8\u2fd1\u2eeb\u2eec\u2fd2\u2eed\u2eee\uf9c4\u2fd3\u2eef\ufad9\u2ef0\uf907\uf908\uface\u2fd4\u2ef1\u2ef2\u2ef3\u2fd5\ufad2\ufad3\ufad4\ufad0\ufacf\ufad1\ufa6c\ufad5\ufad6\ufad7\ufffd'; +const String _ducetRanks = + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u01d4\u01d5\u01d6\u01d7\u01d8\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u01dc\u0266\u02ef\u0423\u106d\u0426\u041e\u02e7\u02fb\u0372\u0411\u0676\u0216\u01fa\u0284\u0418\u10a0\u10cf\u1149\u119d\u11e3\u1223\u125b\u1291\u12c7\u12fc\u023b\u0231\u067f\u0683\u068b\u0270\u040e\u1334\u13b2\u13d5\u1408\u144b\u14ad\u14ce\u1503\u153e\u158a\u15a1\u15d8\u1625\u1658\u1699\u171d\u1748\u175b\u17a9\u17ea\u1820\u1890\u18b7\u18d7\u18f1\u1916\u0378\u041a\u037b\u04c7\u01f2\u04c0\u1330\u13ae\u13d0\u1402\u1445\u14a9\u14c9\u14fe\u1537\u1586\u159d\u15d2\u1620\u1654\u1694\u1719\u1745\u1756\u17a5\u17e6\u181c\u188b\u18b2\u18d2\u18ee\u1912\u037e\u0691\u0382\u0695\u0000' + '\u0000\u0000\u0000\u0000\u0000\u01d9\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u01ea\u026c\u106b\u1070\u106a\u1072\u0693\u0408\u04cd\u05d6\u1337\u02f9\u068f\u0000\u05d7\u04c9\u0548\u067c\u1179\u11cd\u04c3\u1a4d\u040a\u029f\u04d4\u1100\u169c\u02fa\u1107\u1105\u11d0\u0276\u133e\u133c\u1342\u134e\u134b\u1346\u137c\u13e6\u1455\u1453\u1459\u145d\u154a\u1548\u154e\u1552\u141f\u1666\u16a3\u16a1\u16a7\u16b2\u16ad\u067e\u16b6\u1829\u1827\u182d\u1835\u18f6\u1947\u17c9\u133d\u133b\u1341\u134d\u1348\u1345\u137a\u13e4\u1454\u1452\u1458\u145c\u1549\u1547\u154d\u1551\u141d\u1665\u16a2\u16a0\u16a6\u16b1\u16aa\u067d\u16b5\u1828\u1826\u182c\u1832\u18f5\u1946\u18fc' + '\u1354\u1353\u1340\u133f\u1352\u1351\u13dd\u13dc\u13df\u13de\u13e3\u13e2\u13e1\u13e0\u1410\u140f\u1416\u1415\u1467\u1466\u1457\u1456\u1461\u1460\u1465\u1464\u145b\u145a\u14d8\u14d7\u14d6\u14d5\u14dc\u14db\u14de\u14dd\u150d\u150c\u1518\u1516\u1554\u1553\u1559\u1558\u154c\u154b\u1557\u1556\u1555\u1572\u156b\u156a\u1591\u1590\u15ad\u15ac\u1755\u15e1\u15e0\u15e5\u15e4\u15e3\u15e2\u15ef\u15ee\u15e7\u15e6\u1660\u165f\u166a\u1669\u1664\u1663\u196e\u1691\u1690\u16ba\u16b9\u16a5\u16a4\u16b0\u16af\u16ed\u16ec\u1765\u1764\u176b\u176a\u1767\u1766\u17af\u17ae\u17b1\u17b0\u17b7\u17b6\u17b3\u17b2\u17f6\u17f5\u17f1\u17f0\u180e\u180d\u183a\u1839\u183e\u183d\u182b\u182a\u1831\u1830\u1838\u1837\u183c\u183b\u18c1\u18c0\u18fa\u18f9\u18fd\u191d\u191c\u1923\u1922\u1921\u1920\u17be' + '\u13c1\u13ca\u13cc\u13cb\u1968\u1967\u16ff\u13fb\u13fa\u143c\u143e\u1441\u1440\u1928\u148f\u1494\u1499\u14c5\u14c4\u14f4\u14fa\u1525\u1583\u157b\u15c7\u15c6\u1606\u161e\u1881\u1684\u1686\u170e\u16c3\u16c2\u14fd\u14fc\u173c\u173b\u1781\u1964\u1963\u17de\u17e2\u1812\u1815\u1814\u1817\u1847\u1846\u1888\u18a9\u190c\u190b\u192b\u192a\u193c\u1942\u1941\u1944\u195c\u1966\u1965\u1806\u194c\u1982\u1983\u1984\u1985\u1431\u1430\u142f\u15f4\u15f3\u15f2\u1677\u1676\u1675\u1344\u1343\u1550\u154f\u16a9\u16a8\u182f\u182e\u1857\u1856\u1851\u1850\u1855\u1854\u1853\u1852\u148e\u1373\u1372\u1375\u1374\u1381\u1380\u14f1\u14f0\u14da\u14d9\u15ab\u15aa\u16b8\u16b7\u16db\u16da\u193f\u193e\u1592\u142e\u142d\u142b\u14d4\u14d3\u1526\u194d\u1662\u1661\u1371\u1370\u137f\u137e\u16d9\u16d8' + '\u1358\u1357\u135a\u1359\u146b\u146a\u146d\u146c\u155d\u155c\u155f\u155e\u16bf\u16be\u16c1\u16c0\u176f\u176e\u1771\u1770\u1843\u1842\u1845\u1844\u17bd\u17bc\u17fa\u17f9\u1911\u1910\u150f\u150e\u1687\u1442\u1716\u1715\u1930\u192f\u1350\u134f\u1463\u1462\u16cf\u16ce\u16d5\u16d4\u16b4\u16b3\u16d7\u16d6\u1903\u1902\u1619\u168d\u1818\u1593\u1425\u174c\u1399\u13f5\u13f4\u1607\u1810\u17d8\u1935\u196b\u196a\u13c2\u1870\u18b0\u148a\u1489\u1596\u1595\u1754\u1753\u1788\u1787\u190a\u1909\u13a1\u13a4\u13aa\u13c9\u16fe\u13fc\u143b\u143d\u149d\u1492\u149e\u1498\u149f\u14a5\u14a6\u159a\u14f3\u14ea\u14ee\u14f9\u14a8\u187b\u1528\u1534\u157a\u1582\u1573\u160a\u1610\u1616\u161b\u1880\u1885\u164d\u1683\u168b\u167d\u170d\u16f5\u1712\u1742\u178a\u178d\u178f\u1792\u1793\u1796\u1798' + '\u177f\u179e\u17d5\u17dc\u159c\u17e3\u17e5\u181a\u1816\u186f\u1887\u18a8\u18af\u18cf\u161f\u1908\u1931\u1933\u193b\u1945\u1969\u1976\u1981\u1986\u1987\u13c0\u14a7\u14f5\u1524\u1597\u15d0\u15fe\u1752\u197f\u1980\u142c\u1434\u1433\u1807\u1809\u1802\u14bd\u15fa\u15fd\u1988\u1989\u187e\u187f\u1509\u152a\u158d\u1761\u178b\u1790\u179f\u18ba\u18f4\u04e2\u04e5\u1535\u196d\u1536\u1970\u1978\u196c\u1979\u04e6\u04e7\u04e8\u04e9\u04ea\u04eb\u04ec\u04ed\u04ee\u04ef\u04f0\u04f1\u04f2\u04f3\u1050\u1051\u04f4\u04f5\u04f6\u04f7\u04fa\u04fb\u04cb\u04cc\u04d2\u04d5\u04c6\u04d3\u04fc\u04fd\u14fb\u15dd\u17ac\u18db\u1977\u04fe\u04ff\u0500\u0501\u0502\u0503\u0504\u0505\u0506\u196f\u0507\u0508\u0509\u050a\u050b\u050c\u050d\u050e\u050f\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517' + '\u000c\u0009\u0010\u0016\u001b\u00a9\u000f\u0017\u0014\u00aa\u0012\u0015\u0011\u001c\u001d\u00ab\u00ac\u00ad\u001e\u0002\u0006\u001f\u005b\\\u005d\u005e\u0020\u00ae\u005f\u0060\u0061\u0062\u0063\u00af\u00b0\u00b1\u00b2\u00b3\u00b4\u0019\u001a\u0064\u0065\u0066\u0067\u00b5\u00b6\u0068\u00b7\u00b8\u0001\u0069\u00b9\u00a8\u0094\u0095\u0018\u00ba\u006a\u006b\u006c\u0021\u0022\u0023\u000d\u000a\u0013\u0003\u01ca\u00bb\$\u006d\u006e\u006f\u0025\u0026\'\u0070\u0071\u0000\u0028\u0029\u002a\u0072\u0073\u0074\u0075\u002b\u00bc\u0076\u0077\u002c\u0078\u002d\u002e\u0079\u00bd\u00c0\u007a\u1332\u1447\u1539\u1696\u181e\u13d2\u1404\u1500\u1622\u1758\u17e8\u188d\u18d4\u19e9\u19e8\u1af2\u1af1\u04e3\u04e4\u19e3\u19e2\u0000\u0000\u1a1f\u1a8b\u1a89\u1a8d\u0232\u1a43' + '\u0000\u0000\u0000\u0000\u04c4\u04ce\u1992\u02a0\u19d4\u19f2\u1a27\u0000\u1a63\u0000\u1a99\u1ac8\u1a3e\u198b\u19be\u19c3\u19c9\u19cd\u19e7\u19eb\u1a1a\u1a20\u1a46\u1a4a\u1a4e\u1a58\u1a5a\u1a5c\u1a72\u1a7d\u0000\u1a86\u1a90\u1a92\u1ab5\u1ab9\u1abd\u1ac0\u1a2f\u1aa2\u1990\u19d2\u19f0\u1a25\u1aaf\u198a\u19bc\u19c1\u19c8\u19cb\u19e6\u19ea\u1a18\u1a1d\u1a44\u1a49\u1a4c\u1a57\u1a59\u1a5b\u1a6f\u1a7b\u1a88\u1a84\u1a8f\u1a91\u1ab3\u1ab8\u1abc\u1abf\u1a2e\u1aa1\u1a61\u1a97\u1ac6\u1a48\u19bd\u1a19\u1a93\u1a9b\u1aa3\u1ab4\u1a70\u1a47\u1a7a\u1a79\u19e5\u19e4\u19e1\u19e0\u1a78\u1a77\u1af0\u1aef\u1b35\u1b34\u1b3f\u1b3e\u1b41\u1b40\u1b47\u1b46\u1b55\u1b54\u1b5b\u1b5a\u1b63\u1b62\u1a45\u1a7c\u1a85\u1a42\u1a1b\u19cc\u0670\u1af4\u1af3\u1a87\u1a76\u1a75\u1a83\u1a8c\u1a8a\u1a8e' + '\u1ba5\u1ba9\u1b9a\u1b86\u1bac\u1bc8\u1bdf\u1be2\u1be8\u1c0c\u1c2b\u1c6a\u1bf0\u1bd7\u1c6f\u1cbe\u1b70\u1b7d\u1b81\u1b84\u1b94\u1ba3\u1baf\u1bbc\u1bd5\u1be6\u1bee\u1c01\u1c15\u1c1c\u1c37\u1c45\u1c4e\u1c56\u1c60\u1c6d\u1c82\u1c85\u1c9f\u1caa\u1cc1\u1cc6\u1cce\u1cd4\u1cd9\u1ce4\u1ce9\u1cf0\u1b6e\u1b7b\u1b7e\u1b82\u1b91\u1ba1\u1bad\u1bba\u1bd3\u1be5\u1bec\u1bff\u1c13\u1c1a\u1c2e\u1c43\u1c4c\u1c53\u1c5c\u1c6b\u1c80\u1c83\u1c9d\u1ca8\u1cbf\u1cc4\u1ccb\u1cd2\u1cd7\u1ce3\u1ce7\u1cef\u1ba4\u1ba8\u1b99\u1b85\u1baa\u1bc7\u1bde\u1be0\u1be7\u1c0b\u1c2a\u1c69\u1bef\u1bd6\u1c6e\u1cbd\u1c94\u1c92\u1ce0\u1cdd\u1cf5\u1cf3\u1cf8\u1cf6\u1d01\u1d00\u1cfd\u1cfb\u1d06\u1d04\u1d08\u1d07\u1d0a\u1d09\u1d0d\u1d0b\u1d0f\u1d0e\u1d11\u1d10\u1c7f\u1c7e\u1c9c\u1c9b\u1c9a\u1c99\u1c96\u1c95' + '\u1c4b\u1c4a\u054b\u00c2\u002f\u0007\u0004\u0030\u0000\u0000\u1bdd\u1bdc\u1cdc\u1cdb\u1c50\u1c4f\u1b88\u1b87\u1b8a\u1b89\u1b8e\u1b8d\u1bb9\u1bb8\u1ba0\u1b9f\u1bf2\u1bf1\u1bfa\u1bf9\u1bf8\u1bf7\u1bf6\u1bf5\u1c23\u1c22\u1c29\u1c28\u1c49\u1c48\u1d15\u1d14\u1c5b\u1c5a\u1c66\u1c65\u1c77\u1c76\u1c79\u1c78\u1c8b\u1c8a\u1ca5\u1ca4\u1cb2\u1cb1\u1cb6\u1cb5\u1c8d\u1c8c\u1cba\u1cb9\u1cbc\u1cbb\u1d19\u1bb1\u1bb0\u1bf4\u1bf3\u1c04\u1c03\u1c25\u1c24\u1c21\u1c20\u1cb4\u1cb3\u1c17\u1c16\u1d18\u1b72\u1b71\u1b74\u1b73\u1b7a\u1b79\u1ba7\u1ba6\u1b76\u1b75\u1b78\u1b77\u1bb3\u1bb2\u1bbe\u1bbd\u1bcc\u1bcb\u1bdb\u1bda\u1bd9\u1bd8\u1c3e\u1c3d\u1c40\u1c3f\u1c42\u1c41\u1ce6\u1ce5\u1c75\u1c74\u1c71\u1c70\u1c73\u1c72\u1cac\u1cab\u1b90\u1b8f\u1cd6\u1cd5\u1b8c\u1b8b\u1c87\u1c86\u1c89\u1c88' + '\u1b96\u1b95\u1b9e\u1b9d\u1bc2\u1bc1\u1bd0\u1bcf\u1c10\u1c0f\u1c2d\u1c2c\u1c59\u1c58\u1c64\u1c63\u1bc4\u1bc3\u1c08\u1c07\u1c12\u1c11\u1c52\u1c51\u1cf2\u1cf1\u1bfe\u1bfd\u1d17\u1d16\u1bfc\u1bfb\u1c0a\u1c09\u1c27\u1c26\u1c47\u1c46\u1c8f\u1c8e\u1c1f\u1c1e\u1bb5\u1bb4\u1cae\u1cad\u1c06\u1c05\u0000\u1e26\u1e29\u1e2b\u1e2d\u1e2f\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e54\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u0000\u0000\u1e79\u0464\u0465\u026d\u0220\u0278\u0466\u1e27\u1e25\u1e28\u1e2a\u1e2c\u1e2e\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e53\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e69' + '\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e30\u1e55\u0240\u01fd\u0000\u0000\u054c\u054d\u1074\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00cf\u00d0\u00d1\u00d4\u0000\u0467\u00d5\u0468\u00d3\u00d2\u0469\u0000\u0000\u046a\u00ce\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u1e7a\u1e81\u1e85\u1e88\u1e8c\u1e8f\u1e94\u1e96\u1e97\u1e99\u1ea1\u1e9f\u1ea5\u1eaa\u1ea8\u1ead\u1eac\u1eaf\u1eb1\u1eb4\u1eb3\u1eb9\u1eb8\u1ebb\u1ebd\u1ec0\u1ec6\u0000\u0000\u0000\u0000\u1e9c\u1e92\u1e93\u1e9d\u046b\u046c\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u06a5\u06a7\u054e\u042b\u042d\u1075\u0221\u0222\u054f\u0550\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0236\u0000\u0000\u0241\u0279\u228b\u1ee3\u1ee7\u1eea\u1ef1\u1ef4\u1efa\u1f26\u1f30\u1f65\u1f68\u1f8e\u1fb4\u1fe1\u1ff1\u2008\u200b\u2024\u202a\u2042\u2065\u2093\u20ac\u20c7\u20d9\u20e3\u20f7\u218c\u218d\u2288\u2289\u228a\u0000\u210e\u2135\u214c\u2191\u21c2\u21e3\u2215\u2234\u2255\u225c\u00eb\u00f0\u00f4\u00f8\u00fe\u0103\u0108\u010c\u0110\u0112\u0113\u0115\u0116\u0117\u0119\u011a\u011b\u011c\u011d\u011e\u0114\u10a1\u10d0\u114a\u119e\u11e4\u1224\u125c\u1292\u12c8\u12fd\u0429\u0223\u0224\u0416\u1f2f\u2134\u0127\u1eee\u1eed\u1ef7\u1ee4\u1f2c\u2238\u2244\u2262\u1fa0\u1fa5\u1f4a\u1faa\u1fab\u1f4f\u1fac' + '\u1f54\u1fff\u2000\u1fc8\u1fcd\u2001\u1fd2\u1fd8\u200f\u2012\u2013\u2014\u2015\u2018\u201c\u201f\u2020\u202d\u2030\u2031\u2032\u2033\u2034\u2035\u2036\u2039\u208a\u208b\u208c\u20c3\u20c5\u20e1\u2107\u2122\u2123\u2125\u2126\u212c\u212d\u2147\u2149\u2168\u216d\u216e\u216f\u2171\u2176\u2178\u217e\u217f\u2184\u2185\u218a\u21bb\u21bc\u21bd\u21be\u2211\u2206\u220a\u220f\u2210\u2223\u1fd7\u2231\u2228\u222d\u222e\u223a\u223b\u223e\u2241\u2246\u2249\u224c\u224d\u227b\u2280\u2281\u2251\u2282\u2287\u2290\u2293\u028d\u2230\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0551\u0000\u0000\u0000\u0000\u0000\u0000\u2235\u225d\u0000\u0000\u0552\u0000\u0000\u0000\u0000\u2021\u203a\u10a2\u10d1\u114b\u119f\u11e5\u1225\u125d\u1293\u12c9\u12fe\u208d\u20c6\u2109\u1ee6\u21c7\u222f' + '\u02c1\u028e\u028f\u0242\u0243\u0244\u0245\u0246\u0247\u027a\u046d\u046e\u046f\u0470\u0000\u0000\u2298\u0128\u2299\u229b\u229c\u229f\u229e\u22a1\u22a2\u22a3\u22a5\u22a6\u22a7\u22a8\u22a9\u22aa\u22ac\u22ad\u22ae\u22af\u22b0\u22b1\u22b2\u22b3\u22b5\u22b6\u22b7\u22b8\u22b9\u229a\u229d\u22a0\u0129\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0132\u0133\u0134\u0135\u0136\u0137\u0138\u0000\u0031\u007b\u0000\u0000\u0032\u007c\u0000\u0000\u0000\u0000\u0000\u0000\u22a4\u22ab\u22b4\u1f59\u1f5a\u1f5b\u1f5c\u1f5d\u1f5e\u1f60\u2002\u2003\u2022\u2023\u203b\u208e\u210a\u210b\u210c\u2132\u2133\u218b\u218e\u218f\u21e0\u21e1\u2212\u2213\u2214\u21bf\u203c\u203d\u208f\u2004\u2005\u2090\u203e\u2006\u1ef8\u1ef9\u228c\u228d\u228e\u2252\u2253\u2296\u2297\u2007\u2091\u2092\u2170' + '\u22de\u22e1\u22e2\u22e3\u22e5\u22e6\u22e7\u22e8\u22eb\u22ed\u22ee\u22ef\u22f1\u22f5\u22f6\u22f8\u22f9\u22fd\u22fe\u22ff\u2300\u2301\u2302\u2303\u22f2\u22df\u22e0\u22f0\u22e4\u22fa\u22fb\u22fc\u22f3\u22f4\u22e9\u22ea\u22f7\u22ec\u2305\u2306\u2307\u2308\u2309\u230a\u230b\u230c\u230d\u230e\u230f\u2304\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u10a3\u10d2\u114c\u11a0\u11e6\u1226\u125e\u1294\u12ca\u12ff\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231a\u231b\u231c\u231e\u2320\u2321\u2323\u2324\u2325\u2326\u2327\u2328\u2329\u232a\u232b\u232c\u232d\u232e\u232f\u2330\u231d\u231f\u2322\u0139\u013a\u013b\u013c\u013d\u013e\u013f\u0140\u0141\u2331\u2332\u0564\u02c2\u0225\u026e\u0000\u0000\u0000\u007d\u1076\u1077' + '\u1ec9\u1eca\u1ecb\u1ecc\u1ecd\u1ece\u1ecf\u1ed0\u1ed1\u1ed2\u1ed3\u1ed4\u1ed5\u1ed6\u1ed7\u1ed8\u1ed9\u1eda\u1edb\u1edc\u1edd\u1ede\u1edf\u1ee0\u00e8\u00e9\u1ee1\u1ee2\u00d7\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00de\u00df\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u00e7\u00ea\u0000\u0000\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0000\u22c5\u22c6\u22c7\u22c8\u22c9\u22ca\u22cb\u22cc\u22cd\u22ce\u22cf\u22d0\u22d1\u22d2\u22d3\u22d4\u22d5\u22d6\u22d7\u22d8\u22d9\u22da\u22db\u22dc\u22dd\u007e\u007f\u0080\u0000\u0000\u0471\u0000\u22ba\u22bb\u22bc\u22bd\u22be\u22bf\u22c0\u22c1\u22c2\u22c3\u22c4\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u1f5f\u1f62\u1fdd\u20e2\u212b\u214b\u21c0\u21e2\u1f23\u1f24\u203f\u2254\u1f25\u1f27\u201b\u20c4\u217d\u2250\u2040\u210d\u2177\u0000\u1f63\u1f64\u1fb1\u2041\u228f\u2124\u2148\u2209\u1f61\u1fb2\u1fb3\u1fde\u2190\u2108\u214a\u1fdf\u1fe0\u21c1\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u011f\u00fb\u0101\u0106\u00ef\u00f3\u00f7\u0000\u0000\u0000\u0000\u0000\u0000\u00ee\u00f2\u00f6\u0000\u00fc\u00fd\u0107\u0120\u0121\u0125\u0126\u0123\u0124\u0122\u0102\u0118' + '\u0154\u0155\u0163\u0179\u2534\u2535\u2536\u253c\u253d\u253e\u253f\u2540\u2542\u2544\u2545\u2546\u2547\u2549\u254a\u254b\u254c\u254d\u254f\u2551\u2554\u2555\u2556\u2557\u2558\u255c\u255d\u255e\u255f\u2561\u2564\u2566\u2567\u2568\u2569\u256a\u256b\u256c\u256d\u256e\u2570\u2572\u2573\u2574\u2577\u2578\u2579\u257a\u257b\u257c\u257d\u257e\u257f\u2580\u2596\u2597\u0147\u2581\u2595\u259b\u259c\u259d\u259e\u259f\u25a0\u25a3\u25a5\u25a6\u25a8\u25aa\u25ab\u25ac\u25ad\u25ae\u25a7\u2598\u2531\u0000\u0000\u000e\u000b\u25a4\u2599\u259a\u254e\u2550\u2552\u2559\u2562\u2565\u256f\u2575\u2541\u2543\u25a1\u25a2\u02a3\u02a4\u10a4\u10d3\u114d\u11a1\u11e7\u1227\u125f\u1295\u12cb\u1300\u0473\u1052\u2533\u2537\u2538\u2539\u253a\u253b\u2560\u255a\u2576\u2553\u255b\u2582\u2563\u2571' + '\u25af\u0156\u0164\u017a\u0000\u25b0\u25b1\u25b2\u25b3\u25b4\u25b5\u25b6\u25b8\u0000\u0000\u25ba\u25bb\u0000\u0000\u25bc\u25bd\u25be\u25bf\u25c0\u25c1\u25c2\u25c3\u25c4\u25c5\u25c6\u25c7\u25c8\u25c9\u25ca\u25cc\u25ce\u25cf\u25d1\u25d2\u25d3\u25d4\u0000\u25d5\u25d6\u25d7\u25d8\u25d9\u25da\u25dc\u0000\u25de\u0000\u0000\u0000\u25e0\u25e1\u25e2\u25e3\u0000\u0000\u0148\u25e4\u25e5\u25e6\u25e7\u25e8\u25e9\u25ea\u25eb\u0000\u0000\u25ee\u25ef\u0000\u0000\u25f0\u25f1\u25f2\u25d0\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u25f3\u0000\u0000\u0000\u0000\u25cb\u25cd\u0000\u25db\u25b7\u25b9\u25ec\u25ed\u0000\u0000\u10a5\u10d4\u114e\u11a2\u11e8\u1228\u1260\u1296\u12cc\u1301\u25dd\u25df\u1078\u1079\u1019\u101a\u101b\u101c\u101d\u101e\u0565\u107a\u25f4\u0478\u018b\u0000' + '\u0000\u0157\u0165\u017b\u0000\u25fa\u25fb\u25ff\u2600\u25f7\u25f8\u0000\u0000\u0000\u0000\u2601\u25fc\u0000\u0000\u25f9\u25fd\u2606\u2607\u2609\u260b\u260c\u260d\u260e\u260f\u2611\u2612\u2613\u2614\u2615\u2616\u2617\u2618\u2619\u261a\u261b\u261c\u0000\u261d\u261e\u2620\u2621\u2622\u2623\u2625\u0000\u2626\u2627\u0000\u2628\u2603\u0000\u2602\u2604\u0000\u0000\u0149\u0000\u262a\u262b\u262c\u262d\u262e\u0000\u0000\u0000\u0000\u262f\u2630\u0000\u0000\u2631\u2632\u2633\u0000\u0000\u0000\u2605\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2608\u260a\u2610\u2629\u0000\u261f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u10a6\u10d5\u114f\u11a3\u11e9\u1229\u1261\u1297\u12cd\u1302\u018c\u018d\u25fe\u25f6\u25f5\u2624\u0479\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0158\u0166\u017c\u0000\u2635\u2636\u2637\u2638\u2639\u263a\u263b\u263d\u263f\u0000\u2640\u2641\u2642\u0000\u2643\u2644\u2645\u2646\u2647\u2648\u2649\u264a\u264b\u264c\u264e\u264f\u2650\u2651\u2652\u2653\u2654\u2655\u2656\u2657\u2658\u2659\u0000\u265a\u265b\u265c\u265d\u265e\u265f\u2660\u0000\u2661\u2667\u0000\u2662\u2663\u2664\u2665\u2666\u0000\u0000\u014a\u2668\u2669\u266a\u266b\u266c\u266d\u266e\u266f\u2672\u0000\u2673\u2674\u2675\u0000\u2676\u2677\u2678\u0000\u0000\u2634\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u263c\u263e\u2670\u2671\u0000\u0000\u10a7\u10d6\u1150\u11a4\u11ea\u122a\u1262\u1298\u12ce\u1303\u047a\u107b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u264d\u010d\u0109\u0111\u014b\u014c\u014d' + '\u0000\u0159\u0167\u017d\u0000\u2679\u267a\u267b\u267c\u267d\u267e\u267f\u2681\u0000\u0000\u2683\u2684\u0000\u0000\u2685\u2686\u2687\u2688\u2689\u268a\u268b\u268c\u268d\u268e\u268f\u2690\u2691\u2692\u2693\u2695\u2697\u2698\u2699\u269a\u269b\u269c\u0000\u269d\u269e\u269f\u26a0\u26a1\u26a2\u26a4\u0000\u26a5\u26a6\u0000\u26a7\u26a9\u26aa\u26ab\u26ac\u0000\u0000\u014e\u26ad\u26ae\u26af\u26b0\u26b1\u26b2\u26b3\u26b4\u0000\u0000\u26b7\u26b8\u0000\u0000\u26b9\u26ba\u26bb\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0033\u26bc\u26bd\u0000\u0000\u0000\u0000\u2694\u2696\u0000\u26a3\u2680\u2682\u26b5\u26b6\u0000\u0000\u10a8\u10d7\u1151\u11a5\u11eb\u122b\u1263\u1299\u12cf\u1304\u0566\u26a8\u101f\u1020\u1021\u1022\u1023\u1024\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0168\u26cb\u0000\u26bf\u26c0\u26c1\u26c2\u26c3\u26c4\u0000\u0000\u0000\u26c5\u26c6\u26c7\u0000\u26c8\u26c9\u26ca\u26cc\u0000\u0000\u0000\u26cd\u26ce\u0000\u26de\u0000\u26cf\u26d0\u0000\u0000\u0000\u26d1\u26d2\u0000\u0000\u0000\u26d3\u26dd\u26d4\u0000\u0000\u0000\u26d5\u26d6\u26d7\u26dc\u26d8\u26db\u26da\u26d9\u26df\u26e0\u26e1\u26e2\u0000\u0000\u0000\u0000\u26e3\u26e4\u26e5\u26e6\u26e7\u0000\u0000\u0000\u26e8\u26e9\u26ea\u0000\u26eb\u26ec\u26ed\u26ee\u0000\u0000\u26be\u0000\u0000\u0000\u0000\u0000\u0000\u26ef\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u10a9\u10d8\u1152\u11a6\u11ec\u122c\u1264\u129a\u12d0\u1305\u102b\u102c\u102d\u0567\u0568\u0569\u056a\u056b\u056c\u107d\u056d\u0000\u0000\u0000\u0000\u0000' + '\u015a\u015b\u0169\u017e\u016a\u26f0\u26f1\u26f2\u26f3\u26f4\u26f5\u26f6\u26f8\u0000\u26fa\u26fb\u26fc\u0000\u26fd\u26fe\u26ff\u2700\u2701\u2702\u2703\u2704\u2705\u2707\u2708\u270a\u270b\u270c\u270d\u270e\u270f\u2710\u2711\u2712\u2713\u2714\u2715\u0000\u2716\u2717\u2718\u2719\u271a\u271b\u271c\u271d\u271e\u2724\u2725\u271f\u2720\u2721\u2722\u2723\u0000\u0000\u0000\u2727\u2728\u2729\u272a\u272b\u272c\u272d\u272e\u0000\u2731\u2732\u2733\u0000\u2734\u2735\u2736\u2737\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2738\u2739\u0000\u2706\u2709\u2726\u0000\u0000\u0000\u0000\u0000\u26f7\u26f9\u272f\u2730\u0000\u0000\u10aa\u10d9\u1153\u11a7\u11ed\u122d\u1265\u129b\u12d1\u1306\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u047b\u10ab\u10da\u1154\u11a8\u10db\u1155\u11a9\u056e' + '\u2771\u015c\u016b\u017f\u047c\u273a\u273b\u273c\u273d\u273e\u273f\u2740\u2742\u0000\u2744\u2745\u2746\u0000\u2747\u2748\u2749\u274a\u274b\u274c\u274d\u274e\u274f\u2750\u2751\u2752\u2753\u2754\u2755\u2756\u2757\u2758\u2759\u275a\u275b\u275c\u275d\u0000\u275e\u275f\u2760\u2761\u2762\u2763\u2764\u2765\u2766\u276c\u0000\u2767\u2768\u2769\u276a\u276b\u0000\u0000\u014f\u276e\u2772\u2773\u2774\u2775\u2776\u2777\u2778\u0000\u277b\u277c\u277d\u0000\u277e\u277f\u2780\u2781\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2782\u2783\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u276d\u0000\u2741\u2743\u2779\u277a\u0000\u0000\u10ac\u10dc\u1156\u11aa\u11ee\u122e\u1266\u129c\u12d2\u1307\u0000\u276f\u2770\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u016c\u015d\u016d\u0180\u27c6\u2784\u2785\u2786\u2787\u2789\u278a\u278b\u278d\u0000\u278f\u2790\u2791\u0000\u2792\u2793\u2794\u2795\u2797\u2798\u2799\u279a\u279b\u279c\u279d\u279e\u279f\u27a0\u27a1\u27a2\u27a3\u27a4\u27a6\u27a7\u27a8\u27a9\u27aa\u27ac\u27ad\u27ae\u27af\u27b0\u27b1\u27b3\u27b5\u27c3\u27b8\u27bf\u27c1\u27ba\u27bb\u27bc\u27bd\u27be\u27c4\u27d8\u27d9\u27c5\u27c7\u27c8\u27c9\u27ca\u27cb\u27cc\u27cd\u0000\u27d0\u27d1\u27d2\u0000\u27d3\u27d4\u27d5\u27d7\u27b6\u056f\u0000\u0000\u0000\u0000\u27b2\u27b4\u27c2\u27d6\u102e\u102f\u1030\u1031\u1032\u1033\u1034\u2788\u278c\u278e\u27ce\u27cf\u0000\u0000\u10ad\u10dd\u1157\u11ab\u11ef\u122f\u1267\u129d\u12d3\u1308\u1035\u1036\u1037\u1038\u1039\u103a\u103b\u103c\u103d\u0570\u27a5\u27ab\u27b7\u27b9\u27c0\u2796' + '\u0000\u015e\u016e\u0181\u0000\u27da\u27db\u27dc\u27dd\u27de\u27df\u27e0\u27e1\u27e2\u27e3\u27e4\u27e5\u27e6\u27e7\u27e8\u27e9\u27ea\u27eb\u0000\u0000\u0000\u27ec\u27ed\u27ee\u27ef\u27f0\u27f1\u27f2\u27f3\u27f4\u27f5\u27f6\u27f7\u27f8\u27f9\u27fa\u27fb\u27fc\u27fd\u27fe\u27ff\u2800\u2801\u2802\u2803\u0000\u2804\u2805\u2806\u2807\u2808\u2809\u280a\u280b\u280c\u0000\u280d\u0000\u0000\u280e\u280f\u2810\u2811\u2812\u2813\u2814\u0000\u0000\u0000\u2826\u0000\u0000\u0000\u0000\u2815\u2816\u2817\u2818\u2819\u281a\u0000\u281b\u0000\u281c\u2820\u2821\u2822\u2823\u2824\u2825\u281e\u0000\u0000\u0000\u0000\u0000\u0000\u10ae\u10de\u1158\u11ac\u11f0\u1230\u1268\u129e\u12d4\u1309\u0000\u0000\u281d\u281f\u047d\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u2904\u2905\u2906\u2907\u2908\u2909\u290a\u290b\u290c\u290d\u290e\u290f\u2910\u2911\u2912\u2913\u2914\u2915\u2916\u2917\u2918\u2919\u291a\u291b\u291c\u291d\u291e\u291f\u2920\u2921\u2922\u2923\u2924\u2925\u2926\u2927\u2928\u2929\u292a\u292b\u292c\u292d\u292e\u292f\u2930\u2931\u2932\u2933\u2934\u2935\u2936\u2937\u2938\u2939\u293a\u293b\u293c\u293d\u0000\u0000\u0000\u0000\u107e\u293e\u293f\u2940\u2941\u2942\u2943\u1053\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u0192\u047e\u10af\u10df\u1159\u11ad\u11f1\u1231\u1269\u129f\u12d5\u130a\u047f\u0480\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u2945\u2946\u0000\u2947\u0000\u2948\u2949\u294a\u294b\u294d\u0000\u294e\u2951\u294f\u2952\u2953\u2954\u2955\u2956\u2957\u2958\u2959\u295a\u295b\u295c\u295d\u295e\u295f\u2960\u2961\u2962\u2963\u2964\u2965\u2966\u0000\u2967\u0000\u2968\u2969\u296a\u294c\u296b\u296e\u296f\u2970\u2971\u2972\u2973\u2974\u2975\u2976\u2977\u2978\u2979\u297a\u297b\u297c\u297d\u297e\u297f\u0000\u0000\u2980\u2981\u2982\u2983\u2984\u0000\u1054\u0000\u019a\u019b\u019c\u019d\u019e\u019f\u0000\u0000\u10b0\u10e0\u115a\u11ae\u11f2\u1232\u126a\u12a0\u12d6\u130b\u0000\u0000\u296c\u296d\u2944\u2950\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u2a1f\u0578\u0579\u057a\u0483\u0484\u0485\u0486\u0487\u0488\u0489\u048c\u048d\u048e\u048f\u0490\u0491\u0492\u0493\u057b\u025e\u057c\u057d\u057e\u0000\u0000\u057f\u0580\u0581\u0582\u0583\u0584\u10b1\u10e1\u115b\u11af\u11f3\u1233\u126b\u12a1\u12d7\u130c\u10fa\u1173\u11c7\u120b\u124b\u1284\u12b9\u12ef\u1324\u10c8\u0585\u0000\u0586\u0000\u0587\u01a2\u0386\u0387\u0388\u0389\u0000\u0000\u29c8\u29cd\u29cf\u29d0\u29d3\u29d5\u29d7\u29d9\u0000\u29db\u29dd\u29df\u29e1\u29e2\u29e5\u29e7\u29e9\u29eb\u29ec\u29ef\u29f1\u29f3\u29f5\u29f6\u29f9\u29fb\u29fd\u29ff\u2a00\u2a03\u2a06\u2a08\u2a0a\u2a0c\u2a0f\u2a14\u2a16\u2a18\u2a1a\u2a1c\u2a1e\u29c9\u2a10\u29cc\u2a13\u0000\u0000\u0000\u0000\u2a29\u2a2a\u2a2b\u2a2e\u2a2f\u2a30\u2a31\u2a32\u2a33\u2a34\u2a35\u2a36\u2a37\u016f\u0182' + '\u2a2c\u2a2d\u0000\u0000\u2a38\u0494\u0000\u0000\u2a21\u2a23\u2a27\u2a28\u2a25\u2a22\u2a24\u2a26\u29ca\u29ce\u29d1\u29d2\u29d4\u29d6\u29d8\u29da\u0000\u29dc\u29de\u29e0\u29e3\u29e4\u29e6\u29e8\u29ea\u29ed\u29ee\u29f0\u29f2\u29f4\u29f7\u29f8\u29fa\u29fc\u29fe\u2a01\u2a02\u2a04\u2a07\u2a09\u2a0b\u2a0d\u2a11\u2a15\u2a17\u2a19\u2a1b\u2a1d\u2a20\u29cb\u2a05\u2a0e\u2a12\u0000\u0588\u0589\u058a\u058b\u058c\u058d\u058e\u058f\u0000\u0590\u0591\u0592\u0593\u0594\u0595\u0000\u0596\u0597\u048a\u048b\u0495\u0496\u0497\u0598\u0599\u059a\u059b\u0498\u0499\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u2bbe\u2bc0\u2bc2\u2bc6\u2bc9\u2bcb\u2bce\u2bd2\u2bd7\u2bde\u2be2\u2be3\u2be5\u2be7\u2bea\u2bed\u2bf1\u2bf2\u2bf3\u2bf6\u2bf9\u2bfd\u2bfe\u2c04\u2c07\u2c0a\u2c0c\u2c0e\u2c12\u2c14\u2c1b\u2c1e\u2c24\u2c2b\u2c2c\u2c2d\u2c2e\u2c2f\u2c30\u2c35\u2c36\u2c37\u2c38\u2c3a\u2c39\u2c3e\u2c40\u2c42\u2c45\u2c4a\u2c4e\u2c41\u2c50\u2c4c\u0170\u01a6\u0183\u2c56\u2c57\u2c0d\u2c11\u2c15\u2c21\u2c1c\u10b2\u10e2\u115c\u11b0\u11f4\u1234\u126c\u12a2\u12d8\u130d\u02ae\u02af\u049d\u049e\u049f\u04a0\u2c18\u2c19\u2c31\u2c32\u2c33\u2c34\u2c46\u2c47\u2c48\u2c49\u2bca\u2bd8\u2c26\u2c27\u2bfc\u2c0b\u2c13\u2bdc\u2c51\u2c58\u2c59\u2c1a\u2c2a\u2c52\u2c53\u2c5a\u2c5b\u2c5c\u2c5d\u2c5e\u2bee\u2c28\u2c29\u2c3f\u2c3c\u2c43\u2c44\u2bbf\u2bc1\u2bc3\u2bcc\u2bd5\u2bdf\u2bf4\u2bfa\u2bff\u2c00\u2c05' + '\u2c17\u2c1f\u2c16\u2c3b\u2c4b\u2c4d\u2c55\u2c5f\u2c61\u2c64\u2c65\u2c60\u2c62\u2c63\u2c02\u2c66\u10b3\u10e3\u115d\u11b1\u11f5\u1235\u126d\u12a3\u12d9\u130e\u2c67\u2c68\u2c3d\u2c4f\u059e\u059f\u1d7b\u1d7f\u1d83\u1d87\u1d8b\u1d8f\u1d93\u1d9b\u1d9f\u1da3\u1da7\u1dab\u1db0\u1db8\u1dbc\u1dc0\u1dc4\u1dc8\u1dcc\u1dd4\u1dd8\u1ddc\u1de0\u1de4\u1de8\u1dec\u1df0\u1df4\u1df8\u1dfc\u1e00\u1e08\u1e0c\u1d97\u1db4\u1dd0\u1e04\u1e10\u0000\u1e16\u0000\u0000\u0000\u0000\u0000\u1e20\u0000\u0000\u1d78\u1d7c\u1d80\u1d84\u1d88\u1d8c\u1d90\u1d98\u1d9c\u1da0\u1da4\u1da8\u1dac\u1db5\u1db9\u1dbd\u1dc1\u1dc5\u1dc9\u1dd1\u1dd5\u1dd9\u1ddd\u1de1\u1de5\u1de9\u1ded\u1df1\u1df5\u1df9\u1dfd\u1e05\u1e09\u1d94\u1db1\u1dcd\u1e01\u1e0d\u1e11\u1e13\u1e17\u1e19\u1e1b\u02c3\u1dae\u1e1d\u1e21\u1e23' + '\u3430\u3435\u3438\u343d\u3442\u3445\u344a\u344f\u3454\u3457\u345c\u345f\u3465\u346b\u346e\u3474\u3479\u347e\u3483\u3488\u3489\u348b\u348d\u348e\u348f\u3490\u3491\u3494\u3495\u3497\u3499\u349b\u349c\u349e\u34a1\u34a3\u34a5\u34a6\u34a7\u34a8\u34aa\u34ab\u34ad\u34ae\u34b0\u34b2\u34b4\u34b6\u34b8\u34b9\u34ba\u34bc\u34bd\u34be\u34bf\u34c1\u34c2\u34c3\u34c4\u34c5\u34c6\u34c7\u34c8\u34c9\u34ca\u34cc\u34cd\u34ce\u34cf\u34d0\u34d1\u34d2\u34d4\u34d5\u34d6\u34d7\u34d8\u34da\u34db\u34dc\u34dd\u34de\u34df\u34e0\u34e1\u34e2\u34e3\u34e4\u34e6\u34e8\u34ea\u34eb\u34ec\u34ed\u34ee\u350c\u350d\u3510\u3513\u3516\u3519\u351c\u351f\u3522\u3525\u3528\u352b\u352e\u3531\u3534\u3537\u353a\u353d\u3540\u3543\u3546\u3549\u354c\u354f\u3550\u3551\u3552\u3553\u3554\u3555\u3556\u3557\u3558' + '\u3559\u355a\u355b\u355c\u355d\u355f\u3561\u3562\u3563\u3565\u3566\u3567\u3568\u3569\u356a\u356b\u356c\u356d\u356f\u3571\u3572\u3574\u3575\u3576\u3577\u3578\u3579\u357a\u357b\u357c\u357d\u357f\u3580\u3581\u3583\u3584\u3585\u3586\u3587\u3588\u35a0\u35a1\u35a2\u35a5\u35a6\u35a9\u35ac\u35ad\u35ae\u35b1\u35b4\u35b7\u35ba\u35bd\u35c0\u35c1\u35c2\u35c3\u35c4\u35c5\u35c6\u35c7\u35c8\u35c9\u35ca\u35cb\u35cc\u35cd\u35ce\u35cf\u35d0\u35d1\u35d3\u35d5\u35d6\u35d7\u35d8\u35da\u35db\u35dd\u35de\u35df\u35e0\u35e1\u35e3\u35e4\u35e5\u35e6\u35e8\u35e9\u35eb\u35ec\u35ed\u35ee\u35f0\u35f1\u35f3\u35f4\u35f5\u35f6\u35f7\u35f8\u35f9\u35fa\u35fb\u35fc\u35fd\u35fe\u35ff\u3600\u3601\u3602\u3603\u3604\u3606\u3608\u3609\u360a\u360b\u360c\u360d\u360e\u360f\u3610\u3611\u3612\u3613\u3614' + '\u236c\u236d\u236e\u236f\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237a\u237b\u237d\u237e\u237f\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238a\u238b\u238c\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239a\u239b\u239c\u239d\u239e\u239f\u23a0\u23a1\u23a3\u23a4\u23a5\u23a6\u23a7\u23a8\u23a9\u23aa\u23b2\u23b3\u23b4\u23b5\u23b6\u23b7\u23b8\u23b9\u23bb\u23bc\u23bd\u23be\u23bf\u23c0\u23c1\u23c2\u23c3\u0000\u23c4\u23c5\u23c6\u23c7\u0000\u0000\u23c8\u23c9\u23ca\u23cb\u23cc\u23cd\u23ce\u0000\u23cf\u0000\u23d0\u23d1\u23d2\u23d3\u0000\u0000\u23d4\u23d5\u23d6\u23d7\u23d8\u23d9\u23da\u23db\u23e1\u23e2\u23e3\u23e4\u23e5\u23e6\u23e7\u23e8\u23e9\u23ea\u23eb\u23ec\u23ed\u23ee\u23ef\u23f0\u23f2\u23f3\u23f4\u23f5\u23f6\u23f7\u23f8\u23f9' + '\u23fb\u23fc\u23fd\u23fe\u23ff\u2400\u2401\u2402\u2403\u0000\u2404\u2405\u2406\u2407\u0000\u0000\u2408\u2409\u240a\u240b\u240c\u240d\u240e\u240f\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u241a\u241b\u241c\u241d\u241e\u241f\u2420\u2421\u2423\u2424\u2425\u2426\u2427\u2428\u2429\u242a\u242b\u0000\u242c\u242d\u242e\u242f\u0000\u0000\u2430\u2431\u2432\u2433\u2434\u2435\u2436\u0000\u2437\u0000\u2438\u2439\u243a\u243b\u0000\u0000\u243c\u243d\u243e\u243f\u2440\u2441\u2442\u2443\u2444\u2445\u2446\u2447\u2448\u2449\u244a\u0000\u244b\u244c\u244d\u244e\u244f\u2450\u2451\u2452\u245a\u245b\u245c\u245d\u245e\u245f\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246a\u246b\u246c\u246d\u246e\u246f\u2470\u2471\u2479\u247a\u247b\u247c\u247d\u247e\u247f\u2480' + '\u2482\u2483\u2484\u2485\u2486\u2487\u2488\u2489\u248b\u248c\u248d\u248e\u248f\u2490\u2491\u2492\u2493\u0000\u2494\u2495\u2496\u2497\u0000\u0000\u2498\u2499\u249a\u249b\u249c\u249d\u249e\u249f\u24a4\u24a5\u24a6\u24a7\u24a8\u24a9\u24aa\u24ab\u24ad\u24ae\u24af\u24b0\u24b1\u24b2\u24b3\u24b4\u24bd\u24be\u24bf\u24c0\u24c1\u24c2\u24c3\u24c4\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb\u24cc\u24cd\u24d5\u24d6\u24d7\u24d8\u24d9\u24da\u24db\u24dc\u24dd\u24de\u24df\u24e0\u24e1\u24e2\u24e3\u24e4\u24e9\u24ea\u24eb\u24ec\u24ed\u24ee\u24ef\u24f0\u24f6\u24f7\u24f8\u0000\u0000\u0144\u0143\u0142\u02c4\u0257\u0290\u0258\u0259\u025a\u025b\u027b\u02c5\u10e4\u115e\u11b2\u11f6\u1236\u126e\u12a4\u12da\u130f\u103e\u103f\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u0000\u0000\u0000' + '\u238d\u238e\u238f\u2390\u23dc\u23dd\u23de\u23df\u24e5\u24e6\u24e7\u24e8\u24f1\u24f2\u24f3\u24f4\u0518\u0519\u051a\u051b\u051c\u051d\u051e\u051f\u0520\u0521\u0000\u0000\u0000\u0000\u0000\u0000\u2ee3\u2ee5\u2ee7\u2ee9\u2eeb\u2eed\u2eef\u2ef1\u2ef3\u2ef5\u2ef7\u2ef9\u2efb\u2efd\u2eff\u2f01\u2f03\u2f05\u2f07\u2f09\u2f0b\u2f0d\u2f0f\u2f11\u2f13\u2f15\u2f17\u2f19\u2f1b\u2f1d\u2f1f\u2f21\u2f23\u2f25\u2f27\u2f29\u2f2b\u2f2d\u2f2f\u2f31\u2f33\u2f35\u2f37\u2f39\u2f3b\u2f3d\u2f3f\u2f41\u2f43\u2f45\u2f47\u2f49\u2f4b\u2f4d\u2f4f\u2f51\u2f53\u2f55\u2f57\u2f59\u2f5b\u2f5d\u2f5f\u2f61\u2f63\u2f65\u2f67\u2f69\u2f6b\u2f6d\u2f6f\u2f71\u2f73\u2f75\u2f77\u2f79\u2f7b\u2f7d\u2f7f\u2f81\u2f83\u2f85\u2f87\u2f89\u2f8b\u2f8d\u0000\u0000\u2f82\u2f84\u2f86\u2f88\u2f8a\u2f8c\u0000\u0000' + '\u01fe\u2f8e\u2f8f\u2f90\u2f91\u2f92\u2f93\u2f94\u2f95\u2f96\u2f97\u2f98\u2f99\u2f9a\u2f9b\u2f9c\u2f9d\u2f9e\u2f9f\u2fa0\u2fa1\u2fa2\u2fa3\u2fa4\u2fa5\u2fa6\u2fa7\u2fa8\u2fa9\u2faa\u2fab\u2fac\u2fad\u2fae\u2faf\u2fb0\u2fb1\u2fb2\u2fb3\u2fb4\u2fb5\u2fb6\u2fb7\u2fb8\u2fb9\u2fba\u2fbb\u2fbc\u2fbd\u2fbe\u2fbf\u2fc0\u2fc1\u2fc2\u2fc3\u2fc4\u2fc5\u2fc6\u2fc7\u2fc8\u2fc9\u2fca\u2fcb\u2fcc\u2fcd\u2fce\u2fcf\u2fd0\u2fd1\u2fd2\u2fd3\u2fd4\u2fd5\u2fd6\u2fd7\u2fd8\u2fd9\u2fda\u2fdb\u2fdc\u2fdd\u2fde\u2fdf\u2fe0\u2fe1\u2fe2\u2fe3\u2fe4\u2fe5\u2fe6\u2fe7\u2fe8\u2fe9\u2fea\u2feb\u2fec\u2fed\u2fee\u2fef\u2ff0\u2ff1\u2ff2\u2ff3\u2ff4\u2ff5\u2ff6\u2ff7\u2ff8\u2ff9\u2ffa\u2ffb\u2ffc\u2ffd\u2ffe\u2fff\u3000\u3001\u3002\u3003\u3004\u3005\u3006\u3007\u3008\u3009\u300a\u300b\u300c' + '\u300d\u300e\u300f\u3010\u3011\u3012\u3013\u3014\u3015\u3016\u3017\u3018\u3019\u301a\u301b\u301c\u301d\u301e\u301f\u3020\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u302a\u302b\u302c\u302d\u302e\u302f\u3030\u3031\u3032\u3033\u3034\u3035\u3036\u3037\u3038\u3039\u303a\u303b\u303c\u303d\u303e\u303f\u3040\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304a\u304b\u304c\u304d\u304e\u304f\u3050\u3051\u3052\u3053\u3054\u3055\u3056\u3057\u3058\u3059\u305a\u305b\u305c\u305d\u305e\u305f\u3060\u3061\u3062\u3063\u3064\u3065\u3066\u3067\u3068\u3069\u306a\u306b\u306c\u306d\u306e\u306f\u3070\u3071\u3072\u3073\u3074\u3075\u3076\u3077\u3078\u3079\u307a\u307b\u307c\u307d\u307e\u307f\u3080\u3081\u3082\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308a\u308b\u308c' + '\u308d\u308e\u308f\u3090\u3091\u3092\u3093\u3094\u3095\u3096\u3097\u3098\u3099\u309a\u309b\u309c\u309d\u309e\u309f\u30a0\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8\u30a9\u30aa\u30ab\u30ac\u30ad\u30ae\u30af\u30b0\u30b1\u30b2\u30b3\u30b4\u30b5\u30b6\u30b7\u30b8\u30b9\u30ba\u30bb\u30bc\u30bd\u30be\u30bf\u30c0\u30c1\u30c2\u30c3\u30c4\u30c5\u30c6\u30c7\u30c8\u30c9\u30ca\u30cb\u30cc\u30cd\u30ce\u30cf\u30d0\u30d1\u30d2\u30d3\u30d4\u30d5\u30d6\u30d7\u30d8\u30d9\u30da\u30db\u30dc\u30dd\u30de\u30df\u30e0\u30e1\u30e2\u30e3\u30e4\u30e5\u30e6\u30e7\u30e8\u30e9\u30ea\u30eb\u30ec\u30ed\u30ee\u30ef\u30f0\u30f1\u30f2\u30f3\u30f4\u30f5\u30f6\u30f7\u30f8\u30f9\u30fa\u30fb\u30fc\u30fd\u30fe\u30ff\u3100\u3101\u3102\u3103\u3104\u3105\u3106\u3107\u3108\u313b\u3109\u310b\u310c' + '\u310d\u310e\u310f\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311a\u311c\u311d\u311e\u311f\u3120\u3121\u3122\u3123\u312a\u312b\u312c\u312d\u312e\u312f\u3130\u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3138\u3139\u313a\u313c\u313d\u313e\u313f\u3140\u3141\u3142\u3143\u3144\u3145\u3146\u3147\u3148\u3149\u314a\u314b\u314c\u314d\u314e\u314f\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315a\u315b\u315c\u315d\u315e\u315f\u3160\u3161\u3162\u3163\u3164\u3165\u3166\u3167\u3168\u3169\u316a\u316b\u316c\u316d\u316e\u316f\u3170\u3171\u3172\u3173\u3174\u3175\u3176\u3177\u3178\u3179\u317a\u317b\u317c\u317d\u317e\u317f\u3180\u3181\u3182\u3183\u3184\u3185\u3186\u3187\u3188\u3189\u318a\u318b\u318c\u318d\u318e\u318f\u3190\u3191\u3192\u3193\u3194' + '\u3195\u3196\u3197\u3198\u3199\u319a\u319b\u319c\u319d\u319e\u319f\u31a0\u31a1\u31a2\u31a3\u31a4\u31a5\u31a6\u31a7\u31a8\u31a9\u31aa\u31ab\u31ac\u31ad\u31ae\u31af\u31b0\u31b1\u31b2\u31b3\u31b4\u31b5\u31b6\u31b7\u31b8\u31b9\u31ba\u31bb\u31bc\u31bd\u31be\u31bf\u31c0\u31c1\u31c2\u31c3\u31c4\u31c5\u31c6\u31c7\u31c8\u31c9\u31ca\u31cb\u31cc\u31cd\u31ce\u31cf\u31d0\u31d1\u31d2\u31d3\u31d4\u31d5\u31d6\u31d7\u31d8\u31d9\u31da\u31db\u31dc\u31dd\u31de\u31df\u31e0\u31e1\u31e2\u31e3\u31e4\u31e5\u31e6\u31e7\u31e8\u31e9\u31ea\u31eb\u31ec\u31ed\u31ee\u31ef\u31f0\u31f1\u31f2\u31f3\u31f4\u31f5\u31f6\u31f7\u31f8\u31f9\u31fa\u31fb\u31fc\u31fd\u31fe\u31ff\u3200\u3201\u059c\u0293\u310a\u311b\u3124\u3125\u3126\u3127\u3128\u3129\u3202\u3203\u3204\u3205\u3206\u3207\u3208\u3209\u320a' + '\u01de\u3251\u3252\u3253\u3254\u3255\u3256\u3257\u3258\u3259\u325a\u325b\u325c\u325d\u325e\u325f\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326a\u038a\u038b\u0000\u0000\u0000\u326b\u326c\u326d\u32b6\u326e\u326f\u3270\u3271\u3273\u3274\u32b2\u32b4\u3275\u3276\u3277\u3279\u327a\u327b\u327c\u327d\u327e\u327f\u3280\u3282\u32b9\u3283\u3285\u3286\u3287\u3288\u3289\u328a\u328b\u328c\u328d\u328f\u3290\u3291\u3292\u3294\u3295\u3297\u3298\u3299\u329b\u329c\u329d\u329f\u32a0\u32a1\u32a2\u32a3\u32a4\u3296\u32a5\u32a7\u32a8\u32a9\u32ab\u32ac\u32ad\u32ae\u32af\u32b0\u32b7\u32bb\u32bc\u32b8\u32ba\u32bd\u32be\u32bf\u32c0\u3284\u329a\u0262\u0263\u0264\u3293\u32aa\u3272\u3281\u329e\u32b1\u3278\u328e\u32a6\u32b3\u32b5\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u2adb\u2adc\u2add\u2ade\u2adf\u2ae0\u2ae1\u2ae2\u2ae3\u2ae4\u2ae5\u2ae6\u2ae7\u0000\u2ae8\u2ae9\u2aea\u2aeb\u2aec\u2aed\u2aee\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2aef\u2af0\u2af1\u2af2\u2af3\u2af4\u2af5\u2af6\u2af7\u2af8\u2af9\u2afa\u2afb\u2afc\u2afd\u2afe\u2aff\u2b00\u2b01\u2b02\u2b03\u02ab\u02ac\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2b04\u2b05\u2b06\u2b07\u2b08\u2b09\u2b0a\u2b0b\u2b0c\u2b0d\u2b0e\u2b0f\u2b10\u2b11\u2b12\u2b13\u2b14\u2b15\u2b16\u2b17\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2b18\u2b19\u2b1a\u2b1b\u2b1c\u2b1d\u2b1e\u2b1f\u2b20\u2b21\u2b22\u2b23\u2b24\u0000\u2b25\u2b26\u2b27\u0000\u2b28\u2b29\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u2c6f\u2c70\u2c71\u2c72\u2c73\u2c74\u2c75\u2c76\u2c77\u2c78\u2c79\u2c7a\u2c7b\u2c7c\u2c7d\u2c7e\u2c7f\u2c80\u2c81\u2c82\u2c83\u2c84\u2c85\u2c86\u2c87\u2c88\u2c89\u2c8a\u2c8b\u2c8c\u2c8d\u2c8e\u2c8f\u2c90\u2c91\u2c93\u2c94\u2c95\u2c96\u2c97\u2c98\u2c99\u2c9a\u2c9b\u2c9c\u2c9d\u2c9e\u2c9f\u2ca0\u2ca1\u2ca2\u2ca3\u0000\u0000\u2ca4\u2ca5\u2ca6\u2ca7\u2ca8\u2ca9\u2caa\u2cab\u2cac\u2cad\u2cae\u2caf\u2cb0\u2cb1\u2cb2\u2cb3\u0171\u0184\u01a7\u01a8\u01a9\u0034\u0035\u0036\u0037\u0038\u0039\u003a\u2cb4\u0000\u02b0\u02b1\u025f\u1055\u04a1\u04a2\u04a3\u107f\u2c92\u003b\u0000\u0000\u10b4\u10e5\u115f\u11b3\u11f7\u1237\u126f\u12a5\u12db\u1310\u0000\u0000\u0000\u0000\u0000\u0000\u10b5\u10e6\u1160\u11b4\u11f8\u1238\u1270\u12a6\u12dc\u1311\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0472\u028c\u0226\u0291\u025c\u025d\u0200\u0201\u0227\u0292\u0000\u0000\u0000\u0000\u0000\u0000\u10b6\u10e7\u1161\u11b5\u11f9\u1239\u1271\u12a7\u12dd\u1312\u0000\u0000\u0000\u0000\u0000\u0000\u2e42\u2e44\u2e47\u2e4d\u2e4f\u2e52\u2e54\u2e57\u2e58\u2e59\u2e5e\u2e60\u2e63\u2e65\u2e6a\u2e6c\u2e6d\u2e6e\u2e75\u2e78\u2e7b\u2e81\u2e85\u2e88\u2e8a\u2e8c\u2e8f\u2e94\u2e95\u2e98\u2e9c\u2e9f\u2ea0\u2ea1\u2ea2\u2e41\u2e45\u2e48\u2e4e\u2e50\u2e53\u2e55\u2e5a\u2e5f\u2e61\u2e64\u2e66\u2e6b\u2e76\u2e79\u2e7c\u2e82\u2e96\u2e86\u2e8b\u2e90\u2e9a\u2e9d\u2ea3\u2ea4\u2e7f\u2e46\u2e49\u2e4c\u2e56\u2e51\u2e5b\u2e91\u2e67\u2e69\u2e62\u2e6f\u2e77\u2e7a\u2e83\u2e8d\u2e9b\u2e9e\u2e97\u2e99\u2ea5\u2e7d\u2e87\u2e4a\u2e92\u2e89\u2e8e\u2e84\u2e7e\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u2e3a\u2e3b\u2e3c\u2e3d\u2e3e\u2e3f\u2e40\u2e43\u2e4b\u2e93\u2e5c\u2e80\u2ea6\u2ea8\u2ea9\u2eab\u2eac\u2eaf\u2eb1\u2eb2\u2eb4\u2eb6\u2eb8\u2eb9\u2ead\u2eb7\u2e68\u2e5d\u2e70\u2e71\u2ea7\u2eaa\u2eae\u2eb0\u2e72\u2eb5\u2e73\u2e74\u2eba\u2ebb\u2eb3\u2ebd\u2ebc\u0000\u0000\u0000\u0000\u0000\u320b\u320c\u320d\u320e\u320f\u3210\u3211\u3212\u3213\u3214\u3215\u3216\u3217\u3218\u3219\u321a\u321b\u321c\u321d\u321e\u321f\u3220\u3221\u3222\u3223\u3224\u3225\u3226\u3227\u3228\u3229\u322a\u322b\u322c\u322d\u322e\u322f\u3230\u3231\u3232\u3233\u3234\u3235\u3236\u3237\u3238\u3239\u323a\u323b\u323c\u323d\u323e\u323f\u3240\u3241\u3242\u3243\u3244\u3245\u3246\u3247\u3248\u3249\u324a\u324b\u324c\u324d\u324e\u324f\u3250\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u2aa7\u2aa8\u2aa9\u2aaa\u2aab\u2aac\u2aad\u2aae\u2aaf\u2ab1\u2ab2\u2ab3\u2ab5\u2ab6\u2ab7\u2ab8\u2ab9\u2aba\u2abb\u2abc\u2abd\u2abe\u2abf\u2ac0\u2ac1\u2ac2\u2ac3\u2ac4\u2ac5\u2ab0\u2ab4\u0000\u2ac6\u2ac7\u2ac8\u2ac9\u2aca\u2acb\u2acc\u2acd\u2ace\u2acf\u2ad0\u2ad1\u0000\u0000\u0000\u0000\u2ad2\u2ad3\u2ad4\u2ad5\u2ad6\u2ad7\u2ad8\u2ad9\u2ada\u01b2\u01b3\u01b4\u0000\u0000\u0000\u0000\u059d\u0000\u0000\u0000\u026f\u027c\u10b7\u10e8\u1162\u11b6\u11fa\u123a\u1272\u12a8\u12de\u1313\u2cb5\u2cb6\u2cb7\u2cb8\u2cb9\u2cba\u2cbb\u2cbc\u2cbd\u2cbe\u2cbf\u2cc0\u2cc1\u2cc2\u2cc3\u2cc4\u2cc5\u2cc6\u2cc7\u2cc8\u2cc9\u2cca\u2ccb\u2ccc\u2ccd\u2cce\u2ccf\u2cd0\u2cd1\u2cd2\u0000\u0000\u2cd3\u2cd4\u2cd5\u2cd6\u2cd7\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u2cd8\u2cd9\u2cda\u2cdb\u2cdc\u2cdd\u2cde\u2cdf\u2ce0\u2ce1\u2ce2\u2ce3\u2ce4\u2ce5\u2ce6\u2ce7\u2ce8\u2ce9\u2cea\u2ceb\u2cec\u2ced\u2cee\u2cef\u2cf0\u2cf1\u2cf2\u2cf3\u2cf4\u2cf7\u2cf8\u2cf9\u2cfa\u2cfb\u2cfc\u2cfd\u2cfe\u2cff\u2d00\u2d01\u2d02\u2d03\u2d04\u2d05\u0000\u0000\u0000\u0000\u2d06\u2d07\u2d08\u2d09\u2d0a\u2d0b\u2d0c\u2d0d\u2d0e\u2d0f\u2d10\u2d11\u2d12\u2d13\u2d14\u2d15\u2d16\u2d17\u2d18\u2d19\u2d1a\u2d1b\u2d1c\u2d1d\u2d1e\u2d1f\u0000\u0000\u0000\u0000\u0000\u0000\u10b8\u10e9\u1163\u11b7\u11fb\u123b\u1273\u12a9\u12df\u1314\u10ea\u0000\u0000\u0000\u2cf5\u2cf6\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05ba\u05bb\u05bc\u05bd\u05be\u05bf\u05c0\u05c1\u05c2' + '\u2b2a\u2b2b\u2b2c\u2b2d\u2b2e\u2b2f\u2b30\u2b31\u2b32\u2b33\u2b34\u2b35\u2b36\u2b37\u2b38\u2b39\u2b3a\u2b3b\u2b3c\u2b3d\u2b3e\u2b3f\u2b40\u2b41\u2b42\u2b43\u2b44\u2b45\u0000\u0000\u02c6\u02c7\u2d20\u2d21\u2d22\u2d23\u2d24\u2d25\u2d26\u2d29\u2d2a\u2d2b\u2d2c\u2d2d\u2d2e\u2d2f\u2d30\u2d31\u2d32\u2d33\u2d34\u2d35\u2d36\u2d37\u2d38\u2d39\u2d3a\u2d3b\u2d3c\u2d3d\u2d40\u2d41\u2d42\u2d43\u2d44\u2d45\u2d46\u2d47\u2d48\u2d49\u2d4a\u2d4c\u2d4d\u2d4e\u2d4f\u2d50\u2d51\u2d5a\u2d5b\u2d5c\u2d5d\u2d5e\u2d5f\u2d52\u2d4b\u2d54\u2d55\u2d56\u2d27\u2d28\u2d3e\u2d3f\u2d57\u2d58\u2d59\u0000\u2d72\u2d60\u2d62\u2d63\u2d64\u2d65\u2d66\u2d67\u2d68\u2d69\u2d6a\u2d53\u2d61\u2d71\u2d6b\u2d6c\u2d6e\u2d6f\u2d70\u2d6d\u0172\u01aa\u01ab\u01ac\u01ad\u01ae\u01af\u01b0\u01b1\u0000\u0000\u0000' + '\u10b9\u10eb\u1164\u11b8\u11fc\u123c\u1274\u12aa\u12e0\u1315\u0000\u0000\u0000\u0000\u0000\u0000\u10ba\u10ec\u1165\u11b9\u11fd\u123d\u1275\u12ab\u12e1\u1316\u0000\u0000\u0000\u0000\u0000\u0000\u04a4\u04a5\u04a6\u04a7\u04a8\u04a9\u04aa\u1056\u02b2\u02b3\u02b4\u02b5\u04ab\u04ac\u0000\u0000\u003c\u003d\u003e\u003f\u0040\u0081\u0082\u0083\u0084\u0085\u0086\u0041\u0042\u0087\u009c\u18b4\u18d0\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u015f\u0160\u0173\u018e\u0185\u2db8\u2db9\u2dba\u2dbb\u2dbc\u2dbd\u2dbe\u2dbf\u2dc0\u2dc1\u2dc2\u2dc3\u2dc4\u2dc5\u2dc6\u2dc9\u2dca\u2dcb\u2dcc\u2dcd\u2dce\u2dcf\u2dd0\u2dd1\u2dd2\u2dd3\u2dd4\u2dd5\u2dd6\u2dd7\u2dd9\u2dda\u2ddb\u2ddc\u2ddd\u2ddf\u2de0\u2de1\u2de2\u2de3\u2de4\u2de5\u2de6\u2de8\u2de9\u2dea\u2ded\u0150\u2dee\u2def\u2df0\u2df1\u2df2\u2df3\u2df4\u2df5\u2df6\u2df7\u2df8\u2df9\u2dfa\u2dfb\u2dfc\u2dfd\u2dc7\u2dc8\u2dd8\u2dde\u2de7\u2deb\u2dec\u0000\u0000\u0000\u0000\u10bb\u10ed\u1166\u11ba\u11fe\u123e\u1276\u12ac\u12e2\u1317\u02c8\u02c9\u0294\u0260\u02b6\u02b7\u01ff\u05c3\u05c4\u05c5\u05c6\u05c7\u05c8\u05c9\u05ca\u05cb\u05cc\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u05cd\u05ce\u05cf\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u0000\u0000\u0000' + '\u0174\u0190\u0186\u28d1\u28d3\u28d4\u28d5\u28d6\u28d7\u28d8\u28d9\u28dc\u28dd\u28de\u28df\u28e0\u28e1\u28e2\u28e3\u28e4\u28e5\u28e6\u28e7\u28e8\u28e9\u28eb\u28ee\u28f0\u28f3\u28f6\u28f8\u28f9\u28fb\u28ef\u28f1\u28f4\u28fc\u28fd\u28fe\u28ff\u2900\u2901\u2902\u2903\u28ed\u28f7\u28db\u28fa\u10bc\u10ee\u1167\u11bb\u11ff\u123f\u1277\u12ad\u12e3\u1318\u28d2\u28f2\u28f5\u28ea\u28da\u28ec\u2b46\u2b47\u2b48\u2b49\u2b4a\u2b4b\u2b4c\u2b4d\u2b4e\u2b4f\u2b50\u2b51\u2b52\u2b53\u2b54\u2b55\u2b56\u2b57\u2b58\u2b59\u2b5a\u2b5b\u2b5c\u2b5d\u2b5e\u2b5f\u2b60\u2b61\u2b62\u2b63\u2b64\u2b65\u2b66\u2b67\u2b68\u2b69\u2b6a\u2b6b\u0151\u2b6c\u2b6d\u2b6e\u2b6f\u2b70\u2b71\u2b72\u2b73\u2b74\u2b75\u2b76\u2b77\u2b78\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u04b8\u04b9\u04ba\u04bb' + '\u2a39\u2a3a\u2a3b\u2a3c\u2a3d\u2a3e\u2a3f\u2a40\u2a41\u2a42\u2a46\u2a47\u2a48\u2a49\u2a4a\u2a4b\u2a4c\u2a4d\u2a4e\u2a4f\u2a50\u2a51\u2a52\u2a53\u2a54\u2a55\u2a56\u2a58\u2a5a\u2a5b\u2a5c\u2a5d\u2a5e\u2a5f\u2a60\u2a61\u2a57\u2a59\u2a63\u2a64\u2a65\u2a66\u2a67\u2a68\u2a69\u2a6a\u2a6b\u2a6c\u2a6d\u2a6e\u2a6f\u2a70\u2a71\u2a72\u2a62\u0152\u0000\u0000\u0000\u02a7\u02a8\u049a\u049b\u049c\u10bd\u10ef\u1168\u11bc\u1200\u1240\u1278\u12ae\u12e4\u1319\u0000\u0000\u0000\u2a43\u2a44\u2a45\u10be\u10f0\u1169\u11bd\u1201\u1241\u1279\u12af\u12e5\u131a\u2ebe\u2ebf\u2ec0\u2ec1\u2ec2\u2ec3\u2ec4\u2ec5\u2ec6\u2ec7\u2ec8\u2ec9\u2eca\u2ecb\u2ecc\u2ecd\u2ece\u2ecf\u2ed0\u2ed1\u2ed2\u2ed3\u2ed4\u2ed5\u2ed6\u2ed7\u2ed8\u2ed9\u2eda\u2edb\u2edc\u2edd\u2ede\u2edf\u2ee0\u2ee1\u02bf\u02c0' + '\u1b7f\u1b92\u1c2f\u1c54\u1c5d\u1c5e\u1ccc\u1cde\u1c7b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u1d79\u1d7d\u1d81\u1d85\u1d89\u1d8d\u1d91\u1d99\u1d9d\u1da1\u1da5\u1da9\u1dad\u1db6\u1dba\u1dbe\u1dc2\u1dc6\u1dca\u1dd2\u1dd6\u1dda\u1dde\u1de2\u1de6\u1dea\u1dee\u1df2\u1df6\u1dfa\u1dfe\u1e06\u1e0a\u1d95\u1db2\u1dce\u1e02\u1e0e\u1e12\u1e14\u1e18\u1e1a\u1e1c\u0000\u0000\u1e1e\u1e22\u1e24\u04ad\u04ae\u04af\u04b0\u04b1\u04b2\u04b3\u04b4\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2583\u2584\u2585\u2586\u0175\u2587\u2588\u2589\u258a\u0187\u0188\u0000\u258c\u258d\u0000\u0000\u0000\u258b\u0000\u0000\u0000\u0000\u0000' + '\u1397\u139d\u139e\u13c4\u13f3\u1435\u1436\u1486\u14a3\u1578\u1594\u15c4\u1603\u1649\u1681\u16f2\u1701\u16f3\u1702\u16fc\u16f6\u1718\u1708\u170a\u1734\u1786\u178c\u180c\u1866\u1869\u186b\u1884\u18a4\u18ca\u1929\u1940\u197a\u197b\u19c7\u1a4b\u1a74\u1a82\u1abe\u1c02\u133a\u137d\u13b7\u13c3\u140e\u1451\u1490\u14d2\u150b\u1546\u158f\u15a7\u15df\u162c\u165e\u1680\u169f\u1717\u1723\u1763\u17ef\u1825\u18bb\u1338\u13a3\u13a7\u139f\u13b6\u140d\u144f\u1495\u149b\u14a4\u14d1\u1579\u15a5\u162a\u1692\u169d\u1700\u1709\u170b\u1721\u17ed\u1823\u186a\u1882\u1894\u197c\u19bf\u19c5\u19ca\u1ab6\u1aba\u1545\u1762\u1824\u1895\u19c0\u19c6\u1a7e\u1ab7\u1abb\u186c\u13c5\u1439\u14c2\u164b\u1682\u1739\u1789\u1797\u17d3\u1811\u192c\u14f6\u1c1d\u14e3\u1801\u157d\u1585\u1735\u1875\u188a' + '\u13c8\u143a\u14c3\u14f2\u15c5\u1614\u164c\u168a\u173a\u178e\u17d4\u17e1\u18a7\u18e5\u192d\u139a\u13a9\u143f\u148b\u149c\u14a2\u1497\u157f\u1704\u17e4\u1876\u1943\u13ac\u13db\u13fd\u1420\u14a1\u14b1\u159b\u14ec\u187d\u157c\u1584\u1575\u157e\u1599\u1617\u1615\u1600\u164f\u1886\u1685\u168c\u167f\u170f\u1743\u17d7\u17df\u1813\u1871\u1889\u1867\u18aa\u18b1\u191b\u1932\u1934\u193d\u1a1c\u0043\u0044\u0088\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u1759\u004c\u004d\u004e\u004f\u0089\u008a\u0050\u1958\u135f\u137b\u138b\u1391\u13e5\u1421\u141e\u14cb\u14ef\u159f\u15d4\u15ff\u164a\u1656\u167e\u1780\u1784\u17a7\u17bf\u1914\u13a5\u13b0\u13ce\u1493\u14ab\u160e\u16bb\u171b\u17dd\u183f\u18b5\u1349\u16ab\u1833\u0051\u0052\u0053\u0054\u008b\u0000\u0055\u008c\u008d\u0056\u008e' + '\u135e\u135d\u13b9\u13b8\u13bb\u13ba\u13bd\u13bc\u13e8\u13e7\u1412\u1411\u1418\u1417\u141c\u141b\u1414\u1413\u141a\u1419\u1481\u1480\u147f\u147e\u1471\u1470\u1473\u1472\u147d\u147c\u14b3\u14b2\u14e0\u14df\u1513\u1512\u151b\u151a\u1511\u1510\u1515\u1514\u151d\u151c\u1563\u1562\u1565\u1564\u15a9\u15a8\u15b1\u15b0\u15b3\u15b2\u15e9\u15e8\u15f1\u15f0\u15ed\u15ec\u15eb\u15ea\u162e\u162d\u1630\u162f\u1632\u1631\u1668\u1667\u166e\u166d\u1672\u1671\u1670\u166f\u16d1\u16d0\u16d3\u16d2\u16df\u16de\u16dd\u16dc\u1725\u1724\u1727\u1726\u1769\u1768\u1773\u1772\u1779\u1778\u1775\u1774\u17b5\u17b4\u17bb\u17ba\u17c3\u17c2\u17c5\u17c4\u17c7\u17c6\u17f4\u17f3\u17f8\u17f7\u17fe\u17fd\u17fc\u17fb\u184b\u184a\u184f\u184e\u184d\u184c\u1859\u1858\u185b\u185a\u1898\u1897\u189a\u1899' + '\u18bf\u18be\u18bd\u18bc\u18c4\u18c3\u18c6\u18c5\u18c8\u18c7\u18e0\u18df\u18de\u18dd\u1901\u1900\u191f\u191e\u1925\u1924\u1927\u1926\u151e\u17f2\u18c2\u18fb\u1396\u17c8\u17da\u17db\u17ca\u1444\u135c\u135b\u1356\u1355\u1369\u1368\u136b\u136a\u136f\u136e\u136d\u136c\u1379\u1378\u1361\u1360\u1363\u1362\u1367\u1366\u1365\u1364\u1377\u1376\u146f\u146e\u1469\u1468\u145f\u145e\u1475\u1474\u1477\u1476\u147b\u147a\u1479\u1478\u1483\u1482\u155b\u155a\u1561\u1560\u16c5\u16c4\u16bd\u16bc\u16c7\u16c6\u16c9\u16c8\u16cd\u16cc\u16cb\u16ca\u16eb\u16ea\u16e1\u16e0\u16e3\u16e2\u16e7\u16e6\u16e5\u16e4\u16e9\u16e8\u1849\u1848\u1841\u1840\u185d\u185c\u185f\u185e\u1863\u1862\u1861\u1860\u1865\u1864\u18f8\u18f7\u1907\u1906\u1905\u1904\u18ff\u18fe\u15f6\u15f5\u18ae\u18ad\u190e\u190d' + '\u198c\u198e\u199f\u19a7\u199d\u19a5\u19a1\u19a9\u198d\u198f\u19a0\u19a8\u199e\u19a6\u19a2\u19aa\u19ce\u19d0\u19da\u19de\u19d8\u19dc\u0000\u0000\u19cf\u19d1\u19db\u19df\u19d9\u19dd\u0000\u0000\u19ec\u19ee\u19fb\u1a03\u19f9\u1a01\u19fd\u1a05\u19ed\u19ef\u19fc\u1a04\u19fa\u1a02\u19fe\u1a06\u1a21\u1a23\u1a34\u1a3a\u1a32\u1a38\u1a36\u1a3c\u1a22\u1a24\u1a35\u1a3b\u1a33\u1a39\u1a37\u1a3d\u1a5d\u1a5f\u1a69\u1a6d\u1a67\u1a6b\u0000\u0000\u1a5e\u1a60\u1a6a\u1a6e\u1a68\u1a6c\u0000\u0000\u1a94\u1a95\u1aa7\u1aab\u1aa6\u1aa9\u1aa8\u1aad\u0000\u1a96\u0000\u1aac\u0000\u1aaa\u0000\u1aae\u1ac2\u1ac4\u1ad1\u1ad9\u1acf\u1ad7\u1ad3\u1adb\u1ac3\u1ac5\u1ad2\u1ada\u1ad0\u1ad8\u1ad4\u1adc\u1994\u1991\u19d6\u19d3\u19f4\u19f1\u1a29\u1a26\u1a65\u1a62\u1a9c\u1a98\u1aca\u1ac7\u0000\u0000' + '\u19a3\u19ab\u19b2\u19b8\u19b0\u19b6\u19b4\u19ba\u19a4\u19ac\u19b3\u19b9\u19b1\u19b7\u19b5\u19bb\u19ff\u1a07\u1a0e\u1a14\u1a0c\u1a12\u1a10\u1a16\u1a00\u1a08\u1a0f\u1a15\u1a0d\u1a13\u1a11\u1a17\u1ad5\u1add\u1ae4\u1aea\u1ae2\u1ae8\u1ae6\u1aec\u1ad6\u1ade\u1ae5\u1aeb\u1ae3\u1ae9\u1ae7\u1aed\u1996\u1999\u19ae\u199b\u19ad\u0000\u1998\u19af\u1997\u199a\u1995\u1993\u199c\u04d6\u1a1e\u04d7\u04df\u04d1\u1a0a\u19f7\u1a09\u0000\u19f6\u1a0b\u19d7\u19d5\u19f5\u19f3\u19f8\u04d9\u04d8\u04da\u1a2b\u1a30\u1a40\u1a3f\u0000\u0000\u1a2d\u1a41\u1a2c\u1a31\u1a2a\u1a28\u0000\u04dd\u04dc\u04de\u1a9e\u1aa4\u1ab1\u1ab0\u1a7f\u1a80\u1aa0\u1ab2\u1a9f\u1aa5\u1a9d\u1a9a\u1a81\u04d0\u04cf\u04c1\u0000\u0000\u1ae0\u1acd\u1adf\u0000\u1acc\u1ae1\u1a66\u1a64\u1acb\u1ac9\u1ace\u04c5\u04db\u0000' + '\u01df\u01e0\u01e1\u01e2\u01e3\u01e4\u01e5\u01eb\u01e6\u01e7\u01e8\u0000\u0000\u0000\u0000\u0000\u0202\u0203\u0204\u0205\u0207\u020a\u0402\u01f9\u02e9\u02ea\u02eb\u02ec\u02f1\u02f2\u02f3\u02f4\u042e\u042f\u0434\u0435\u0286\u0288\u028a\u0436\u01da\u01db\u0000\u0000\u0000\u0000\u0000\u01ec\u042a\u042c\u043a\u043b\u043c\u043e\u043f\u0440\u0443\u02ed\u02ee\u0444\u026a\u0282\u01ed\u0445\u0447\u0449\u044a\u0437\u069e\u038c\u038d\u0275\u0274\u026b\u0421\u040b\u0438\u0439\u0414\u0237\u0448\u0415\u069a\u020d\u0446\u02d6\u02d7\u043d\u02d8\u02d9\u02da\u02db\u02dc\u02dd\u02de\u01e9\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u10cb\u1544\u0000\u0000\u1211\u1251\u128a\u12bf\u12f5\u132a\u067a\u0698\u0686\u02fe\u0375\u165c' + '\u10cc\u1101\u117a\u11ce\u1212\u1252\u128b\u12c0\u12f6\u132b\u067b\u0699\u0687\u02ff\u0376\u0000\u1339\u1450\u169e\u18dc\u1496\u150a\u15a6\u15de\u162b\u165d\u1722\u17ad\u17ee\u0000\u0000\u0000\u1080\u1081\u1082\u1083\u1084\u1085\u1086\u1087\u177d\u1088\u108a\u108b\u108c\u108d\u108e\u108f\u1090\u1091\u1092\u1093\u1094\u1095\u1096\u1097\u1098\u1099\u109a\u109b\u109c\u109d\u109e\u109f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u01bb\u01bc\u01bd\u01be\u01bf\u01c0\u01c1\u01c2\u0096\u0097\u0098\u01c3\u01c4\u009d\u009e\u009f\u00a0\u01c5\u00a1\u00a2\u00a3\u0099\u01c6\u01c7\u01c8\u01c9\u009a\u009b\u008f\u0090\u0091\u0092\u0057\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u1385\u1386\u13d8\u0549\u05d8\u13e9\u13ea\u149a\u05d9\u054a\u14cc\u1505\u1506\u1507\u1501\u1517\u1541\u1542\u15db\u15d6\u05da\u165a\u1679\u05db\u05dc\u171f\u174a\u175d\u175e\u175f\u05dd\u05de\u17cc\u1803\u1805\u05df\u1918\u05e0\u1ac1\u05e1\u1919\u05e2\u15a2\u1347\u13b4\u13d9\u05e3\u1448\u144d\u14af\u14c7\u1628\u1697\u1e7b\u1e82\u1e86\u1e89\u153b\u05e4\u14b6\u1a71\u19c2\u19c4\u1a73\u0675\u05e5\u05e6\u05e7\u05e8\u140b\u1406\u1449\u153c\u1588\u05e9\u074e\u05ea\u1387\u14c6\u05eb\u110a\u110c\u1104\u1106\u117c\u1108\u117d\u11d1\u1214\u1109\u1254\u110b\u11d2\u1255\u12c2\u1103\u1540\u1567\u1569\u156f\u1892\u189d\u189f\u18a1\u1571\u18d9\u18e2\u18e4\u15da\u13d7\u140a\u1627\u153a\u1566\u1568\u156e\u188e\u189c\u189e\u18a0\u1570\u18d5\u18e1\u18e3\u15d5\u13d3\u1405\u1623' + '\u1049\u104a\u104b\u13ff\u13fe\u127a\u104c\u104d\u104e\u10cd\u05ec\u05ed\u0000\u0000\u0000\u0000\u05ee\u05f4\u05f1\u05f6\u05f8\u05fa\u05fb\u05fc\u05fd\u05fe\u05f0\u05f3\u05ff\u0600\u0601\u0602\u0603\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u060b\u060c\u060d\u060e\u060f\u0610\u05f9\u0611\u0612\u0613\u0614\u0615\u0616\u0617\u0618\u0619\u061a\u061b\u061c\u061d\u061e\u061f\u0620\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u0630\u0636\u0633\u062f\u0631\u0632\u0634\u0635\u0637\u0638\u0639\u063a\u063b\u063c\u063d\u063e\u063f\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e\u065f\u0660\u0661' + '\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u066a\u066b\u066c\u066d\u066e\u066f\u0671\u0672\u0673\u0674\u0697\u069b\u069c\u069d\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a6\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06ca\u06cb\u06cc\u06cd\u06c9\u06ce\u06cf\u06d0\u06d1\u06d3\u06d2\u06d4\u06d5\u06d6\u06d7\u06d8\u06d9\u06db\u06dc\u06dd\u06de\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e5\u06e6\u06e7\u06e8\u06e9\u06ea\u06eb\u06ec\u0688\u06ed\u06ee\u06ef\u06f0\u06f2\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u06fa\u06da\u0682\u068e\u06f1\u06f3\u06fb\u06fd\u06fc\u06fe\u06ff\u0701\u0700\u0702\u0703\u0705\u0707\u0709\u070b\u070c' + '\u0704\u0706\u070d\u070f\u070e\u0710\u0711\u0713\u0712\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071e\u0720\u0721\u0722\u0723\u0724\u0725\u0726\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u0731\u0732\u0733\u0734\u0735\u0736\u0738\u073a\u073b\u0730\u0737\u0739\u073c\u073d\u073e\u073f\u0741\u0743\u0745\u0747\u0748\u0749\u074a\u074b\u074c\u074d\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u076e\u076f\u0770\u0771\u0708\u070a\u071d\u071f\u0772\u0773\u0774\u0775\u0776\u0777\u0740\u0742\u0744\u0746\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789' + '\u078a\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u038e\u038f\u0390\u0391\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u03da\u03dd\u07af\u07b0\u07b1\u07b2\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802\u0803' + '\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883' + '\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u10fb\u1174\u11c8\u120c\u124c\u1285\u12ba\u12f0\u1325\u110d\u1117\u111d\u1123\u1128\u112d\u1132\u1137\u113c\u1141\u117e\u0301\u030c\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0302\u0303\u0304' + '\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030d\u1102\u117b\u11cf\u1213\u1253\u128c\u12c1\u12f7\u132c\u1113\u1119\u111f\u1125\u112a\u112f\u1134\u1139\u113e\u1143\u1181\u0315\u0316\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a\u032b\u032c\u032d\u032e\u1336\u13b5\u13da\u140c\u144e\u14b0\u14d0\u1508\u1543\u158c\u15a4\u15dc\u1629\u165b\u169b\u1720\u174b\u1760\u17ab\u17ec\u1822\u1893\u18b9\u18da\u18f3\u191a\u1333\u13b1\u13d4\u1407\u144a\u14ac\u14cd\u1502\u153d\u1589\u15a0\u15d7\u1624\u1657\u1698\u171c\u1747\u175a\u17a8\u17e9\u181f\u188f\u18b6\u18d6\u18f0\u1915\u10c9\u1118\u111e\u1124\u1129\u112e\u1133\u1138\u113d\u1142\u117f\u10fc\u1175\u11c9\u120d\u124d\u1286\u12bb\u12f1\u1326\u110e\u10ca' + '\u08b6\u08b7\u08b8\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u0901\u0902\u0903\u0904\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936' + '\u0937\u0938\u0939\u093a\u093b\u093c\u093d\u093e\u093f\u0940\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u094e\u094f\u0950\u0951\u0952\u0953\u0954\u0955\u0956\u0957\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0964\u0965\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0970\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097d\u097e\u097f\u0980\u0981\u0982\u0983\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098d\u098e\u098f\u0990\u0991\u0992\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09a9\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b1\u09b2\u09b3\u09b4\u09b5\u09b6\u09b7\u09b8' + '\u09b9\u09ba\u09bb\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4\u09c5\u09c6\u09c7\u09c8\u09c9\u09ca\u09cb\u09cc\u09cd\u09ce\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d7\u09d8\u09d9\u09da\u09db\u09dc\u09dd\u09de\u09df\u09e0\u09e1\u09e2\u09e3\u09e4\u09e5\u09e6\u09e7\u09e8\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09f0\u09f1\u09f2\u09f3\u09f4\u09f5\u09f6\u09f7\u09f8\u09f9\u09fa\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a01\u0a02\u0a03\u0a04\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a\u0a0b\u0a0c\u0a0d\u0a0e\u0a0f\u0a10\u0a11\u0a12\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0fda\u0fdb\u0fdc\u0a1e\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a29\u0a2a\u0a2b\u0a2c\u0a2d' + '\u0a2e\u0a2f\u0a30\u0a31\u0a32\u0a33\u0a34\u0a35\u0a36\u0a37\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a\u0a38\u0a39\u0a3a\u0a3b\u0a3c\u0a3d\u0a3e\u0a3f\u0a40\u0a41\u0a42\u0a43\u0a44\u0a45\u0a46\u0a47\u0a48\u0a49\u0a4a\u0a4b\u0a4c\u0a4d\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a59\u0a5a\u0a5b\u0a5c\u0a5d\u0a5e\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0a70\u0a71\u0a72\u0a73\u0a74\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a81\u0a82\u0a83\u0a84\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8e\u0a8f\u0a90\u0a91\u0a92\u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7' + '\u0aa8\u0aa9\u0aaa\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab1\u0ab2\u0ab3\u0ab4\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0aba\u0abb\u0abc\u0abd\u0abe\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac6\u0ac7\u0ac8\u0acb\u0acc\u0acd\u0ace\u0acf\u0ad0\u0ad1\u0ad2\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae0\u0ae1\u0ae2\u0ae3\u0ae4\u0ae5\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0af0\u0af1\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b01\u0b02\u0b03\u0b04\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0d\u0b0e\u0b0f\u0b10\u0b11\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u10fd\u1176\u11ca\u120e\u124e\u1287\u12bc\u12f2\u1327\u110f' + '\u10fe\u1177\u11cb\u120f\u124f\u1288\u12bd\u12f3\u1328\u1110\u10ff\u1178\u11cc\u1210\u1250\u1289\u12be\u12f4\u1329\u1111\u0b12\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b29\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b31\u0b32\u0b33\u0b34\u0b35\u0b36\u0b37\u0b38\u0b39\u0b3a\u0b3b\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u03ac\u03ad\u0b43\u0b44\u0b45\u0b46\u0b47\u0b48\u0b49\u0b4a\u0b4b\u0b4c\u0b4d\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b56\u0b57\u0b58\u0b59\u0b5a\u0b5b\u0b5c\u0b5d\u0b5e\u0b5f\u0b60\u0b61\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u0b62\u0b63\u0b64\u0b65\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b70\u0b71' + '\u0e55\u0e56\u0e57\u0e58\u0e59\u0e5a\u0e5b\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e\u0e7f\u0e80\u0e81\u0e82\u0e83\u0e84\u0e85\u0e86\u0e87\u0e88\u0e89\u0e8a\u0e8b\u0e8c\u0e8d\u0e8e\u0e8f\u0e90\u0e91\u0e92\u0e93\u0e94\u0e95\u0e96\u0e97\u0e98\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea0\u0ea1\u0ea2\u0ea3\u0ea4\u0ea5\u0ea6\u0ea7\u0ea8\u0ea9\u0eaa\u0eab\u0eac\u0ead\u0eae\u0eaf\u0eb0\u0eb1\u0eb2\u0eb3\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0eba\u0ebb\u0ebc\u0ebd\u0ebe\u0ebf\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0ec5\u0ec6\u0ec7\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0ece\u0ecf\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4' + '\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0eda\u0edb\u0edc\u0edd\u0ede\u0edf\u0ee0\u0ee1\u0ee2\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f00\u0f01\u0f02\u0f03\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f13\u0f14\u0f15\u0f16\u0f17\u0f18\u0f19\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e\u0f1f\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32\u0f33\u0f34\u0f35\u0f36\u0f37\u0f38\u0f39\u0f3a\u0f3b\u0f3c\u0f3d\u0f3e\u0f3f\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46\u0f47\u0f48\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54' + '\u0b72\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b82\u0b83\u0b84\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a\u0b8b\u0b8c\u0b8d\u0b8e\u0b8f\u0b90\u0b91\u0b92\u0b93\u0b94\u0b95\u0b96\u0b97\u0b98\u0b99\u0b9a\u0b9b\u0b9c\u0b9d\u0b9e\u0b9f\u0ba0\u0ba1\u0ba2\u0ba3\u0ba4\u0ba5\u0ba6\u0ba7\u0ba8\u0ba9\u0baa\u0bab\u0bac\u0bad\u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0bba\u0bbb\u0bbc\u0bbd\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2\u0bc3\u0bc4\u0bc5\u0bc6\u0bc7\u0bc8\u0bc9\u0bca\u0bcb\u0bcc\u0bcd\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6\u0bd7\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf0\u0bf1' + '\u0bf2\u0bf3\u0bf4\u0394\u0395\u0396\u0398\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a2\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u0bf5\u0bf6\u0bf7\u0bf8\u0bf9\u0bfa\u0bfb\u0bfc\u0bfd\u0bfe\u0bff\u0c00\u0c01\u0c02\u0c03\u0c04\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0d\u0c0e\u0c0f\u0c10\u0c11\u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26\u0c27\u0c28\u0c29\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0404\u0405\u0406\u0407\u0c34\u0c35\u0c36\u0c37\u0c38\u0c39\u0c3a\u0c3b\u0c3c\u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44\u0c45\u0c46\u0c47\u0c48\u0c49\u0c4a\u0c4b\u0c4c\u0c4d\u0c4e\u0c4f\u0c50\u0c51\u0c52\u0c53\u0392\u0393\u0c54\u0c55' + '\u0c56\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c60\u0c61\u06b9\u0c62\u0c63\u0c64\u0c65\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c82\u0c83\u0c84\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\u0c8c\u0c8d\u0c8e\u0c8f\u0c90\u0c91\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0ca9\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3\u0cb4\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cba\u0cbb\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc5\u0cc6\u0cc7\u0cc8\u023f\u0689\u068a\u0cc9\u0cca\u0ccb\u0ccc\u0ccd\u0cce\u0ccf\u0cd0\u0cd1' + '\u0cd2\u0cd3\u0cd4\u0cd5\u0cd6\u0cd7\u0cd8\u0cd9\u0cda\u0cdb\u0cdc\u0cdd\u0cde\u0cdf\u0ce0\u0ce1\u0ce2\u0ce3\u0ce4\u0ce5\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d02\u0d03\u0d04\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0d\u0d0e\u0d0f\u0d10\u0d11\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d29\u0d2a\u0d2b\u0d2c\u0d2d\u0d2f\u0d2e\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d3a\u0d3b\u0d3c\u0d3d\u0d3e\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44\u0d45\u0d46\u0d47\u0d48\u0d49\u0d4a\u0d4b\u0d4c\u0d4d\u0d4e\u0d4f\u0d50\u0d51' + '\u0d52\u0d53\u0d54\u0d55\u0d56\u0d57\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d60\u0d61\u0d62\u0d63\u0d64\u0d65\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d82\u0d83\u0d84\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d97\u0d98\u0d99\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db2\u0db3\u0db4\u0db5\u0db6\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbc\u0dbd\u0dbe\u0dbf\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0000\u0000\u0dc6\u0dc7\u0dc8\u0dc9\u0dca\u0dcb\u0dcc\u0dcd\u0dce\u0dcf' + '\u0dd0\u0dd1\u0dd2\u0dd3\u0dd4\u0dd5\u0dd6\u0dd7\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde\u0ddf\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0000\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df2\u0df3\u0df4\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e\u0e2f\u0e30\u0e31\u0e32\u0e33\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e3b\u0e3c\u0e3d\u0e3e\u0e3f\u0e40\u0e41\u0e42\u0e43\u0e44\u0e45\u0e46\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e' + '\u1d1b\u1d1d\u1d1f\u1d21\u1d23\u1d25\u1d27\u1d29\u1d2b\u1d2d\u1d2f\u1d31\u1d33\u1d35\u1d37\u1d39\u1d3b\u1d3d\u1d3f\u1d41\u1d43\u1d45\u1d47\u1d49\u1d4b\u1d4d\u1d4f\u1d51\u1d53\u1d55\u1d57\u1d59\u1d5b\u1d5d\u1d5f\u1d61\u1d63\u1d65\u1d67\u1d69\u1d6b\u1d6d\u1d6f\u1d71\u1d73\u1d75\u1d77\u0000\u1d1a\u1d1c\u1d1e\u1d20\u1d22\u1d24\u1d26\u1d28\u1d2a\u1d2c\u1d2e\u1d30\u1d32\u1d34\u1d36\u1d38\u1d3a\u1d3c\u1d3e\u1d40\u1d42\u1d44\u1d46\u1d48\u1d4a\u1d4c\u1d4e\u1d50\u1d52\u1d54\u1d56\u1d58\u1d5a\u1d5c\u1d5e\u1d60\u1d62\u1d64\u1d66\u1d68\u1d6a\u1d6c\u1d6e\u1d70\u1d72\u1d74\u1d76\u0000\u1609\u1608\u160b\u1736\u1794\u1398\u180f\u152c\u152b\u15c9\u15c8\u1938\u1937\u13a6\u164e\u13a2\u13ab\u18ab\u18ce\u18cd\u18ac\u152e\u152d\u1744\u148d\u1791\u170c\u1491\u158e\u1896\u17d9\u1936' + '\u1af6\u1af5\u1af8\u1af7\u1afa\u1af9\u1afc\u1afb\u1afe\u1afd\u1b02\u1b01\u1b04\u1b03\u1b06\u1b05\u1b08\u1b07\u1b0a\u1b09\u1b0c\u1b0b\u1b11\u1b10\u1b13\u1b12\u1b15\u1b14\u1b1b\u1b1a\u1b1d\u1b1c\u1b1f\u1b1e\u1b21\u1b20\u1b23\u1b22\u1b25\u1b24\u1b27\u1b26\u1b29\u1b28\u1b2b\u1b2a\u1b2d\u1b2c\u1b2f\u1b2e\u1b65\u1b64\u1b67\u1b66\u1b00\u1aff\u1b0f\u1b0e\u1b17\u1b16\u1b19\u1b18\u1b31\u1b30\u1b33\u1b32\u1b39\u1b38\u1b3b\u1b3a\u1b3d\u1b3c\u1b45\u1b44\u1b49\u1b48\u1b4b\u1b4a\u1b4d\u1b4c\u1b4f\u1b4e\u1b51\u1b50\u1b53\u1b52\u1b59\u1b58\u1b5d\u1b5c\u1b5f\u1b5e\u1b61\u1b60\u1b69\u1b68\u1b6b\u1b6a\u1b6d\u1b6c\u1b0d\u0e4f\u0e50\u0e51\u0e52\u0e53\u0e54\u1b37\u1b36\u1b57\u1b56\u0058\u0008\u0005\u1b43\u1b42\u0000\u0000\u0000\u0000\u0000\u0295\u027d\u027e\u02e4\u104f\u0296\u02e5' + '\u1d7a\u1d7e\u1d82\u1d86\u1d8a\u1d8e\u1d92\u1d9a\u1d9e\u1da2\u1da6\u1daa\u1daf\u1db7\u1dbb\u1dbf\u1dc3\u1dc7\u1dcb\u1dd3\u1dd7\u1ddb\u1ddf\u1de3\u1de7\u1deb\u1def\u1df3\u1df7\u1dfb\u1dff\u1e07\u1e0b\u1d96\u1db3\u1dcf\u1e03\u1e0f\u0000\u1e15\u0000\u0000\u0000\u0000\u0000\u1e1f\u0000\u0000\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233a\u233b\u233c\u233d\u233e\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234a\u234b\u234c\u234d\u234e\u234f\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2359\u235a\u235b\u235c\u235d\u235e\u235f\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236a\u233f\u2358\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u236b\u04b5\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u237c\u2391\u23a2\u23ab\u23ba\u23e0\u23f1\u23fa\u2410\u2419\u2422\u2453\u2472\u2481\u248a\u24ac\u24b5\u24c5\u24f5\u24a0\u24a1\u24a2\u24a3\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u24f9\u24fa\u24fb\u24fc\u24fd\u24fe\u24ff\u0000\u2500\u2501\u2502\u2503\u2504\u2505\u2506\u0000\u2507\u2508\u2509\u250a\u250b\u250c\u250d\u0000\u250e\u250f\u2510\u2511\u2512\u2513\u2514\u0000\u2515\u2516\u2517\u2518\u2519\u251a\u251b\u0000\u251c\u251d\u251e\u251f\u2520\u2521\u2522\u0000\u2523\u2524\u2525\u2526\u2527\u2528\u2529\u0000\u252a\u252b\u252c\u252d\u252e\u252f\u2530\u0000\u1b7c\u1b80\u1b83\u1b93\u1bae\u1bbb\u1bed\u1c00\u1c14\u1c1b\u1c30\u1c44\u1c4d\u1c55\u1c5f\u1c84\u1c9e\u1ca9\u1cc0\u1cc5\u1d0c\u1c57\u1b6f\u1ba2\u1bea\u1c7c\u1cdf\u1ce8\u1ced\u1cf7\u1cfc\u1d05' + '\u044b\u044c\u03c6\u03c7\u03c8\u03c9\u044d\u044e\u044f\u03ca\u03cb\u0450\u03cc\u03cd\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u020f\u0283\u02e6\u045a\u045b\u03ce\u03cf\u045c\u045d\u03d0\u03d1\u03d2\u03d3\u03d4\u03d5\u03d6\u03d7\u03d8\u03d9\u02df\u02e0\u02e1\u02e2\u0277\u1cc9\u0297\u02a1\u021b\u02a2\u021a\u0238\u0430\u0431\u0432\u0409\u020b\u020c\u0298\u02e3\u0403\u040d\u0210\u021c\u02f5\u020e\u045e\u045f\u0460\u0461\u0462\u023a\u041d\u0433\u021d\u040c\u021e\u021f\u0ac9\u0aca\u0422\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\ua8ab\ua910\ua8b2\ua8b1\ua8b0\ua8bf\ua8db\ua8e6\ua8e9\ua8ea\ua90a\ua90c\ua950\ua951\ua954\ua955\ua952\ua956\ua966\ua96b\ua97d\ua97c\ua986\ua985\ua99f\ua9ad\u0000\ua9b9\ua9bd\ua9cb\ua9e9\ua9ef\ua9f3\ua9f7\ua9f8\uaa15\uaa22\uaa23\ua8a6\uaa2b\uaa2f\uaa3a\uaa52\uaa63\uaa74\uaa75\uaa92\uaaa0\uaaad\uaab3\uaab1\uaab4\uaab5\uaab2\uaaba\uaabb\uaabc\uaac2\uaacd\uaacc\uaacf\uaad8\uaae3\uaae4\uaae5\uaaf8\uab02\uab0c\uab0b\uab12\uab13\uab15\uab29\uab37\uab3b\uab45\uab4b\uab4c\uab4d\uab56\uab69\uab6a\uab6c\uab6d\uab70\uab72\uab73\uab84\uab8a\uab92\uab9e\uaba0\uaba2\uaba3\uaba6\uaba5\uabaa\uabac\uabb1\uabb3\uabbb\uabbf\uabc4\uabc6\uabcc\uabcf\uabd5\uabdc\uabdd\uabdf\uabe0\uabe3\uabe5\uabea\uabeb\uabec\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\ua897\ua8a5\ua8aa\ua8ad\ua8ae\ua8b5\ua8b7\ua8bb\ua8bd\ua8ce\ua8d2\ua8d5\ua8da\ua8dc\ua8de\ua8e5\ua8e7\ua8e8\ua8f1\ua8fa\ua8fc\ua8fe\ua8ff\ua902\ua909\ua90b\ua90f\ua911\ua913\ua914\ua926\ua92a\ua935\ua936\ua937\ua938\ua93a\ua942\ua946\ua948\ua94e\ua94f\ua953\ua957\ua95d\ua95e\ua962\ua963\ua965\ua967\ua968\ua96c\ua96e\ua976\ua977\ua979\ua97a\ua97b\ua97e\ua980\ua984\ua99a\ua99d\ua99e\ua9ab\ua9ac\ua9b1\ua9b3\ua9b5\ua9b6\ua9b8\ua9bb\ua9c7\ua9c9\ua9d0\ua9e3\ua9e4\ua9e8\ua9eb\ua9ee\ua9f0\ua9f1\ua9f2\ua9f4\ua9f5\uaa13\uaa20\uaa25\uaa26\uaa27\uaa28\uaa29\uaa2a\uaa2e\uaa36\uaa39\uaa44\uaa45\uaa47\uaa48\uaa49\uaa4a\uaa51\uaa53\uaa59\uaa5a\uaa5b\uaa5c\uaa62\uaa69\uaa6a\uaa6b\uaa73\uaa86\uaa87\uaa8c\uaa90\uaa91\uaa99\uaa9f\uaaae\uaab0\uaab9\uaabf\uaac1\uaac5\uaac6\uaac7' + '\uaacb\uaace\uaad2\uaad4\uaad6\uaad7\uaad9\uaadb\uaadc\uaadd\uaadf\uaae0\uaaf7\uaafa\uaafe\uab00\uab01\uab0a\uab0f\uab14\uab16\uab2a\uab2b\uab2d\uab2e\uab2f\uab38\uab39\uab3a\uab3d\uab3f\uab46\uab48\uab49\uab55\uab5a\uab5e\uab60\uab63\uab6b\uab6e\uab71\uab7c\uab7f\uab83\uab89\uab8d\uab8e\uab8f\uab90\uab93\uab94\uab97\uab9f\uaba1\uaba4\uabab\uabad\uabae\uabb2\uabb4\uabb5\uabb7\uabb8\uabb9\uabba\uabbc\uabc0\uabc5\uabc8\uabcb\uabcd\uabce\uabd0\uabd2\uabd3\uabd4\uabd6\uabd7\uabd8\uabd9\uabdb\uabde\uabe2\uabe9\uabed\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6\u0fe7\u0fe8\u0000\u0000\u0000\u0000' + '\u01dd\u022b\u029c\u0441\u100d\u105d\u36bd\u10bf\u03db\u03de\u03e0\u03e2\u03e4\u03e7\u03ea\u03ec\u03ee\u03f0\u100e\u1010\u03f2\u03f5\u03f8\u03fa\u03fc\u03fd\u03fe\u03ff\u0211\u02f6\u02f7\u02f8\u1011\u10f1\u116a\u11be\u1202\u1242\u127b\u12b0\u12e6\u131b\u01b5\u01b6\u01b7\u01b8\u01b9\u01ba\u0212\u105f\u1060\u1061\u1062\u1063\u100f\u1012\ua903\ua905\ua906\u105e\u375d\u0442\u1013\u1014\u0000\u364a\u364b\u3655\u3656\u365e\u365f\u3668\u3669\u3671\u3672\u367a\u367d\u3685\u3688\u3694\u3697\u36a0\u36a3\u36a7\u36aa\u36b0\u36b3\u36b9\u36bc\u36c2\u36c5\u36c8\u36cb\u36d0\u36d3\u36d6\u36d9\u36dd\u36e0\u36e5\u36e6\u36e9\u36ec\u36ef\u36f4\u36f7\u36fc\u3701\u3706\u370a\u370e\u3714\u3717\u3719\u3721\u3724\u3726\u372e\u3731\u3733\u373b\u373e\u3740\u374b\u374e\u3750\u3759\u3763' + '\u376b\u376f\u3776\u377c\u377d\u3785\u3786\u378d\u378e\u3794\u3799\u37a0\u37a7\u37ae\u37b3\u37b4\u37b9\u37bd\u37c2\u37c7\u3662\u3679\u369f\u0000\u0000\u00a4\u00a6\u04e0\u04e1\u1064\u1065\u3790\u0213\u3646\u3648\u3651\u3653\u365a\u365c\u3664\u3666\u366d\u366f\u3677\u367c\u3683\u3687\u3692\u3696\u369d\u36a2\u36a5\u36a9\u36ae\u36b2\u36b7\u36bb\u36c0\u36c4\u36c6\u36ca\u36ce\u36d2\u36d4\u36d8\u36db\u36df\u36e1\u36e3\u36e8\u36ea\u36ee\u36f2\u36f6\u36fa\u36ff\u3704\u3708\u370c\u3712\u3716\u3718\u371f\u3723\u3725\u372c\u3730\u3732\u3739\u373d\u373f\u3749\u374d\u374f\u3757\u3761\u3769\u376d\u3774\u3778\u377a\u3781\u3783\u3789\u378b\u3792\u3797\u379e\u37a5\u37ac\u37b0\u37b1\u37b8\u37bc\u37c0\u37c5\u3661\u3676\u369c\u37b6\u37bb\u37bf\u37c4\u0214\u1066\u1068\u1069\u36ac' + '\u0000\u0000\u0000\u0000\u0000\u37c8\u37ca\u37cc\u37ce\u37d0\u37d1\u37d3\u37d5\u37d6\u37d9\u37dd\u37df\u37e1\u37e2\u37e6\u37e7\u37e8\u37e9\u37ea\u37ec\u37ed\u37f1\u37f3\u37f6\u37f8\u37fb\u37fd\u37fe\u3800\u3801\u3802\u3803\u3805\u3808\u3809\u380c\u380f\u37cf\u37db\u37e3\u3810\u37f7\u37d4\u0000\u3431\u3436\u35a3\u3439\u35a7\u35aa\u343e\u3443\u3446\u35af\u35b2\u35b5\u35b8\u35bb\u35be\u3492\u344b\u3450\u3455\u349f\u3458\u345d\u3460\u3466\u346c\u346f\u3475\u347a\u347f\u3484\u3511\u3514\u3517\u351a\u351d\u3520\u3523\u3526\u3529\u352c\u352f\u3532\u3535\u3538\u353b\u353e\u3541\u3544\u3547\u354a\u354d\u350e\u348a\u348c\u35d2\u35d4\u35d9\u35dc\u35e2\u35e7\u35ea\u3496\u35ef\u35f2\u3498\u349a\u349d\u34a2\u34a4\u34a9\u34ac\u34af\u34b1\u34b3\u34b5\u34b7\u34bb\u34c0\u34cb' + '\u34d3\u34d9\u3605\u3607\u34e5\u34e7\u34e9\u355e\u3560\u3564\u356e\u3570\u3573\u357e\u3582\u0000\u1015\u1016\ua899\ua8b9\ua89d\ua928\ua89f\ua8a8\ua8a1\uaa4b\ua8af\ua8a3\ua89a\ua93c\ua92c\ua8be\u37c9\u37eb\u37e0\u37d8\u37f9\u37fa\u37f5\u37f4\u380e\u37f2\u380a\u380d\u37cd\u37dc\u37fc\u37ff\u3806\u3807\u3804\u380b\u37cb\u37d2\u37da\u37de\u37ee\u37ef\u37f0\u37d7\u37e4\u37e5\u3811\u3812\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u3691\u36b6\u36bf\u36f1\u3703\u3711\u371e\u372b\u3738\u3748\u3768\u3791\u3796\u379d\u37a4\u37ab' + '\u032f\u0331\u0333\u0335\u0337\u0339\u033b\u033d\u0341\u0344\u0346\u0348\u034a\u034c\u0330\u0332\u0334\u0336\u0338\u033a\u033c\u033e\u0342\u0345\u0347\u0349\u034b\u034d\u0343\u033f\u0340\u0000\u034e\u0352\u0350\u035e\u0353\u0358\u034f\u0357\u0351\u035a\u0362\u0367\u0366\u0364\u0371\u035f\u0361\u0365\u0363\u036a\u035c\u0368\u036f\u036b\u0359\u0354\u035d\u0360\u0369\u0355\u0370\u035b\u036c\u0356\u036d\u036e\ua91c\ua96d\ua9b2\uaa94\u1112\u1180\u11d3\u1215\u1256\u128d\u12c3\u12f8\u1731\u1184\u1187\u118a\u118d\u1190\u1192\u1194\u1196\u1198\u11d4\u11d6\u11d8\u11d9\u11da\u11db\u3432\u343a\u343f\u3447\u344c\u3451\u3459\u3461\u3467\u3470\u3476\u347b\u3480\u3485\u3434\u343c\u3441\u3449\u344e\u3453\u345b\u3463\u3469\u3472\u3478\u347d\u3482\u3487\u3473\u346a\u3464\u1017' + '\ua898\ua8b8\ua89c\ua927\ua8ba\ua8d8\ua89b\ua8d6\ua8b3\ua904\ua9ca\uaa14\ua9f6\ua9d1\uab64\ua92b\ua9bc\ua9d8\ua9cc\uaa78\ua917\uaa2d\uab30\uaa7e\ua8f3\uaa89\uaa4c\ua943\uab51\ua8cd\ua90d\ua9fc\uab98\ua8c4\ua8dd\ua9e5\ua89e\ua8a7\ua8a0\ua964\ua916\ua900\ua94a\ua947\uaa60\ua8c3\uab32\ua908\ua939\u11dc\u11dd\u11de\u11df\u1216\u1217\u1218\u1219\u121a\u121b\u121c\u121d\u121e\u121f\u1257\u1147\u119b\u11e1\u1221\u1259\u128f\u12c5\u12fa\u132e\u1115\u111b\u1121\u1520\u1484\u1485\u15fb\u364c\u3657\u3660\u366a\u3673\u367b\u3686\u3695\u36a1\u36a8\u36b1\u36ba\u36c3\u36c9\u36d1\u36d7\u36de\u36e7\u36ed\u36f5\u36fd\u3702\u3707\u370b\u370f\u3715\u3722\u372f\u373c\u374c\u375a\u3764\u376c\u3770\u3777\u377e\u3787\u378f\u3795\u379a\u37a1\u37a8\u37af\u37b5\u37ba\u37be\u37c3\ua8c2' + '\u364e\u364f\u3650\u364d\u3658\u3659\u3663\u366c\u366b\u3675\u3674\u3680\u3681\u3682\u367e\u367f\u3689\u368a\u368c\u368b\u368d\u368e\u368f\u3690\u3698\u3699\u369a\u369b\u36a4\u36ad\u36ab\u36b4\u36b5\u36be\u36cc\u36cd\u36da\u36f0\u36f8\u36f9\u36fe\u3710\u371d\u371a\u371b\u371c\u3727\u3728\u3729\u372a\u3735\u3736\u3734\u3737\u3746\u3743\u3744\u3747\u3745\u3741\u3742\u3751\u3752\u3756\u3753\u3754\u3755\u375b\u375c\u375e\u375f\u3760\u3765\u3766\u3767\u3772\u3773\u3771\u377f\u3780\u3788\u379b\u379c\u37a3\u37a2\u37a9\u37aa\u37b7\u10ce\u1148\u119c\u11e2\u1222\u125a\u1290\u12c6\u12fb\u132f\u1116\u111c\u1122\u1127\u112c\u1131\u1136\u113b\u1140\u1145\u1183\u1186\u1189\u118c\u118f\u1522\u1424\u1390\u13be\u16f1\u172b\u1428\u1429\u142a\u156d\ua969\ua9c0\ua93b\ua9be\ua9d9' + '\u1729\u1673\u1a4f\u1637\u15b4\u15b5\u1639\u14e6\u13ec\u15b6\u172c\u1674\u1a50\u1a51\u163a\u15b7\u1523\u15b8\u163b\u14e7\u1804\u1a52\u163d\u1427\u15ba\u14bc\u1678\u1a53\u163e\u13ef\u15bb\u163f\u13f0\u1635\u15bd\u1640\u13f1\u1636\u15be\u1633\u1634\u172a\u15bf\u1642\u14e8\u177a\u177b\u177c\u1730\u167a\u1a54\u1643\u1732\u167b\u1a55\u1644\u15c1\u1645\u1733\u167c\u1a56\u1646\u15c2\u1647\u15c3\u1648\u1384\u13bf\u13ed\u13ee\u13eb\u13f2\u1426\u14e9\u151f\u1521\u156c\u15b9\u15bc\u15c0\u15f7\u15f8\u15f9\u15fc\u1638\u163c\u1641\u172d\u1728\u172e\u172f\u17cd\u17cf\u18c9\u189b\u1388\u1146\u119a\u11e0\u1220\u1258\u128e\u12c4\u12f9\u132d\u1114\u111a\u1120\u1126\u112b\u1130\u1135\u113a\u113f\u1144\u1182\u1185\u1188\u118b\u118e\u1191\u1193\u1195\u1197\u1199\u11d5\u11d7\u14e5' + '\u3cee\u3cf4\u3cf5\u3d03\u3d04\u3d1d\u3d2e\u3d3c\u3d3d\u3d3e\u3d43\u3d44\u3d46\u3d47\u3d48\u3d49\u3d53\u3d54\u3d55\u3d56\u3d57\u3d58\u3d5b\u3d5c\u3d5d\u3d64\u3d65\u3d66\u3d6b\u3d6c\u3d6d\u3d6e\u3d6f\u3d74\u3d75\u3d76\u3d78\u3d7c\u3d7d\u3d82\u3d86\u3d9a\u3da3\u3da4\u3da8\u3dab\u3db7\u3db9\u3def\u3df0\u3df1\u3df2\u3df3\u3df4\u3df5\u3df6\u3e37\u3e38\u3e39\u3e3a\u3e3b\u3e3c\u3e3d\u3e3e\u3e3f\u3e7b\u3e7c\u3e7d\u3e7e\u3e7f\u3e80\u3e81\u3e82\u3ec9\u3eca\u3ecb\u3ecc\u3ecd\u3ece\u3ecf\u3ed0\u3ed1\u3ed2\u3ed3\u3ed4\u3ed5\u3ed6\u3f17\u3f18\u3f19\u3f1a\u3f1b\u3f1c\u3f1d\u3f1e\u3f1f\u3f20\u3f21\u3f22\u3f75\u3fbf\u3f76\u3f77\u3f78\u3f79\u3f7a\u3f7b\u3f7c\u3f7d\u3f7e\u3fc0\u3fc1\u3fc2\u3fc3\u3fc4\u3ff0\u3ff1\u3ff2\u3ff3\u3ff4\u3ff5\u3ff6\u3ff7\u401d\u401e\u401f\u4020\u4021' + '\u4022\u4023\u404b\u404c\u404d\u404e\u404f\u4050\u4051\u4052\u4053\u4054\u4055\u4056\u4057\u4058\u4059\u4075\u4076\u4077\u4078\u4079\u407a\u407b\u407c\u408e\u408f\u4090\u4091\u409f\u40a0\u40a7\u40a8\u40ac\u40ad\u40ae\u40b0\u40b1\u40b2\u40b7\u40b8\u40bc\u40be\u40cb\u40d8\u40de\u40e2\u40e8\u40ed\u40ee\u40f1\u40f2\u40f3\u4102\u4103\u4104\u410a\u4112\u4122\u4128\u4129\u412b\u412c\u412d\u4131\u4132\u413b\u4144\u4146\u4149\u4153\u4154\u415d\u4163\u4164\u4165\u4166\u4167\u416b\u416c\u4178\u4179\u417a\u417e\u4184\u4185\u4187\u4189\u41a3\u41b0\u41c2\u41c3\u41c4\u41d3\u41d4\u41e7\u41e8\u41fe\u41ff\u4200\u4201\u4202\u4203\u4213\u4214\u4215\u4216\u4217\u422e\u422f\u4230\u4231\u4232\u423d\u423e\u423f\u4240\u4241\u4242\u4248\u4249\u424a\u424b\u424c\u4256\u4257\u425d\u425e' + '\u425f\u4260\u4261\u4262\u4263\u4264\u4265\u4266\u4267\u4268\u4274\u427a\u427b\u427c\u427e\u427f\u4280\u4282\u4283\u4290\u4298\u4299\u429a\u42a7\u42a8\u42b4\u42b5\u42b6\u42c3\u42ca\u42cb\u42cc\u42d5\u42da\u42ea\u42f4\u42f5\u42f6\u42f7\u42f8\u4313\u4316\u4318\u431c\u431e\u4321\u8136\u432e\u432f\u433a\u433c\u433d\u433e\u4342\u4348\u434d\u4353\u435b\u4364\u4365\u4371\u437f\u4384\u4385\u4390\u4396\u43aa\u43ab\u43ac\u43b1\u43b2\u43b3\u43b8\u43b9\u43c0\u43c6\u43c7\u43c8\u43d3\u43d6\u43dc\u43dd\u43e0\u43e2\u43e4\u43ea\u43f4\u43f7\u43f8\u43fc\u4404\u4405\u440a\u440c\u4412\u4418\u441b\u441e\u8f66\u443e\u443f\u4440\u445b\u445c\u445d\u44a4\u44a5\u44a6\u44a7\u44a8\u44a9\u44aa\u44ab\u44ac\u44ad\u44ae\u44af\u44b0\u44b1\u44f3\u44f4\u44f5\u44f6\u44f7\u44f8\u44f9\u44fa\u44fb' + '\u453e\u453f\u4540\u4541\u4542\u4543\u4544\u4545\u4546\u4547\u4548\u4549\u454a\u454b\u454c\u4595\u4596\u4597\u4598\u4599\u459a\u459b\u459c\u459d\u459e\u459f\u45a0\u45a1\u45a2\u45a3\u45a4\u45f4\u45f5\u45f6\u45f7\u45f8\u45f9\u45fa\u45fb\u45fc\u45fd\u45fe\u45ff\u4600\u4601\u4602\u4603\u4604\u4605\u4606\u4607\u4608\u4609\u460a\u4652\u4653\u4654\u4655\u4656\u4657\u4658\u4659\u465a\u465b\u465c\u465d\u465e\u465f\u4660\u4661\u4662\u4663\u4664\u4665\u4666\u4667\u4668\u4669\u466a\u466b\u466c\u466d\u46a6\u46a7\u46a8\u46a9\u46aa\u46ab\u46ac\u46ad\u46ae\u46af\u46b0\u46b1\u46b2\u46b3\u46b4\u46b5\u46e8\u46e9\u46ea\u46eb\u46ec\u46ed\u46ee\u46ef\u46f0\u46f1\u46f2\u46f3\u46f4\u46f5\u46f6\u472c\u472d\u472e\u472f\u4730\u4731\u4732\u4733\u4734\u4735\u4754\u4755\u4756\u4757\u4758' + '\u4759\u475a\u475b\u475c\u475d\u4776\u4777\u4778\u4779\u478b\u478c\u478d\u478e\u478f\u4790\u4791\u479e\u479f\u47a0\u47aa\u47ab\u47b7\u47b8\u47b9\u47c2\u47c6\u47c7\u47cd\u47ce\u47d7\u4291\u47df\u47fb\u47fc\u47ff\u4806\u481a\u481e\u482b\u4865\u4866\u4867\u4868\u4869\u486a\u486b\u486c\u486d\u486e\u4899\u489a\u489b\u489c\u489d\u48cb\u48cc\u48cd\u48ce\u48cf\u48d0\u48d1\u48d2\u48d3\u48fa\u48fb\u48fc\u48fd\u48fe\u48ff\u4900\u4901\u493b\u493c\u493d\u493e\u493f\u4940\u4941\u4974\u4975\u4976\u4977\u4978\u4979\u497a\u497b\u497c\u497d\u497e\u49aa\u49ab\u49ac\u49ad\u49ae\u49af\u49b0\u49db\u49dc\u49dd\u49de\u49df\u49e0\u49e1\u49ff\u4a00\u4a01\u4a02\u4a03\u4a04\u4a05\u4a06\u4a07\u4a1c\u4a1d\u4a1e\u4a1f\u4a20\u4a21\u4a22\u4a2f\u4a30\u4a31\u4a32\u4a35\u4a36\u4a37\u4a40\u4a41' + '\u4a45\u4a4a\u4a4c\u4a5e\u4a64\u4a6d\u4a73\u4a74\u4a80\u4a86\u4a8b\u4a90\u4a91\u4a93\u4aa2\u4aab\u4aac\u4aad\u4ab4\u4ab5\u4ac0\u4ac1\u4ac2\u4ac3\u4ac4\u4ac5\u4acf\u4ad0\u4ada\u4adb\u4adc\u4ae4\u4ae8\u4af0\u4af8\u4b0b\u4b0c\u4b0d\u4b0e\u4b0f\u4b10\u4b33\u4b34\u4b35\u4b36\u4b37\u4b38\u4b39\u4b67\u4b68\u4b69\u4b6a\u4b6b\u4b6c\u4b6d\u4b6e\u4b6f\u4b70\u4b71\u4b72\u4b73\u4b74\u4b75\u4b76\u4b77\u4b78\u4ba9\u4baa\u4bab\u4bac\u4bad\u4bae\u4baf\u4bb0\u4bb1\u4bb2\u4bb3\u4be1\u4be2\u4be3\u4be4\u4be5\u4be6\u4be7\u4be8\u4be9\u4bea\u4beb\u4bec\u4bed\u4bee\u4bef\u4bf0\u4bf1\u4bf2\u4bf3\u4b3a\u4bf4\u4bf5\u4bf6\u4bf7\u4c3a\u4c3b\u4c3c\u4c3d\u4c3e\u4c3f\u4c40\u4c41\u4c42\u4c7c\u4c7d\u4c7e\u4c7f\u4c80\u4c81\u4c82\u4c83\u4c84\u4c85\u4c86\u4c87\u4c88\u4c89\u4c8a\u4c8b\u4c8c\u4c8d' + '\u4c8e\u4c8f\u4c90\u4c91\u4c92\u4cb8\u4cb9\u4cba\u4cbb\u4cbc\u4cbd\u4cbe\u4cbf\u4cc0\u4cc1\u4cc2\u4cc3\u4cc4\u4cc5\u4cc6\u4cc7\u4ce6\u4ce7\u4ce8\u4ce9\u4cea\u4ceb\u4cec\u4ced\u4cee\u4cef\u4cf0\u4cf1\u4cf2\u4cf3\u4d11\u4d12\u4d13\u4d14\u4d15\u4d16\u4d2b\u4d2c\u4d2d\u4d2e\u4d2f\u4d44\u4d45\u4d4b\u4d4c\u4d50\u4d51\u4d52\u4d53\u4d5b\u4d5c\u4d5d\u4d61\u4d65\u4d69\u4d6a\u4d75\u4d7b\u4d7c\u4d86\u4d8a\u4d8e\u4d8f\u4d95\u4d98\u4d9c\u4d9f\u4da6\u4da8\u4dad\u4dae\u4db4\u4db5\u4dbf\u4dc0\u4dc1\u4dc2\u4dd2\u4ddc\u4ddd\u4dde\u4ddf\u4df4\u4df5\u4df6\u4df7\u4e03\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e17\u4e18\u4e21\u4e22\u4e23\u4e24\u4e25\u4e31\u4e32\u4e33\u4e34\u4e3a\u4e3b\u4e3c\u4e41\u4e47\u4e48\u4e4b\u4e52\u4e54\u4e5b\u4e62\u4e75\u4e77\u4e7b\u4e7c\u4e83\u4e89\u4e8b\u4e8c' + '\u4e8f\u4e90\u4e91\u4e92\u4e93\u4e94\u4e95\u4e98\u4e99\u4e9d\u4e9e\u4ea6\u4ea8\u4ea9\u4eb0\u4eb8\u4eb9\u4eba\u4ec0\u4ec1\u4ec2\u4ec3\u4ec4\u4ece\u4ed4\u4ed5\u4eda\u4edb\u4ede\u4edf\u4ee0\u4ee5\u4ee6\u4eeb\u4ef1\u4ef3\u4efd\u4efe\u4eff\u4f00\u4f0d\u4f0e\u4f0f\u4f10\u4f11\u4f12\u4f13\u4f14\u4f2d\u4f2e\u4f2f\u4f30\u4f31\u4f32\u4f33\u4ef4\u4f34\u4f5e\u4f5f\u4f60\u4f61\u4f62\u4f63\u4f64\u4f65\u4f66\u4f67\u4f68\u4f8c\u4f8d\u4f8e\u4fad\u4fae\u4faf\u4fb0\u4fb1\u4fb2\u4fb3\u4fb4\u4fb5\u4fb6\u4fb7\u4fb8\u4fb9\u4fba\u4fbb\u4fbc\u4fe8\u4fe9\u4fea\u4feb\u4fec\u4fed\u4fee\u4fef\u4ff0\u4ff1\u4ff2\u4ff3\u4ff4\u4ff5\u4ff6\u5024\u5025\u5026\u5027\u5028\u5029\u502a\u502b\u502c\u502d\u5048\u5049\u504a\u504b\u504c\u504d\u504e\u504f\u5050\u5069\u506a\u506b\u506c\u506d\u506e\u506f' + '\u5070\u5071\u5072\u5073\u5074\u508c\u508d\u508e\u508f\u5090\u5091\u5092\u5093\u5094\u5095\u5096\u5097\u50a9\u50aa\u50ab\u50ac\u50ad\u50ae\u50af\u50b8\u50b9\u50ba\u50bb\u50bc\u50c0\u50c1\u50c2\u50c7\u50c8\u50c9\u50ca\u50d2\u50d6\u50d7\u50df\u50e4\u50ea\u50f0\u50fa\u50fb\u50fd\u50ff\u5104\u5107\u510e\u5118\u5119\u511d\u511e\u511f\u5128\u5129\u512a\u512b\u512c\u512d\u513a\u513b\u513c\u513d\u5149\u514a\u514b\u514c\u514d\u514e\u5159\u515a\u5166\u5167\u5168\u5169\u516a\u516b\u5178\u5179\u517a\u517b\u517c\u517d\u517e\u517f\u5186\u5187\u5188\u5189\u518a\u5196\u5197\u5198\u5199\u51a5\u51a6\u51ab\u51ac\u51ad\u51b1\u51b2\u51b8\u51b9\u51ba\u51bb\u51ca\u51cb\u51cc\u51cd\u51d6\u51d7\u51d8\u51e8\u51e9\u51f4\u51f5\u51f6\u51f7\u51f8\u51f9\u51fa\u51fb\u5203\u5204\u5205\u5206' + '\u5207\u5208\u5209\u5213\u5214\u5215\u5216\u5217\u5225\u5226\u5227\u5228\u5229\u5232\u5233\u5234\u5235\u523e\u524c\u524d\u524e\u524f\u525c\u525d\u525e\u5265\u5266\u5267\u5268\u526d\u5271\u5277\u527c\u528d\u528f\u5290\u5297\u5298\u529b\u52a0\u52a7\u52a8\u52a9\u52aa\u52af\u52b0\u52bb\u52bc\u52bd\u52be\u52c4\u52c5\u52c6\u52c7\u52c8\u52c9\u52ce\u52cf\u52d6\u52d7\u52d8\u52de\u52df\u52e0\u52e5\u52e6\u52e7\u52eb\u52f0\u52f1\u52f6\u52ff\u5307\u530d\u5311\u5312\u5313\u5314\u531c\u531e\u531f\u5323\u5325\u5326\u5329\u532c\u5335\u5336\u5340\u5341\u534c\u534d\u534e\u534f\u5350\u5351\u5352\u5353\u535b\u535c\u536b\u536c\u536d\u536e\u536f\u5370\u5379\u537a\u537b\u537c\u537d\u5383\u5387\u5388\u5389\u5391\u5392\u5395\u5396\u5399\u539b\u539e\u53a4\u53a9\u53bc\u53bd\u53be\u53bf' + '\u53c0\u53eb\u53ec\u53ed\u53ee\u53ef\u53f0\u53f1\u53f2\u53f3\u53f4\u53f5\u542f\u5430\u5431\u5432\u5433\u5434\u5435\u5436\u5437\u5438\u5439\u543a\u543b\u543c\u547b\u547c\u547d\u547e\u547f\u5480\u5481\u5482\u5483\u5484\u5485\u5486\u5487\u5488\u5489\u548a\u548b\u548c\u548d\u548e\u54c2\u54c3\u54c4\u54c5\u54c6\u54c7\u54c8\u54c9\u54ca\u54cb\u54cc\u54cd\u548f\u54ce\u54cf\u54d0\u5511\u5512\u5513\u5514\u5515\u5516\u5517\u5518\u5519\u551a\u551b\u551c\u551d\u551e\u551f\u5520\u5521\u5522\u5523\u5524\u5525\u5526\u5527\u5528\u5529\u552a\u552b\u552c\u5565\u5566\u5567\u5568\u5569\u556a\u556b\u556c\u556d\u556e\u556f\u5570\u5571\u5572\u5573\u5574\u5575\u5576\u55a2\u55a3\u55a4\u55a5\u55a6\u55a7\u55a8\u55a9\u55aa\u55ab\u55ac\u55ad\u55ae\u55af\u55b0\u55b1\u55e7\u55e8\u55e9\u55ea' + '\u55eb\u55ec\u55ed\u55ee\u55ef\u55f0\u55f1\u55f2\u55f3\u5620\u5621\u5622\u5623\u5624\u5625\u5626\u5627\u5628\u5629\u562a\u562b\u562c\u562d\u564e\u564f\u5650\u5666\u5667\u5668\u5669\u566a\u566b\u566c\u5679\u567a\u567f\u5680\u5681\u5682\u5683\u5687\u5688\u5689\u5691\u5692\u5698\u56a5\u56a6\u56ab\u56ac\u56ad\u56af\u56b1\u56b2\u56b3\u56b8\u56bc\u56c3\u56ca\u56cf\u56d0\u56d4\u56de\u56e3\u56e4\u56e8\u56e9\u56fd\u56fe\u56ff\u5716\u5717\u5718\u5752\u5753\u5754\u5755\u5756\u5757\u5758\u5759\u575a\u57a0\u57a1\u57a2\u57a3\u57a4\u57a5\u57a6\u57a7\u57a8\u57a9\u57aa\u57ab\u57ac\u57ad\u57ae\u57ed\u57ee\u57ef\u57f0\u57f1\u57f2\u57f3\u57f4\u57f5\u57f6\u57f7\u57f8\u57f9\u57fa\u57fb\u57fc\u57fd\u583b\u583c\u583d\u583e\u583f\u5840\u5841\u589a\u589b\u589c\u589d\u589e\u589f\u58a0' + '\u58a1\u58a2\u58a3\u58a4\u58a5\u58a6\u58a7\u58a8\u58a9\u58f3\u58f4\u58f5\u58f6\u58f7\u58f8\u58f9\u58fa\u58fb\u58fc\u58fd\u58fe\u58ff\u5946\u5900\u5901\u5947\u5948\u5949\u594a\u594b\u594c\u594d\u594e\u594f\u5950\u5951\u5952\u5953\u5954\u5955\u5956\u5957\u5958\u5959\u595a\u5995\u5996\u5997\u5998\u5999\u599a\u599b\u599c\u599d\u599e\u599f\u59a0\u59a1\u59a2\u59a3\u59a4\u59a5\u59a6\u59a7\u59a8\u59a9\u59aa\u59e3\u59e4\u59e5\u59e6\u59e7\u59e8\u59e9\u59ea\u59eb\u59ec\u59ed\u59ee\u59ef\u59f0\u59f1\u5a15\u5a16\u5a17\u5a18\u5a19\u5a1a\u5a1b\u5a1c\u5a2f\u5a30\u5a31\u5a32\u5a33\u5a34\u5a4b\u5a4c\u5a4d\u5a4e\u5a4f\u5a50\u5a51\u5a52\u5a53\u5a54\u5a55\u5a56\u5a62\u5a63\u5a6d\u5a6e\u5a6f\u5a73\u5a74\u5a75\u5a76\u5a86\u5a87\u5a88\u5a8c\u5a8d\u5a90\u5a92\u5a93\u5a94\u5a96\u5aa1' + '\u5aa2\u5aa6\u5aa7\u5aae\u5aaf\u5ab0\u5ab1\u5ab9\u5aba\u5abb\u5acc\u5acd\u5ace\u5acf\u5ad0\u5ae1\u5ae2\u5ae3\u5ae4\u5ae5\u5ae6\u5ae7\u5ae8\u5ae9\u5aea\u5aeb\u5aec\u5af2\u5af3\u5af4\u5af5\u5af6\u5afa\u5afb\u5afc\u5afd\u5b06\u5b07\u5b08\u5b0d\u5b0e\u5b0f\u5b10\u5b16\u5b17\u5b1b\u5b1c\u5b1f\u5b23\u5b2b\u5b31\u5b32\u5b34\u5b35\u5b38\u5b3b\u5b3d\u5b42\u5b45\u5b46\u5b4f\u5b50\u5b51\u5b52\u5b57\u5b5b\u5b5d\u5b64\u5b68\u5b75\u5b6d\u5b76\u5b77\u5b78\u5b81\u5b82\u5b83\u5b86\u5b8d\u5b8e\u5ba7\u5ba8\u5bb2\u5bb3\u5bb4\u5bb5\u5bb6\u5bb7\u5bd9\u5bda\u5bdb\u5c02\u5c03\u5c04\u5c05\u5c06\u5c07\u5c08\u5c09\u5c0a\u5c0b\u5c23\u5c24\u5c25\u5c26\u5c27\u5c28\u5c29\u5c2a\u5c2b\u5c2c\u5c42\u5c43\u5c44\u5c45\u5c46\u5c47\u5c63\u5c64\u5c65\u5c66\u5c67\u5c68\u5c69\u5c6a\u5c6b\u5c6c\u5c6d' + '\u5c6e\u5c84\u5c85\u5c86\u5c87\u5c88\u5c89\u5c8a\u5c8b\u5c8c\u5c8d\u5c8e\u5c8f\u5c9f\u5ca0\u5ca1\u5ca2\u5cb2\u5cb3\u5cb4\u5cb5\u5cb6\u5cb7\u5ccf\u5cd0\u5cd1\u5cd2\u5cd3\u5cd4\u5cd5\u5cd6\u5cd7\u5ce3\u5ce4\u5ce5\u5cea\u5ceb\u5cec\u5ced\u5cee\u5cef\u5cf6\u5cf7\u5cfe\u5d00\u5d01\u5d05\u5d09\u5d0e\u5d20\u5d21\u5d29\u5d33\u5d34\u5d3b\u5d3c\u5d41\u8061\u5d46\u5d47\u5d49\u5d4a\u5d4b\u5d4e\u5d4f\u5d6e\u5d96\u5d97\u5d98\u5d99\u5d9a\u5dde\u5ddf\u5de0\u5de1\u5de2\u5de3\u5de4\u5de5\u5de6\u5de7\u5e4c\u5e4d\u5e4e\u5e4f\u5e50\u5e51\u5e52\u5ead\u5eae\u5eaf\u5eb0\u5eb1\u5eb2\u5eb3\u5eb4\u5eb5\u5eb6\u5eb7\u5eb8\u5eb9\u5eba\u5ebb\u5ebc\u5f15\u5f16\u5f17\u5f18\u5f19\u5f1a\u5f1b\u5f1c\u5f1d\u5f1e\u5f1f\u5f20\u5f21\u5f22\u5f90\u5f23\u5f91\u5f92\u5f93\u5f94\u5f95\u5f96\u5f97\u5f98' + '\u5f99\u5f9a\u5f9b\u5f9c\u5f9d\u5f9e\u5f9f\u5fa0\u5fa1\u5fa2\u5fa3\u6004\u6005\u6006\u6007\u6008\u6009\u600a\u600b\u600c\u600d\u600e\u600f\u6010\u6011\u6012\u6013\u6014\u6015\u6016\u6017\u6018\u6019\u6a38\u601a\u601b\u6073\u6074\u6075\u6076\u6077\u6078\u6079\u607a\u607b\u607c\u607d\u607e\u607f\u6080\u6081\u6082\u6083\u6084\u6085\u6086\u6087\u6088\u6089\u608a\u608b\u608c\u60dd\u60de\u60df\u60e0\u60e1\u60e2\u60e3\u60e4\u60e5\u60e6\u60e7\u60e8\u60e9\u60ea\u60eb\u60ec\u60ed\u60ee\u613b\u60ef\u60f0\u613c\u613d\u613e\u613f\u6140\u6141\u6142\u6143\u6144\u6145\u6146\u6147\u6148\u60f1\u6149\u614a\u614b\u614c\u614d\u614e\u614f\u6150\u6151\u6180\u6181\u6182\u6183\u6184\u6185\u6186\u6187\u6188\u6189\u618a\u618b\u618c\u61ad\u61ae\u61af\u61b0\u61b1\u61b2\u61b3\u61d2\u61d3' + '\u61d4\u61d5\u61d6\u61d7\u61d8\u61d9\u61da\u61db\u61dc\u61eb\u61ec\u61ed\u61ee\u61ef\u61f0\u61f1\u61f2\u61f3\u61f4\u61f5\u6206\u6207\u61f6\u6211\u6212\u6217\u6218\u6219\u621a\u622d\u622e\u6233\u6234\u6235\u6236\u623a\u623b\u623c\u623d\u623e\u623f\u6247\u6248\u6249\u624a\u624b\u6254\u6255\u6256\u6257\u625f\u6260\u6261\u6262\u6263\u62da\u6264\u626e\u626f\u6270\u6271\u6272\u6273\u6279\u627a\u627b\u6282\u6283\u628b\u628c\u6290\u6291\u6292\u6293\u6297\u6298\u629a\u629b\u629c\u62a1\u62a3\u62a4\u62ab\u62ac\u62ad\u62b2\u62b3\u62b9\u62be\u62c2\u62c9\u62ca\u62cb\u62cc\u62d3\u62d4\u62d5\u62db\u62e4\u62e5\u62ed\u62ee\u62ef\u62f0\u62f1\u62f2\u62f5\u62f6\u62f7\u62f8\u62f9\u62ff\u6300\u6301\u6302\u6308\u6309\u630f\u6310\u6311\u6315\u631a\u631b\u631d\u631f\u6321\u6325\u6326' + '\u632b\u632e\u632f\u6330\u6336\u6339\u633e\u633f\u6340\u6341\u6342\u6353\u6357\u635c\u635d\u635e\u635f\u6360\u6363\u636d\u6372\u6373\u6374\u6375\u6376\u6377\u6378\u6381\u6382\u6383\u6384\u638c\u638d\u638e\u638f\u6390\u6392\u6393\u6394\u6395\u6396\u6397\u63a1\u63a2\u63a3\u63a4\u63a5\u63aa\u63ab\u63ad\u63b0\u63b6\u63c1\u63d0\u63d9\u63da\u63e3\u63f2\u63f3\u63f4\u63f5\u6411\u6412\u6413\u645a\u645b\u645c\u645d\u645e\u645f\u6460\u6461\u6462\u6463\u6464\u64b7\u64b8\u64b9\u64ba\u64bb\u64bc\u64bd\u64be\u64bf\u64c0\u64c1\u6518\u6519\u651a\u651b\u651c\u651d\u651e\u651f\u6520\u6521\u6522\u6523\u6524\u6525\u657b\u657c\u657d\u657e\u657f\u6580\u6581\u6582\u6583\u6584\u6585\u6586\u6587\u6588\u6589\u658a\u65fb\u65fc\u65fd\u65fe\u65ff\u6600\u6601\u6602\u6603\u6604\u6605\u6606' + '\u6607\u6608\u6609\u660a\u660b\u660c\u660d\u660e\u660f\u6610\u6611\u6612\u6613\u6614\u6615\u668d\u668e\u668f\u6690\u6691\u6692\u6693\u6694\u6695\u6696\u6697\u6698\u6699\u669a\u669b\u669c\u669d\u669e\u669f\u66a0\u66a1\u66a2\u66fe\u66ff\u6700\u6701\u6702\u6703\u6704\u6705\u6706\u6707\u6708\u6709\u670a\u670b\u670c\u670d\u670e\u670f\u6710\u6711\u6712\u6713\u6714\u6777\u6778\u6779\u677a\u677b\u677c\u677d\u677e\u677f\u6780\u6781\u6782\u6783\u6784\u67d7\u67d8\u67d9\u67da\u67db\u67dc\u67dd\u67de\u67df\u67e0\u67e1\u67e2\u67e3\u67e4\u67e5\u67e6\u67e7\u67e8\u67e9\u6821\u6822\u6823\u6824\u6825\u6826\u6827\u6828\u6829\u682a\u682b\u682c\u682d\u682e\u682f\u6830\u6831\u6832\u6857\u6858\u6859\u685a\u685b\u685c\u685d\u685e\u685f\u6860\u6861\u6862\u6863\u6864\u6881\u6882\u6883' + '\u6884\u6885\u6886\u6887\u68a3\u68a4\u68a5\u68a6\u68a7\u68a8\u68a9\u68aa\u68be\u68bf\u68c0\u68c1\u68c2\u68c3\u68d2\u68d3\u68d4\u68d5\u68d6\u68d7\u68d8\u68e1\u68e2\u68e7\u68e8\u68e9\u68ef\u68f0\u68f7\u6906\u6907\u6915\u6916\u6917\u6935\u6936\u6937\u6938\u6939\u695f\u6960\u6961\u6962\u6963\u6964\u6965\u6966\u6992\u6993\u6994\u6995\u6996\u6997\u69c1\u69c2\u69c3\u69c4\u69c5\u69c6\u69c7\u69c8\u69c9\u69f4\u69f5\u69f6\u69f7\u69f8\u69f9\u69fa\u69fb\u69fc\u69fd\u69fe\u69ff\u6a39\u6a3a\u6a3b\u6a3c\u6a3d\u6a3e\u6a3f\u6a40\u6a41\u6a42\u6a43\u6a44\u6a45\u6a46\u6a47\u6a48\u6a49\u6a6c\u6a6d\u6a6e\u6a6f\u6a70\u6a71\u6a72\u6a73\u6a74\u6a75\u6a76\u6a77\u6a96\u6a97\u6a98\u6a99\u6a9a\u6a9b\u6a9c\u6a9d\u6ac7\u6ac8\u6ac9\u6aca\u6acb\u6acc\u6acd\u6ace\u6acf\u6ad0\u6ad1\u6aec\u6aed' + '\u6aee\u6aef\u6af0\u6af1\u6afe\u6aff\u6b0b\u6b0c\u6b18\u6b0d\u6b19\u6b1a\u6b1b\u6b1f\u6b25\u6b28\u6b29\u6b2e\u6b36\u6b37\u6b3c\u6b3f\u6b46\u6b48\u6b49\u6b4b\u6b4d\u6b55\u6b57\u6b5e\u6b5f\u6b62\u6b63\u6b64\u6b6f\u6b73\u6b74\u6b76\u6b78\u6b79\u6b80\u6b86\u6b87\u6b90\u6b91\u6b92\u6b93\u6b94\u6b9e\u6b9f\u6ba0\u6ba1\u6ba2\u6ba8\u6ba9\u6baa\u6bab\u6bb2\u6bb3\u6bb4\u6bb5\u6bb6\u6bb7\u6bb8\u6bb9\u6bc5\u6bc6\u6bc7\u6bd8\u6bce\u6bcf\u6bd0\u6bd1\u6bd9\u6bda\u6bdb\u6be0\u6be1\u6be2\u6be3\u6be4\u6be9\u6bea\u6beb\u6bed\u6bee\u6bef\u6bf0\u6bf1\u6bf8\u6bfb\u6bfd\u6c1d\u6c1e\u6c1f\u6c20\u6c21\u6c38\u6c39\u6c3a\u6c3b\u6c3c\u6c3d\u6c3e\u6c3f\u6c40\u6c41\u6c56\u6c57\u6c58\u6c59\u6c5a\u6c5b\u6c6d\u6c6e\u6c6f\u6c70\u6c71\u6c72\u6c73\u6c74\u6c95\u6c96\u6c97\u6c98\u6c99\u6c9a\u6c9b' + '\u6c9c\u6cb4\u6cb5\u6cb6\u6cb7\u6cb8\u6cb9\u6cc9\u6cca\u6ccb\u6ccc\u6ccd\u6cce\u6ccf\u6cdb\u6cdc\u6cdd\u6cde\u6cdf\u6ce0\u6cf1\u6cf2\u6cf3\u6cf4\u6cf5\u6cf6\u6d00\u6d01\u6d02\u6d0a\u6d0b\u6d0c\u6d11\u6d15\u6d16\u6d19\u6d1a\u6d1b\u6d1c\u6d23\u6d32\u6d33\u6d34\u6d35\u6d40\u6d41\u6d42\u6d43\u6d5b\u6d5c\u6d5d\u6d5e\u6d5f\u6d60\u6d61\u6d82\u6d83\u6d84\u6d85\u6d86\u6d87\u6d88\u6d89\u6dac\u6dad\u6dae\u6daf\u6db0\u6db1\u6db2\u6db3\u6db4\u6db5\u6dd5\u6dd6\u6dd7\u6dd8\u6dd9\u6dda\u6ddb\u6ddc\u6e06\u6e07\u6e08\u6e09\u6e0a\u6e0b\u6e0c\u6e0d\u6e0e\u6e0f\u6e10\u6e11\u6e36\u6e37\u6e38\u6e39\u6e3a\u6e3b\u6e3c\u6e3d\u6e3e\u6e3f\u6e59\u6e5a\u6e5b\u6e5c\u6e73\u6e74\u6e75\u6e76\u6e77\u6e78\u6e79\u6e7a\u6e7b\u6e7c\u6e92\u6e93\u6e94\u6e95\u6e96\u6e97\u6e98\u6e99\u6eab\u6eac\u6ead' + '\u6eae\u6eaf\u6eb0\u6ec8\u6ec9\u6eca\u6ecb\u6ed3\u6edc\u6ee0\u6ee1\u6ee4\u6ee5\u6ee7\u6ee8\u6ee9\u6eeb\u6eec\u6eed\u6eee\u6eef\u6ef1\u6ef5\u6ef8\u6efb\u6efc\u6f06\u6f07\u6f0b\u6f0c\u6f0d\u6f0e\u6f0f\u6f13\u6f14\u6f15\u6f16\u6f17\u6f18\u6f1d\u6f1e\u6f1f\u6f20\u6f26\u6f27\u6f28\u6f29\u6f2a\u6f2b\u6f2c\u6f32\u6f33\u6f34\u6f35\u6f36\u6f37\u6f3b\u6f3c\u6f3d\u6f43\u6f44\u6f45\u6f46\u6f4b\u6f4c\u6f4d\u6f4e\u6f4f\u6f50\u6f51\u6f55\u6f56\u6f58\u6f59\u6f5b\u6f5c\u6f5d\u6f61\u6f66\u6f67\u6f68\u6f69\u6f72\u6f74\u6f75\u6f8a\u6f8b\u6f8c\u6fa5\u6fa6\u6fa7\u6fb4\u6fb5\u6fbf\u6fc0\u6fca\u6fcb\u6fd2\u6fd3\u6fd4\u6fd5\u6fda\u6fdd\u6fde\u6fdf\u6fe3\u6fe6\u6fe8\u6fe9\u6fec\u6fee\u6ff1\u6ff7\u7000\u7001\u700b\u700c\u700d\u700e\u701f\u7020\u7021\u7022\u7023\u7024\u703e\u703f\u7040' + '\u7041\u7042\u7043\u7044\u7045\u7046\u7047\u7048\u7049\u704a\u7058\u7059\u705a\u705b\u705c\u705d\u705e\u705f\u7060\u7076\u7077\u7078\u7079\u707a\u707b\u707c\u707d\u707e\u707f\u7080\u7081\u7082\u7083\u7084\u70a0\u70a1\u70a2\u70a3\u70a4\u70a5\u70a6\u70a7\u70bc\u70bd\u70be\u70bf\u70c0\u70c1\u70c2\u70c3\u70c4\u70c5\u70c6\u70c7\u70d9\u70da\u70db\u70dc\u70dd\u70de\u70df\u70e0\u70e1\u70e2\u70e3\u70e4\u70fa\u70fb\u70fc\u70fd\u70fe\u70ff\u7100\u710f\u7110\u7121\u7122\u7123\u7124\u7125\u7126\u712b\u712c\u712d\u7132\u7138\u7139\u713a\u713e\u7141\u7144\u7145\u7148\u7158\u715e\u7165\u716a\u716b\u7174\u7175\u7176\u717c\u7180\u7189\u718d\u718f\u7192\u7193\u7194\u7197\u7198\u719a\u719b\u719e\u71a1\u71a2\u71a7\u71a8\u71aa\u71ab\u71ad\u71b0\u71b2\u71b7\u71be\u71bf\u71c9\u71ca' + '\u71cb\u71d4\u71d5\u71d9\u71dd\u71de\u71e3\u71e8\u71e9\u71ed\u71f0\u71f2\u71f3\u71f4\u71f7\u71f8\u71ff\u7200\u7201\u7202\u7203\u721c\u721d\u721e\u721f\u7220\u7221\u7222\u7223\u7224\u7225\u7246\u7247\u7248\u7249\u724a\u724b\u724c\u724d\u7261\u7262\u7263\u7264\u7265\u7266\u7267\u7268\u727b\u727c\u727d\u727e\u727f\u7280\u7281\u7282\u7283\u7284\u7285\u7286\u7287\u7288\u7289\u728a\u728b\u728c\u72a8\u72a9\u72aa\u72ab\u72ac\u72ad\u72ae\u72af\u72b0\u72cb\u72cc\u72cd\u72ce\u72cf\u72d0\u72d1\u72d2\u72d3\u72d4\u72d5\u72d6\u72d7\u72e5\u72e6\u72e7\u72e8\u72e9\u72ea\u72eb\u72ec\u72ed\u72ee\u72ef\u7300\u7301\u7302\u7303\u7304\u7305\u7306\u7307\u7308\u731d\u731e\u731f\u7320\u7321\u7322\u7323\u7324\u7325\u7331\u7332\u7333\u7334\u7335\u7336\u7337\u7345\u7346\u7347\u7348\u734d' + '\u734e\u734f\u7351\u7352\u7357\u735a\u735c\u735e\u7360\u7364\u7365\u7366\u7367\u7368\u7369\u7373\u7374\u7377\u7378\u737b\u737d\u737e\u7384\u7388\u7394\u7395\u73b2\u73b3\u73b4\u73b5\u73b6\u73dd\u73de\u73df\u73e0\u73e1\u73e2\u73e3\u73e4\u73e5\u73e6\u7402\u7403\u7404\u7405\u7406\u7407\u7408\u7425\u7426\u7427\u7428\u7429\u742a\u742b\u7451\u7452\u7453\u7454\u7455\u7456\u7457\u7458\u7459\u745a\u745b\u745c\u745d\u745e\u745f\u7460\u7461\u7480\u7481\u7482\u7483\u7484\u7485\u7486\u7487\u7488\u74aa\u74ab\u74ac\u74ad\u74ae\u74af\u74b0\u74c4\u74c5\u74c6\u74c7\u74c8\u74c9\u74ca\u74e3\u74cb\u74e4\u74e5\u74e6\u74e7\u74e8\u74e9\u74ea\u74eb\u74fd\u74fe\u74ff\u7500\u7501\u7502\u7503\u750f\u751b\u751c\u751d\u7526\u7527\u7528\u7529\u752b\u752c\u7530\u7532\u7537\u754b\u754c\u754d' + '\u754e\u7563\u7564\u7565\u7572\u7573\u7574\u7581\u7582\u7583\u7584\u7592\u7593\u7594\u7595\u7596\u7597\u7598\u7599\u75ad\u75ae\u75af\u75b0\u75b1\u75bc\u75bd\u75c1\u75c2\u75c3\u75c4\u75c5\u75cb\u75d1\u75d2\u75d5\u75d7\u75d8\u75de\u75ec\u75ed\u75f5\u75f6\u75f7\u75f8\u75f9\u75fa\u7608\u7609\u760a\u760b\u760c\u760d\u760e\u760f\u7629\u762a\u762b\u762c\u763c\u763d\u763e\u763f\u7640\u7641\u7642\u7643\u7644\u7645\u7646\u7647\u7648\u7649\u764a\u764b\u764c\u765d\u765e\u765f\u7660\u7661\u7662\u7663\u7664\u7665\u767d\u767e\u767f\u7680\u7681\u7682\u7683\u7684\u7685\u7686\u7687\u7688\u7698\u7699\u769a\u769b\u769c\u769d\u769e\u769f\u76a0\u76a1\u76a2\u76a3\u76b4\u76b5\u76b6\u76b7\u76b8\u76b9\u76ba\u76bb\u76bc\u76bd\u76be\u76cc\u76cd\u76ce\u76cf\u76d0\u76d1\u76d2\u76e0\u76e1' + '\u76e2\u76e8\u76e9\u76ea\u76eb\u76ec\u76ed\u76ee\u76fb\u76fc\u76fe\u76ff\u7700\u7701\u7704\u7705\u7708\u770d\u7712\u771b\u771c\u771d\u771e\u772a\u772b\u772c\u7734\u7735\u7736\u7737\u7738\u7739\u773a\u7743\u7744\u7745\u7746\u7747\u7752\u7753\u7754\u7755\u775c\u775d\u7765\u7766\u7767\u7771\u7772\u7773\u7774\u7775\u7776\u777d\u777e\u777f\u7780\u7781\u7782\u7783\u7786\u7787\u7789\u778a\u778c\u7790\u7792\u7797\u7798\u77a1\u77a2\u77aa\u77ab\u77ac\u77ad\u77af\u77b6\u77b7\u77bc\u77bd\u77be\u77bf\u77c6\u77ca\u77cc\u77cd\u77dc\u77ee\u77ef\u77f0\u77f1\u77f2\u77f3\u77f4\u781e\u781f\u7820\u7821\u7822\u7823\u7824\u7825\u7826\u7847\u7848\u7849\u784a\u784b\u784c\u784d\u784e\u784f\u7850\u7851\u7852\u7853\u7854\u7879\u787a\u787b\u787c\u787d\u787e\u787f\u7880\u7881\u7882\u7883' + '\u7884\u78b0\u78b1\u78b2\u78b3\u78b4\u78b5\u78b6\u78b7\u78b8\u78b9\u78ba\u78bb\u78bc\u78e3\u78e4\u78e5\u78e6\u78e7\u78e8\u78e9\u78ea\u78eb\u78ec\u78ed\u78ee\u78ef\u78f0\u78f1\u78f2\u78f3\u78f4\u78f5\u78f6\u78f7\u78f8\u78f9\u78fa\u78fb\u78fc\u78fd\u78fe\u791e\u791f\u7920\u7921\u7922\u7923\u7924\u7925\u7926\u7927\u7928\u7929\u792a\u792b\u7952\u7953\u7954\u7955\u7956\u7957\u7958\u7959\u795a\u795b\u795c\u795d\u795e\u795f\u7960\u7961\u797d\u797e\u797f\u7980\u7981\u7982\u7983\u79a0\u7984\u7985\u7986\u7987\u7988\u7989\u798a\u798b\u798c\u798d\u798e\u79a1\u79a2\u79a3\u79a4\u79a5\u79a6\u79a7\u79a8\u798f\u79a9\u79b9\u79bf\u79c0\u79c1\u79c2\u79c3\u79c4\u79c5\u79c6\u79d2\u79d3\u79d4\u79d5\u79dd\u79de\u79df\u9feb\u79e1\u79e9\u79ea\u79ec\u79ff\u7a0f\u7a10\u7a1f\u7a20\u7a21' + '\u7a22\u7a32\u7a33\u7a34\u7a35\u7a36\u7a37\u7a41\u7a42\u7a43\u7a44\u7a45\u7a46\u7a52\u7a53\u7a54\u7a55\u7a56\u7a57\u7a65\u7a66\u7a67\u7a68\u7a69\u7a6a\u7a6b\u7a77\u7a78\u7a85\u7a86\u7a87\u7a88\u7a89\u7a8a\u7a8b\u7a91\u7a92\u7a93\u7a94\u7a95\u7a96\u7a97\u7a9d\u7a9e\u7a9f\u7aa3\u7aa4\u7aa6\u7aaa\u7aac\u7aad\u7aaf\u7ab0\u7aba\u7ac5\u7ac6\u7ac7\u7ac8\u7ae7\u7ae8\u7ae9\u7aea\u7aeb\u7aec\u7aed\u7aee\u7aef\u7af0\u7af1\u7af2\u7af3\u7af4\u7b1a\u7b1b\u7b1c\u7b1d\u7b1e\u7b1f\u7b20\u7b21\u7b22\u7b23\u7b24\u7b25\u7b26\u7b51\u7b52\u7b53\u7b54\u7b55\u7b56\u7b57\u7b58\u7b59\u7b5a\u7b81\u7b82\u7b83\u7b84\u7b85\u7b86\u7b87\u7b88\u7bc1\u7bc2\u7bc3\u7bc4\u7bc5\u7bc6\u7bc7\u7bc8\u7bc9\u7bca\u7bcb\u7bcc\u7c00\u7c01\u7c02\u7c03\u7c04\u7c05\u7c06\u7c07\u7c08\u7c09\u7c0a\u7c0b\u7c0c' + '\u7c0d\u7c0e\u7c0f\u7c10\u7c11\u7c33\u7c34\u7c35\u7c36\u7c37\u7c38\u7c39\u7c5d\u7c5e\u7c5f\u7c60\u7c61\u7c62\u7c63\u7c64\u7c65\u7c66\u7c82\u7c83\u7c84\u7c85\u7c86\u7c87\u7c9c\u7c9d\u7c9e\u7c9f\u7ca0\u7ca1\u7ca2\u7cac\u7cad\u7cae\u7caf\u7cb0\u7cbe\u7cbf\u7cc2\u7cc3\u7cc4\u7cc5\u7cca\u7ccc\u7ccd\u7cce\u7ccf\u7cd4\u7cd5\u7cd8\u7ce8\u7d0e\u7cfb\u7d0f\u7d1f\u7d20\u7d2a\u7d44\u7d5c\u7d5d\u7d6a\u7d7b\u7d80\u7d84\u7d87\u7d88\u7d89\u7d8a\u7d8b\u7d8c\u7d90\u7d93\u7d94\u7d96\u7da3\u7da8\u7dad\u7dae\u7db2\u7db3\u7db4\u7dbd\u7dbe\u7dbf\u7dc1\u7dc5\u7dc6\u7dd0\u7dd1\u7dd2\u7dd3\u7dd9\u7de2\u7de3\u7de9\u7deb\u7df0\u7df1\u7df5\u7e06\u7e07\u7e08\u7e10\u7e11\u7e12\u7e13\u7e17\u7e18\u7e19\u7e22\u7e24\u7e25\u7e26\u7e27\u7e28\u7e2e\u7e31\u7e32\u7e33\u7e37\u7e38\u7e40\u7e4b\u7e4c' + '\u7e59\u7e5a\u7e5b\u7e5c\u7e5d\u7e5e\u7e5f\u7e68\u7e69\u7e6a\u7e6b\u7e6f\u7e70\u7e71\u7e72\u7e7a\u7e7b\u7e7c\u7e7d\u7e7e\u7e8c\u7e8d\u7e99\u7e9a\u7e9b\u7ea0\u7ea1\u7ea5\u7eae\u7eaf\u7eb0\u7eb7\u7eb8\u7eb9\u7ebd\u7ec9\u7ecd\u7ece\u7ed2\u7ed3\u7ed4\u7ed5\u7ed6\u7ed9\u7eda\u7ee0\u7ee1\u7ee2\u7ee5\u7ee8\u7eed\u7efc\u7efd\u7f09\u7f0a\u7f13\u7f18\u7f19\u7f25\u7f26\u7f27\u7f28\u7f29\u7f33\u7f34\u7f37\u7f38\u7f39\u7f3f\u7f40\u7f41\u7f42\u7f49\u7f4f\u7f52\u7f5b\u7f65\u7f66\u7f78\u7f79\u7f9b\u7f9c\u7f9d\u7f9e\u7f9f\u7fa0\u7fa1\u7fa2\u7fa3\u7fa4\u7fa5\u7fa6\u7fa7\u7fa8\u7fd6\u7fd7\u7fd8\u7fd9\u7fda\u7fdb\u7fdc\u7fdd\u8005\u8006\u8007\u8008\u8009\u800a\u800b\u800c\u800d\u8032\u8033\u8034\u8035\u8036\u8037\u8038\u8039\u803a\u803b\u803c\u803d\u803e\u8062\u8063\u8064\u8065' + '\u8066\u8067\u8068\u8069\u806a\u806b\u806c\u806d\u806e\u806f\u8070\u8071\u8072\u8099\u809a\u809b\u809c\u809d\u809e\u809f\u80a0\u80a1\u80a2\u80a3\u80a4\u80a5\u80a6\u80a7\u80bb\u80bc\u80bd\u80be\u80bf\u80c0\u80c1\u80c2\u80c3\u80c4\u80c5\u80c6\u80c7\u80c8\u80c9\u80dc\u80dd\u80de\u80df\u80e0\u80e1\u80e2\u80e3\u80e4\u80e5\u80f9\u80fa\u80fb\u80fc\u80fd\u80fe\u80ff\u8100\u8101\u8118\u8119\u811a\u811b\u8123\u8124\u8125\u8126\u812b\u812c\u812d\u8133\u8134\u8137\u8138\u8139\u813a\u813d\u8144\u8149\u8155\u815b\u8164\u8166\u816b\u816d\u8170\u8178\u8179\u817b\u8182\u818a\u818b\u818c\u818f\u8194\u8195\u8196\u81a4\u81a5\u81b0\u81b1\u81b2\u81b3\u81b8\u81b9\u81ba\u81bb\u81bc\u81c7\u81c8\u81cd\u81ce\u81cf\u81d0\u81d1\u81d2\u81d3\u81d4\u81dc\u81dd\u81de\u81e4\u81e5\u81e6\u81eb' + '\u81ec\u81ed\u81ee\u81ef\u81f0\u81f1\u81f5\u81f6\u81f7\u8201\u820c\u820f\u8211\u8212\u8213\u8214\u8216\u821a\u8225\u8226\u8227\u823d\u823e\u823f\u8240\u8241\u827a\u827b\u827c\u827d\u827e\u827f\u8280\u8281\u8282\u8283\u8284\u8285\u82cc\u82cd\u82ce\u82cf\u82d0\u82d1\u82d2\u82d3\u82d4\u82d5\u832e\u832f\u8330\u8331\u8332\u8333\u8334\u8335\u8336\u8337\u8338\u8339\u833a\u833b\u833c\u833d\u833e\u833f\u8390\u8391\u8392\u8393\u8394\u8395\u8396\u8397\u8398\u8399\u839a\u839b\u839c\u839d\u839e\u839f\u83a0\u83a1\u83a2\u8412\u83a3\u8413\u8414\u8415\u8416\u8417\u8418\u8419\u841a\u841b\u841c\u841d\u841e\u841f\u8420\u8421\u8422\u8423\u8424\u8425\u8426\u8427\u8428\u8429\u8491\u8492\u8493\u8494\u8495\u8496\u8497\u8498\u8499\u849a\u849b\u849c\u849d\u849e\u84f4\u84f5\u84f6\u84f7' + '\u84f8\u84f9\u84fa\u84fb\u84fc\u84fd\u84fe\u84ff\u8500\u8501\u8502\u8503\u8504\u8505\u855d\u855e\u855f\u8560\u8561\u8562\u8563\u8564\u8565\u8566\u8567\u8568\u8569\u856a\u856b\u856c\u856d\u856e\u856f\u8570\u8571\u8572\u8573\u8574\u8575\u8576\u8577\u8578\u8579\u857a\u857b\u85b5\u85b6\u85b7\u85b8\u85b9\u85ba\u85bb\u85bc\u85bd\u85be\u85bf\u85c0\u85c1\u85c2\u85c3\u85c4\u85c5\u85c6\u85c7\u85c8\u85c9\u85ca\u85cb\u85cc\u8606\u8607\u8608\u8609\u860a\u860b\u860c\u860d\u860e\u860f\u8610\u8611\u8612\u863b\u863c\u863d\u863e\u863f\u8640\u8641\u8642\u8643\u8644\u8645\u8646\u8668\u8669\u866a\u866b\u866c\u866d\u866e\u866f\u8690\u8691\u8692\u8693\u8694\u8695\u8696\u8697\u8698\u8699\u869a\u869b\u869c\u86b8\u86b9\u86ba\u86bb\u86bc\u86c5\u86c6\u86c7\u86d3\u86d4\u86d5\u86da\u86df' + '\u86e0\u86e1\u86e3\u86e4\u86e5\u86e6\u86e7\u86ea\u86ee\u86f1\u86f5\u86f6\u86f7\u86f8\u86ff\u8700\u8704\u8705\u8708\u8709\u870a\u870b\u870c\u8711\u8715\u8718\u871a\u871b\u871c\u8720\u8738\u8739\u873a\u8762\u8763\u8764\u878a\u878b\u878c\u878d\u878e\u878f\u8790\u8791\u8792\u87b8\u87b9\u87ba\u87bb\u87bc\u87bd\u87be\u87bf\u87c0\u87e7\u87e8\u87e9\u87ea\u87eb\u87ec\u87ed\u87ee\u87ef\u8822\u8823\u8824\u8825\u8826\u8827\u8828\u8829\u882a\u882b\u882c\u882d\u8864\u8865\u8866\u8867\u8868\u8869\u886a\u886b\u886c\u886d\u882e\u886e\u889a\u889b\u889c\u889d\u889e\u889f\u88a0\u88a1\u88cc\u88cd\u88ce\u88cf\u88d0\u88d1\u88d2\u88d3\u88d4\u88d5\u88d6\u88d7\u88d8\u88d9\u88da\u88db\u88fe\u88ff\u8900\u8901\u8902\u891b\u891c\u891d\u891e\u891f\u8920\u8921\u8922\u892d\u892e\u892f\u8930' + '\u8931\u8932\u893f\u8940\u8941\u894a\u894b\u894c\u894d\u894e\u8956\u8957\u8958\u8960\u8965\u8969\u896c\u896e\u8973\u8974\u8975\u897c\u8986\u8990\u8991\u8992\u8997\u8998\u89a0\u89a1\u89a2\u89be\u89bf\u89c0\u89e9\u89ea\u89eb\u89ec\u89ed\u89ee\u8a09\u8a0a\u8a0b\u8a0c\u8a0d\u8a0e\u8a0f\u8a2c\u8a2d\u8a2e\u8a2f\u8a4e\u8a4f\u8a50\u8a51\u8a52\u8a53\u8a54\u8a55\u8a56\u8a57\u8a58\u8a59\u8a5a\u8a5b\u8a5c\u8a5d\u8a78\u8a79\u8a7a\u8a7b\u8a7c\u8a7d\u8a7e\u8a7f\u8a80\u8a81\u8a82\u8a99\u8a9a\u8a9b\u8a9c\u8a9d\u8a9e\u8ab2\u8ab3\u8ab4\u8ab5\u8ab6\u8ab7\u8ab8\u8ab9\u8aba\u8abb\u8acc\u8acd\u8ace\u8acf\u8ad0\u8ad1\u8adf\u8ae0\u8ae7\u8ae8\u8ae9\u8aea\u8af1\u8af6\u8afa\u8afb\u8aff\u8b00\u8b06\u8b07\u8b0c\u8b0d\u8b0f\u8b12\u8b13\u8b1f\u8b20\u8b27\u8b28\u8b29\u8b2e\u8b2f\u8b30\u8b31' + '\u8b34\u8b35\u8b3b\u8b3c\u8b41\u8b42\u8b48\u8b49\u8b4a\u8b4b\u8b51\u8b52\u8b53\u8b58\u8b59\u8b5f\u8b60\u8b61\u8b62\u8b63\u8b69\u8b6d\u8b6e\u8b87\u8b96\u8b97\u8b98\u8b9d\u8ba2\u8ba3\u8ba4\u8ba5\u8ba6\u8ba9\u8baa\u8bab\u8bac\u8baf\u8bb0\u8bb3\u8bb6\u8bb7\u8bbc\u8bbd\u8bb8\u8bc4\u8bce\u8bcf\u8bd0\u8be3\u8be4\u8bfe\u8bff\u8c00\u8c01\u8c02\u8c03\u8c04\u8c05\u8c06\u8c07\u8c08\u8c09\u8c0a\u8c0b\u8c0c\u8c0d\u8c0e\u8c3c\u8c3d\u8c3e\u8c3f\u8c40\u8c41\u8c42\u8c43\u8c44\u8c45\u8c46\u8c47\u8c48\u8c49\u8c4a\u8c4b\u8c78\u8c79\u8c7a\u8c7b\u8c7c\u8c7d\u8c7e\u8c7f\u8c80\u8ca4\u8ca5\u8ca6\u8ca7\u8ca8\u8ca9\u8caa\u8cab\u8cac\u8cad\u8cae\u8caf\u8cdd\u8cde\u8cdf\u8ce0\u8ce1\u8ce2\u8ce3\u8ce4\u8ce5\u8ce6\u8ce7\u8ce8\u8ce9\u8cea\u8ceb\u8cec\u8d15\u8d16\u8d17\u8d18\u8d19\u8d39\u8d3a' + '\u8d3b\u8d3c\u8d3d\u8d5a\u8d5b\u8d5c\u8d5d\u8d5e\u8d5f\u8d60\u8d61\u8d80\u8d81\u8d82\u8d83\u8d84\u8d85\u8d86\u8d9e\u8d9f\u8da0\u8da1\u8da2\u8da3\u8da4\u8db0\u8db1\u8db2\u8db3\u8db4\u8db5\u8dbe\u8dbf\u8dc0\u8dcc\u8dfd\u8e27\u8e49\u8e67\u8e68\u8e6e\u8e78\u8e7b\u8e7d\u8e81\u8e82\u8e85\u8e87\u8e8b\u8e8d\u8e90\u8e93\u8e97\u8e98\u8e9b\u8e9d\u8e9e\u8e9f\u8ea3\u8ea4\u8ea5\u8ea6\u8ea7\u8ea8\u8eaa\u8eab\u8eac\u8eaf\u8eb1\u8eb7\u8ebe\u8ebf\u8ec4\u8eca\u8ecf\u8ed0\u8ed1\u8ed6\u8ed7\u8edf\u8ee1\u8ee2\u8ee4\u8ee5\u8ee6\u8ee7\u8eee\u8eef\u8ef0\u8ef1\u8ef8\u8ef9\u8f04\u8f07\u8f08\u8f0d\u8f11\u8f15\u8f16\u8f18\u8f19\u8f1a\u8f1b\u8f30\u8f31\u8f45\u8f46\u8f47\u8f48\u8f49\u8f4a\u8f4b\u8f59\u8f5a\u8f5b\u8f67\u8f68\u8f69\u8f7e\u8f7f\u8f80\u8f81\u8f82\u8f83\u8f84\u8f85\u8f86\u8f87' + '\u8f91\u8f92\u8f93\u8f94\u8f95\u8f9e\u8f9f\u8fa7\u8faf\u8fb3\u8fbd\u8fc0\u8fed\u8ffd\u8ffe\u9001\u9002\u9010\u9017\u9019\u901a\u901f\u902a\u902b\u902c\u9035\u9036\u9037\u9038\u9045\u9046\u9047\u9048\u9049\u904a\u904b\u904c\u9056\u9057\u9058\u9059\u905a\u905b\u9062\u9063\u9064\u9065\u9066\u9067\u9068\u9069\u9074\u9075\u9076\u9077\u9078\u9079\u907d\u907e\u907f\u9080\u9082\u9083\u9084\u9085\u9086\u9087\u9088\u9089\u908a\u908b\u9091\u9093\u9094\u9095\u9096\u9099\u909a\u909b\u909c\u909e\u909f\u90a0\u90a4\u90a5\u90aa\u90ab\u90b7\u90b8\u90b9\u90ba\u90bb\u90bc\u90bd\u90be\u90da\u90db\u90dc\u90dd\u90de\u90df\u90e0\u90e1\u90e2\u90e3\u90e4\u90e5\u90e6\u90e7\u90e8\u9106\u9107\u9108\u9109\u910a\u910b\u911f\u9120\u9121\u9122\u9123\u9124\u9125\u9126\u9149\u9127\u914a\u914b' + '\u914c\u914d\u914e\u914f\u9150\u9151\u9152\u9153\u9154\u9155\u9156\u9157\u9158\u916f\u9170\u9171\u9172\u9173\u9174\u9175\u9176\u9177\u9178\u9187\u9188\u9189\u918a\u918b\u918c\u918d\u91a4\u91a5\u91a6\u91a7\u91a8\u91c0\u91c1\u91c2\u91c3\u91c4\u91ce\u91cf\u91d6\u91d7\u91df\u91e6\u91e7\u91ec\u91f2\u91f8\u91ff\u9200\u9201\u9202\u9203\u9206\u9207\u9208\u9209\u9214\u9217\u9219\u921a\u9221\u9223\u9225\u922c\u922d\u922e\u9237\u9246\u9247\u9248\u9249\u924a\u924b\u924c\u924d\u924e\u9269\u926a\u926b\u926c\u927d\u927e\u927f\u9280\u9281\u928b\u928c\u928d\u928e\u92a6\u92a7\u92a8\u92a9\u92ba\u92bb\u92bc\u92bd\u92be\u92c8\u92c9\u92ca\u92cb\u92cc\u92cd\u92d6\u92d7\u92d8\u92d9\u92da\u92db\u92dc\u92e5\u92e6\u92e7\u92e8\u92ef\u92f5\u92f6\u92f7\u92fb\u92fc\u92ff\u9300\u9302\u9303' + '\u930a\u9310\u931c\u9346\u9351\u9356\u9357\u9359\u935b\u935c\u9373\u9374\u938d\u938e\u938f\u9390\u93a4\u93a5\u93bd\u93be\u93bf\u93c0\u93c1\u93c2\u93c3\u93e1\u93e2\u93e3\u93f8\u93f9\u93fa\u93fb\u93fc\u9419\u9428\u9429\u942a\u942b\u9439\u943a\u943b\u943c\u9448\u9449\u944a\u944b\u9456\u9459\u945a\u945e\u9463\u9467\u9472\u9482\u9483\u9484\u9494\u9495\u9496\u94ae\u94af\u94b0\u94b1\u94b2\u94b3\u94b4\u94b5\u94cb\u94cc\u94cd\u94ce\u94cf\u94d0\u94d1\u94e3\u94e4\u94e5\u94e6\u94e7\u94e8\u94fb\u94fc\u94fd\u950b\u950c\u950d\u950e\u950f\u951e\u951f\u9520\u9521\u9522\u9530\u9531\u9532\u9533\u9539\u953c\u954c\u954d\u9551\u9552\u955a\u955b\u9568\u9569\u956a\u956b\u956c\u9574\u9575\u9576\u9577\u9578\u9579\u9586\u9587\u9588\u9589\u958a\u958b\u959a\u959b\u95a9\u95aa\u95ab\u95ac' + '\u95b8\u95b9\u95ba\u95bb\u95bc\u95bd\u95be\u95bf\u95c0\u95ce\u95cf\u95d0\u95d1\u95da\u95e0\u95e1\u95e2\u95e3\u95e4\u95ef\u95f0\u95f1\u95f2\u95f8\u95f9\u95ff\u9609\u961e\u9639\u966a\u966b\u966c\u966d\u96b8\u96b9\u96ba\u96f6\u96f7\u96f8\u96f9\u96fa\u96fb\u96fc\u9743\u9744\u9745\u9746\u9747\u9748\u9749\u974a\u979e\u979f\u97a0\u97a1\u97e3\u97e4\u97e5\u97e6\u97e7\u97e8\u9821\u9822\u9823\u9824\u9825\u9826\u9827\u9828\u9829\u982a\u982b\u9861\u9862\u9863\u9864\u9865\u9866\u9867\u9868\u9869\u986a\u986b\u986c\u98a1\u98a2\u98a3\u98a4\u98a5\u98a6\u98a7\u98a8\u98a9\u98c4\u98c5\u98c6\u98c7\u98c8\u98c9\u98ca\u98cb\u98cc\u98cd\u98e2\u98e3\u98e4\u98e5\u98e6\u98e7\u98e8\u98e9\u98fc\u98fd\u98fe\u98ff\u9900\u9901\u9907\u9908\u9909\u9911\u9923\u994f\u9950\u9951\u9972\u9973\u9974' + '\u999e\u99be\u9a00\u9a17\u9a18\u9a19\u9a25\u9a29\u9a2c\u9a2d\u9a2e\u9a30\u9a3f\u9a40\u9a4e\u9a4f\u9a50\u9a51\u9a5b\u9a5c\u9a5d\u9a68\u9a69\u9a6a\u9a6b\u9a6c\u9a6d\u9a6e\u9a79\u9a7a\u9a7b\u9a7c\u9a8a\u9a8b\u9a8c\u9a8d\u9a8e\u9a8f\u9a90\u9a91\u9a92\u9aa1\u9aa2\u9aa3\u9aa4\u9aa5\u9aa6\u9aa7\u9ab6\u9abb\u9ac1\u9ac2\u9ac3\u9ac5\u9ae1\u9ae6\u9af6\u9b01\u9b02\u9b08\u9b1b\u9b1c\u9b1d\u9b1e\u9b1f\u9b33\u9b34\u9b35\u9b42\u9b43\u9b44\u9b45\u9b46\u9b5a\u9b5b\u9b5c\u9b5d\u9b5e\u9b5f\u9b60\u9b73\u9b74\u9b75\u9b76\u9b77\u9b78\u9b79\u9b90\u9b91\u9b92\u9b9d\u9b9e\u9b9f\u9ba0\u9ba1\u9ba2\u9bab\u9bac\u9bad\u9bae\u9bb3\u9bb4\u9bb5\u9bbd\u9bbe\u9bb6\u9bbf\u9bc7\u9bc8\u9bc9\u9bcc\u9bce\u9bcf\u9bda\u9bdd\u9bde\u9be7\u9be8\u9be9\u9bf1\u9bf2\u9bf3\u9bf4\u9bf8\u9bfa\u9bfb\u9bfe\u9bff' + '\u7a79\u9c01\u9c02\u9c0d\u9c11\u9c12\u9c13\u9c14\u9c16\u9c19\u9c1b\u9c20\u9c29\u9c2a\u9c36\u9c3a\u9c3b\u9c3c\u9c3d\u9c3e\u9c3f\u9c40\u9c41\u9c4b\u9c4c\u9c4d\u9c5b\u9c5c\u9c5d\u9c5e\u9c68\u9c69\u9c6a\u9c6b\u9c6c\u9c6d\u9c6e\u9c74\u9c75\u9c76\u9c77\u9c78\u9c79\u9c82\u9c83\u9c84\u9c85\u9c86\u9c8e\u9c8f\u9c9c\u9c9d\u9c9e\u9ca0\u9ca1\u9ca9\u9cab\u9cb2\u9cb4\u9cb6\u9cc4\u9cc6\u9cc7\u9cc9\u9ccb\u9ccd\u9cd0\u9cd1\u9cd2\u9cd4\u9cd5\u9cd6\u9cd9\u9cda\u9cdb\u9cdc\u9cde\u9cdf\u9ce0\u9ce2\u9cec\u9ced\u9cee\u9cf7\u9cf8\u9cf9\u9cfa\u9cfb\u9cfc\u9d0a\u9d0b\u9d0c\u9d0d\u9d0e\u9d0f\u9d1c\u9d24\u9d25\u9d26\u9d27\u9d28\u9d29\u9d2a\u9d2b\u9d34\u9d35\u9d36\u9d37\u9d38\u9d39\u9d3a\u9d4a\u9d4b\u9d4c\u9d4d\u9d4e\u9d4f\u9d50\u9d58\u9d59\u9d5a\u9d5b\u9d5c\u9d5d\u9d62\u9d63\u9d64\u9d69' + '\u9d6a\u9d6b\u9d6c\u9d6d\u9d6e\u9d73\u9d74\u9d77\u9d78\u9d7c\u9d7d\u9d7e\u9d80\u9d81\u9d82\u9d86\u9d89\u9d8a\u9d8b\u9d8c\u9d91\u9d95\u9d9b\u9d9c\u9d9d\u9da2\u9da3\u9da8\u9daa\u9dab\u9db7\u9db9\u9dbc\u9dbe\u9dbf\u9dc0\u9dc1\u9dc2\u9dc6\u9dc7\u9dc8\u9dcb\u9dce\u9dd0\u9dd1\u9dd4\u9dd7\u9dd8\u9ddc\u9de7\u9de8\u9df4\u9df5\u9df6\u9dfd\u9dfe\u9dff\u9e00\u9e01\u9e02\u9e03\u9e04\u9e05\u9e16\u9e17\u9e18\u9e19\u9e2d\u9e2e\u9e2f\u9e30\u9e31\u9e32\u9e33\u9e34\u9e41\u9e42\u9e43\u9e44\u9e45\u9e46\u9e47\u9e48\u9e49\u9e56\u9e57\u9e58\u9e59\u9e5a\u9e5b\u9e5c\u9e5d\u9e5e\u9e5f\u9e69\u9e6a\u9e6b\u9e6c\u9e6d\u9e6e\u9e6f\u9e70\u9e71\u9e72\u9e78\u9e79\u9e7a\u9e7b\u9e81\u9e82\u9e83\u9e84\u9e85\u9e86\u9e8a\u9e8b\u9e8c\u9e92\u9e93\u9e96\u9ecd\u9ed0\u9ed3\u9ed4\u9ed5\u9ed6\u9edc\u9edd' + '\u9ede\u9edf\u9ee0\u9ee1\u9ee4\u9ee5\u9ee8\u9ee9\u9eea\u9eeb\u9eec\u9eef\u9ef0\u9ef1\u9ef2\u9ef3\u9ef4\u9ef8\u9ef9\u9efa\u9efb\u9efc\u9efd\u9efe\u9f05\u9f06\u9f07\u9f0c\u9f0d\u9f14\u9f16\u9f17\u9f28\u9f29\u9f33\u9f39\u9f3a\u9f3b\u9f46\u9f47\u9f48\u9f49\u9f4a\u9f57\u9f58\u9f59\u9f5a\u9f5b\u9f5c\u9f5d\u9f5e\u9f5f\u9f60\u9f72\u9f73\u9f74\u9f75\u9f76\u9f77\u9f78\u9f84\u9f85\u9f86\u9f87\u9f88\u9f89\u9f8a\u9f9a\u9f9b\u9f9c\u9f9d\u9f9e\u9fac\u9fad\u9fae\u9faf\u9fb0\u9fb1\u9fb2\u9fb3\u9fc1\u9fc2\u9fc3\u9fc4\u9fc5\u9fcc\u9fcd\u9fce\u9fd9\u9fda\u9fdb\u9fdc\u9fdd\u9fe4\u9fe5\u9fe6\u9fe7\u9fec\u9fed\u9fee\u9fef\u9ff0\u9ff4\u9ff5\u9ff6\u9ff9\ua02d\ua031\ua032\ua034\ua035\ua03b\ua042\ua043\ua044\ua04d\ua051\ua059\ua05a\ua06d\ua06e\ua06f\ua070\ua071\ua072\ua073\ua074\ua08e' + '\ua08f\ua090\ua091\ua092\ua093\ua094\ua0a7\ua0a8\ua0a9\ua0aa\ua0ab\ua0ac\ua0ad\ua0ae\ua0bf\ua0c0\ua0c1\ua0c2\ua0c3\ua0d5\ua0d6\ua0d7\ua0d8\ua0d9\ua0da\ua0db\ua0dc\ua0dd\ua0f3\ua0f4\ua0f5\ua0f6\ua0f7\ua0f8\ua0f9\ua10a\ua10b\ua10c\ua10d\ua10e\ua10f\ua110\ua121\ua122\ua123\ua124\ua125\ua126\ua127\ua128\ua137\ua138\ua139\ua13a\ua13b\ua143\ua144\ua145\ua149\ua14a\ua14b\ua14c\ua151\ua157\ua15a\ua15c\ua15e\ua17d\ua184\ua195\ua19f\ua1a1\ua1aa\ua1ab\ua1b2\ua1b3\ua1b4\ua1b5\ua1b6\ua1bc\ua1bd\ua1be\ua1bf\ua1c0\ua1c1\ua1c4\ua1c5\ua1c6\ua1c7\ua1c8\ua1c9\ua1cd\ua1ce\ua1d3\ua1d4\ua1d5\ua1d6\ua1de\ua1e2\ua1e4\ua1e9\ua1eb\ua1ed\ua1f1\ua1f2\ua1f4\ua1f7\ua1f9\ua1fb\ua1fe\ua1ff\ua20a\ua20b\ua20c\ua20d\ua20e\ua20f\ua21a\ua21b\ua224\ua225\ua22d\ua22e\ua22f\ua230\ua236\ua237\ua238' + '\ua239\ua23a\ua23b\ua23c\ua244\ua245\ua246\ua247\ua24c\ua24d\ua24e\ua24f\ua256\ua257\ua25c\ua262\ua263\ua264\ua265\ua267\ua268\ua269\ua26b\ua272\ua273\ua27c\ua27d\ua27e\ua280\ua289\ua28a\ua292\ua297\ua298\ua29a\ua29b\ua2a4\ua2a5\ua2a6\ua2a7\ua2a8\ua2a9\ua2ab\ua2b2\ua2b3\ua2b4\ua2b5\ua2b9\ua2ba\ua2bb\ua2bd\ua2c2\ua2c7\ua2c8\ua2c9\ua2e1\ua2e2\ua2e3\ua2e4\ua2e5\ua2e6\ua2e7\ua2e8\ua309\ua30a\ua30b\ua30c\ua30d\ua30e\ua30f\ua310\ua311\ua312\ua313\ua330\ua331\ua332\ua333\ua334\ua354\ua355\ua356\ua357\ua358\ua359\ua35a\ua35b\ua35c\ua35d\ua381\ua382\ua383\ua384\ua385\ua386\ua387\ua388\ua389\ua38a\ua38b\ua38c\ua38d\ua38e\ua38f\ua390\ua391\ua392\ua3b8\ua3b9\ua3ba\ua3bb\ua3bc\ua3bd\ua3be\ua3bf\ua3c0\ua3c1\ua3d7\ua3d8\ua3d9\ua3da\ua3db\ua3dc\ua3dd\ua3de\ua3df\ua3fa\ua3fb' + '\ua3fc\ua3fd\ua3fe\ua3ff\ua400\ua401\ua402\ua403\ua404\ua418\ua419\ua41a\ua41b\ua41c\ua41d\ua41e\ua42e\ua42f\ua430\ua431\ua432\ua433\ua43a\ua43b\ua43c\ua443\ua446\ua447\ua44c\ua456\ua465\ua466\ua4a4\ua4a5\ua4ae\ua4af\ua4b0\ua4d1\ua4d8\ua4d9\ua4da\ua4db\ua4dc\ua4dd\ua4f5\ua4f6\ua4f7\ua4f8\ua4f9\ua4fa\ua4fb\ua4fc\ua4fd\ua4fe\ua4ff\ua500\ua501\ua521\ua522\ua523\ua524\ua525\ua526\ua527\ua528\ua529\ua52a\ua52b\ua52c\ua52d\ua52e\ua52f\ua530\ua531\ua532\ua550\ua551\ua552\ua553\ua554\ua555\ua556\ua557\ua558\ua559\ua575\ua576\ua577\ua578\ua579\ua57a\ua57b\ua57c\ua5a8\ua5a9\ua5aa\ua5ab\ua5ac\ua5ad\ua5ae\ua5af\ua5b0\ua5cc\ua5cd\ua5ce\ua5cf\ua5d0\ua5d1\ua5d2\ua5d3\ua5d4\ua5d5\ua5d6\ua5fa\ua5fb\ua5fc\ua5fd\ua5fe\ua5ff\ua611\ua612\ua613\ua614\ua615\ua616\ua617\ua633\ua634' + '\ua635\ua636\ua637\ua638\ua639\ua63a\ua63b\ua64b\ua64c\ua64d\ua64e\ua64f\ua658\ua659\ua65c\ua660\ua668\ua669\ua66a\ua676\ua68d\ua68e\ua6a5\ua6ad\ua6b7\ua6c4\ua6cb\ua6cd\ua6d2\ua6d3\ua6d6\ua6dc\ua6df\ua6e0\ua6e1\ua6e6\ua6e7\ua6e8\ua6f1\ua6f9\ua700\ua702\ua705\ua706\ua70d\ua70e\ua70f\ua714\ua715\ua719\ua71a\ua71b\ua71c\ua71f\ua722\ua723\ua724\ua727\ua728\ua729\ua72a\ua72b\ua72d\ua72e\ua72f\ua731\ua732\ua734\ua735\ua733\ua736\ua740\ua744\ua745\ua74b\ua751\ua752\ua753\ua754\ua755\ua756\ua75a\ua75b\ua75c\ua75e\ua75f\ua760\ua761\ua762\ua763\ua765\ua766\ua767\ua76a\ua76b\ua76e\ua76f\ua775\ua77b\ua77c\ua780\ua781\ua782\ua783\ua786\ua787\ua78f\ua790\ua791\ua797\ua798\ua799\ua79f\ua7a0\ua7a2\ua7a3\ua7a4\ua7a6\ua7b2\ua7b5\ua7b7\ua7ba\ua7c7\ua7c8\ua7c9\ua7ce\ua7cf\ua7d0' + '\ua7d2\ua7d7\ua7de\ua7e2\ua7ed\ua7ef\ua7f4\ua7f9\ua7fe\ua7ff\ua806\ua807\ua80a\ua80b\ua80c\ua80d\ua815\ua816\ua81a\ua81b\ua826\ua82a\ua82b\ua837\ua838\ua839\ua840\ua841\ua845\ua846\ua84c\ua84d\ua854\ua855\ua856\ua85b\ua85c\ua85d\ua85e\ua85f\ua862\ua863\ua864\ua866\ua87d\ua882\ua883\ua887\ua888\ua88a\ua88b\ua890\ua893\ua896\u3cfe\u4441\u445e\u4eef\u5d2a\u5d2b\u5d42\u5d44\u624c\u9f34\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f6b\u0f6c\u0f6d\u0f6e\u0f6f\u0f70\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f7f\u0f80\u0f81\u0f82\u0f83\u0f84\u0f85\u0f86\u0f87\u0f88\u0f89\u0f8a\u0f8b\u0f8c\u0f8d\u0f8e\u0f8f\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96\u0f97\u0f98\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2' + '\u3cce\u3ccf\u3cd0\u3cd1\u3cd2\u3cd3\u3cd4\u3cd5\u3cd6\u3cd7\u3cd8\u3cd9\u3cda\u3cdd\u3cde\u3cdf\u3ce0\u3ce1\u3ce2\u3ce3\u3ce5\u3ce6\u3ce7\u3ce4\u3ce8\u3ce9\u3cea\u3ceb\u3cec\u3ced\u3cef\u3cf0\u3cf1\u3cf2\u3cf3\u3cf6\u3cf7\u3cf8\u3cfb\u3cfc\u3cff\u3d00\u3d01\u3d02\u6b51\u3d05\u3d06\u3d07\u3d08\u3d09\u3d0a\u3d0b\u3d0c\u3d0d\u3d0e\u3d0f\u3d10\u3d12\u3d13\u3d14\u3d15\u3cf9\u3d16\u3d17\u3d18\u3d19\u3d1a\u3d1b\u3d1c\u3d1e\u3d1f\u3d20\u3d21\u3d11\u3d22\u3d23\u3d24\u3d26\u3d27\u3d28\u3d29\u3d2a\u3d2b\u3d2c\u3d2d\u3d2f\u3d30\u3d31\u3d32\u3d33\u3d34\u3d35\u3d36\u3d37\u3d39\u3d3a\u3d3b\u51c4\u4ef6\u3d3f\u3d40\u3d41\u3d42\u3d45\u3d4a\u3d4b\u3d4c\u3d4d\u3d4e\u3d4f\u3d50\u3d51\u3d52\u3d59\u3d5a\u3d5e\u3d5f\u3d60\u3d61\u3d62\u3d63\u3d67\u3d68\u3d69\u3d6a\u3d70\u3d71\u3d72' + '\u3d73\u3d77\u3d79\u3d7a\u3d7b\u3d7e\u3d7f\u3d80\u3d81\u3d83\u3d84\u3d85\u3d87\u3d88\u3d89\u3d8a\u3cdb\u3d8b\u3d8c\u3d8d\u3d8e\u3d8f\u3d90\u3d91\u3d92\u3d93\u3d94\u3d95\u3d96\u3d97\u3d98\u3d99\u3d9b\u3d9c\u3d9d\u3d9e\u3d9f\u3da0\u3da1\u3da2\u3da5\u3da6\u3da7\u3da9\u3daa\u3dac\u3dad\u3dae\u3daf\u3db0\u3db1\u3db2\u3db3\u3db4\u3db5\u3db6\u3db8\u3dba\u3dbb\u3dbc\u3dbd\u3dbe\u3dbf\u3dc0\u3dc1\u3dc2\u3dc3\u3dc4\u3dc5\u3dc6\u3dc7\u3dc8\u3dc9\u3dca\u3dcb\u3dcc\u3dcd\u3dce\u3dcf\u3dd0\u3dd1\u3dd2\u3dd3\u3dd4\u3dd5\u3dd6\u3dd7\u3dd8\u3dd9\u3dda\u3ddb\u3ddc\u3ddd\u3dde\u3ddf\u3de0\u3de1\u3de2\u3de3\u3de4\u3de5\u3de6\u3de7\u3de8\u3de9\u3dea\u3deb\u3dec\u3ded\u3dee\u3df7\u3df8\u3df9\u3dfa\u3dfb\u3dfc\u3dfd\u3dfe\u3dff\u3e00\u3e01\u3e02\u3e03\u3e04\u3e05\u3e06\u3e07\u3e08' + '\u3e09\u3e0a\u3e0b\u3e0c\u3e0d\u3e0e\u3e0f\u3e10\u3e11\u3e12\u3e13\u3e14\u3e15\u3e16\u3e17\u3e18\u3e19\u3e1a\u3e1b\u3e1c\u3e1d\u3e1e\u3e1f\u3e20\u3e21\u3e22\u3e23\u3e24\u3e25\u3e26\u3e27\u3e28\u3e29\u3e2a\u3e2b\u3e2c\u3e2d\u3e2e\u3e2f\u3e30\u3e31\u3e32\u3e33\u3e34\u3e35\u3e40\u3e41\u3e42\u3e43\u3e44\u3e45\u3e46\u3e47\u3e48\u3e49\u3e4a\u3e4b\u3e4c\u3e4d\u3e4e\u3e4f\u3e50\u3e51\u3e52\u3e53\u3e54\u3e55\u3e56\u3e57\u3e58\u3e59\u3e5a\u3e5b\u3e5c\u3e5d\u3e5e\u3e5f\u3e60\u3e61\u3e62\u3e63\u3e64\u3e65\u3e66\u3e67\u3e68\u3e69\u3e6a\u3e6b\u3e6c\u3e6d\u3e6e\u3e6f\u3e70\u3e71\u3e72\u3e73\u3e74\u3e75\u3e76\u3e36\u3e77\u3e78\u3e79\u3e7a\u3e83\u3e84\u3e85\u3e86\u3e87\u3e88\u3e89\u3e8a\u3e8b\u3e8c\u3e8d\u3e8e\u3e8f\u3e90\u3e91\u3e92\u3e93\u3e94\u3e95\u3e96\u3e97\u3e98\u3e99' + '\u3e9a\u3e9b\u3e9c\u3e9d\u3e9e\u3e9f\u3ea0\u3ea1\u3ea2\u3ea3\u3ea4\u3ea5\u3ea6\u3ea7\u3ea8\u3ea9\u3eaa\u3eab\u3eac\u3ead\u3eae\u3eaf\u3eb0\u3eb1\u3eb2\u3eb3\u3eb4\u3eb5\u3eb6\u3eb7\u3eb8\u3eb9\u3eba\u3ebb\u3ebc\u3ebd\u3ebe\u3ebf\u3ec0\u3ec1\u3ec2\u3ec3\u3ec4\u3ec5\u3ec6\u3ec7\u3ed7\u3ed8\u3ed9\u3eda\u3edb\u3edc\u3edd\u3ede\u3edf\u3ee0\u3ee1\u3ee2\u3ee3\u3ee4\u3ee5\u3ee6\u3ee7\u3ee8\u3ee9\u3eea\u3eeb\u3eec\u3eed\u3eee\u3eef\u3ef0\u3ef1\u3ef2\u3ef3\u3ef4\u3ef5\u3ef6\u3ef7\u3ef8\u3ef9\u3efa\u3efb\u3efc\u3efd\u3efe\u3eff\u3f00\u3f01\u3f02\u3f03\u3f04\u3f05\u3f06\u3f07\u3f08\u3f09\u3f0a\u3f0b\u3f0c\u3f0d\u3f0e\u3f0f\u3f10\u3f11\u3f12\u3f13\u3f14\u3f15\u3f16\u3f23\u3f24\u3f25\u3f26\u3f27\u3f28\u3f29\u3f2a\u3f2b\u3f2c\u3f2d\u3f2e\u3f2f\u3f30\u3f31\u3f32\u3f33\u3f34' + '\u3f35\u3f36\u3f37\u3f38\u3f39\u3f3a\u3f3b\u3f3c\u3f3d\u3f3e\u3f3f\u3f40\u3f41\u3f42\u3f43\u3f44\u3f45\u3f46\u3f47\u3f48\u3f49\u3f4a\u3f4b\u3f4c\u3f4d\u3f4e\u3f4f\u3f50\u3f51\u3f52\u3f53\u3f54\u3f55\u3f56\u3f57\u3f58\u3f59\u3f5a\u3f5b\u3f5c\u3f5d\u3f5e\u3f5f\u3f60\u3f61\u3f62\u3f63\u3f64\u3f65\u3f66\u3f67\u3f68\u3f69\u3f6a\u3f6b\u3f6c\u3f6d\u3f6e\u3f6f\u3f70\u3f71\u3f72\u3f73\u3f74\u3f7f\u3f80\u3f81\u3f82\u3f83\u3f84\u3f85\u3f86\u3f87\u3f88\u3f89\u3f8a\u3f8b\u3f8c\u3f8d\u3f8e\u3f8f\u3f90\u3f91\u3f92\u3f93\u3f94\u3f95\u3f96\u3f97\u3f98\u3f99\u3f9a\u3f9b\u3f9c\u3f9d\u3f9e\u3f9f\u3fa0\u3fa1\u3fa2\u3fa3\u3fa4\u3fa5\u3fa6\u3fa7\u3fa8\u3fa9\u3faa\u3fab\u3fac\u3fad\u3fae\u3faf\u3fb0\u3fb1\u3fb2\u3fb3\u3fb4\u3fb5\u3fb6\u3fb7\u3fb8\u3fb9\u3fba\u3fbb\u3fbc\u3fbd\u3fbe' + '\u3fc5\u3fc6\u3fc7\u3fc8\u3fc9\u3fca\u3fcb\u3fcc\u3fcd\u3fce\u3fcf\u3fd0\u3fd1\u3fd2\u3fd3\u3fd4\u3fd5\u3fd6\u3fd7\u3fd8\u3fd9\u3fda\u3fdb\u3fdc\u3fdd\u3fde\u3fdf\u3fe0\u3fe1\u3fe2\u3fe3\u3fe4\u3fe5\u3fe6\u3fe7\u3fe8\u3fe9\u3fea\u3feb\u3fec\u3fed\u3fee\u3ff8\u3ff9\u3ffa\u3ffb\u3ffc\u3ffd\u3ffe\u3fff\u4000\u4001\u4002\u4003\u4004\u4005\u4006\u4007\u4008\u4009\u400a\u400b\u400c\u400d\u400e\u400f\u4010\u4011\u4012\u4013\u4014\u4015\u4016\u4017\u4018\u4019\u401a\u401b\u4024\u4025\u4026\u4027\u4028\u4029\u402a\u402b\u402c\u402d\u402e\u402f\u4030\u4031\u4032\u4033\u4034\u4035\u4036\u4037\u4038\u4039\u403a\u403b\u403c\u403d\u403e\u403f\u4040\u4041\u4042\u4043\u4044\u4045\u4046\u4047\u405a\u4048\u4049\u405b\u405c\u404a\u405d\u405e\u405f\u4060\u4061\u4062\u4063\u4064' + '\u4065\u4066\u4067\u4068\u4069\u406a\u406b\u406c\u406d\u406e\u406f\u4070\u4071\u4072\u4073\u4074\u407d\u407e\u407f\u4080\u4081\u4082\u4083\u4084\u4085\u4086\u4087\u4088\u4089\u408a\u408b\u4092\u4093\u4094\u4095\u4096\u4097\u4098\u4099\u409a\u409b\u409c\u409d\u408c\u409e\u40a1\u40a2\u40a3\u40a4\u40a5\u40a6\u40a9\u40aa\u40ab\u40af\u40b3\u40b4\u40b5\u40b6\u40b9\u40ba\u40bb\u40bd\u40bf\u40c0\u40c1\u40c2\u40c3\u40c4\u40c5\u40c6\u40c7\u40c8\u40c9\u40ca\u40cc\u40cd\u40ce\u40cf\u40d0\u40d1\u40d2\u40d3\u40d4\u40d5\u40d6\u40d7\u40d9\u40da\u40db\u40dc\u40dd\u40df\u40e0\u40e1\u40e3\u40e4\u40e5\u40e6\u40e7\u40e9\u40ea\u40eb\u40ec\u40ef\u40f4\u40f5\u40f6\u40f7\u40f8\u40f9\u40fa\u40fb\u40fc\u40fd\u40fe\u40ff\u4101\u4105\u4106\u4107\u4108\u410b\u4109\u410c\u410d\u410e\u410f' + '\u4110\u4111\u4113\u4114\u4115\u4116\u4117\u4118\u4119\u411a\u411b\u411c\u411d\u411e\u411f\u4120\u4121\u4123\u4124\u4125\u4126\u4127\u412e\u412f\u4130\u4133\u4134\u4135\u4136\u4137\u4138\u4139\u413a\u413c\u413d\u413e\u413f\u4140\u4141\u4142\u4143\u4145\u4147\u4148\u414a\u414b\u414c\u414d\u414e\u414f\u4150\u4151\u4152\u4155\u4156\u4157\u4158\u4159\u415a\u415b\u415e\u415f\u4160\u4161\u4162\u4168\u4169\u416a\u416d\u416e\u416f\u4170\u4171\u4172\u4173\u4174\u4175\u4176\u4177\u417b\u417c\u417d\u417f\u4180\u4181\u4182\u4183\u4186\u4188\u418a\u418b\u418c\u418d\u418e\u418f\u4190\u4192\u4193\u4194\u4195\u4196\u4197\u4198\u4199\u419a\u419b\u419c\u419d\u419e\u419f\u41a0\u41a1\u41a2\u41a4\u41a5\u41a6\u41a7\u41a8\u41a9\u41aa\u41ab\u41ac\u41ad\u41ae\u41af\u41b1\u41b2\u41b3' + '\u41b4\u41b5\u41b6\u41b7\u41b8\u41b9\u41ba\u41bb\u41bc\u41bd\u41be\u41bf\u41c0\u41c1\u41c5\u41c6\u41c7\u41c8\u41c9\u41ca\u41cb\u41cc\u41cd\u41ce\u41cf\u41d0\u41d1\u41d2\u41d5\u41d6\u41d7\u41d8\u41d9\u41da\u41db\u41dc\u41dd\u41de\u41df\u41e0\u41e1\u41e2\u41e3\u41e4\u41e5\u41e6\u41e9\u41ea\u41eb\u41ec\u41ed\u41ee\u41ef\u41f0\u41f1\u41f2\u41f3\u41f4\u41f5\u41f6\u41f7\u41f8\u41f9\u41fa\u41fb\u41fc\u41fd\u4204\u4205\u4206\u4207\u4208\u4209\u420a\u420b\u420c\u420d\u420e\u420f\u4210\u4211\u4212\u4218\u4219\u421a\u421b\u421c\u421d\u421e\u421f\u4220\u4221\u4222\u4223\u4224\u4225\u4226\u4227\u4228\u4229\u422a\u422b\u422c\u422d\u4233\u4243\u4234\u4235\u4236\u4237\u4238\u4239\u423a\u423b\u4244\u4245\u4246\u4247\u423c\u424d\u424e\u424f\u4250\u4251\u4252\u4253\u4254\u4255' + '\u4258\u4259\u425a\u425b\u425c\u4269\u426a\u426b\u426c\u426d\u426e\u426f\u4270\u4271\u4272\u4273\u4275\u4276\u4277\u4278\u4279\u427d\u4281\u4284\u4285\u4286\u4287\u4288\u4289\u428a\u428b\u428c\u428d\u428e\u428f\u4292\u4293\u4294\u4295\u4296\u4297\u429b\u429c\u429d\u429e\u429f\u42a0\u42a1\u42a2\u42a3\u42a4\u42a5\u42a6\u42a9\u42aa\u42ab\u42ac\u42ad\u42ae\u42af\u42b0\u42b1\u42b2\u42b3\u42b7\u42b8\u42b9\u42ba\u42bb\u42bc\u42bd\u42be\u42bf\u42c0\u42c1\u42c2\u42c4\u42c5\u42c6\u42c7\u42c8\u42c9\u42cd\u42ce\u42cf\u42d0\u42d1\u5c2d\u42d2\u42d3\u42d4\u42d6\u42d7\u42d8\u42d9\u42db\u42dc\u42dd\u42de\u42df\u42e0\u42e1\u42e2\u42e3\u42e4\u42e5\u42e6\u42e7\u42e8\u42e9\u42eb\u42ec\u42ed\u42ee\u42ef\u42f0\u42f1\u42f2\u42f3\u42f9\u42fa\u42fb\u42fc\u42fd\u42fe\u42ff\u4300\u4301' + '\u4302\u4303\u4304\u4305\u4306\u4307\u4308\u4309\u430a\u430b\u430c\u430d\u430e\u430f\u4310\u4311\u4312\u4314\u4315\u4317\u4319\u431a\u431b\u431d\u431f\u4320\u4322\u4323\u4324\u4325\u4326\u4327\u4328\u4329\u432a\u432b\u432c\u432d\u4330\u4331\u4332\u4333\u4334\u4335\u4337\u4338\u4339\u433b\u433f\u4340\u4341\u4343\u4344\u4345\u4346\u4347\u4349\u434a\u434b\u434c\u434e\u434f\u4350\u4351\u4352\u4354\u4355\u4356\u3cdc\u4357\u4358\u4359\u435a\u435c\u435d\u435f\u435e\u4360\u4361\u4362\u4363\u4366\u4367\u4368\u4369\u436a\u436b\u436d\u436e\u436f\u4370\u4372\u4373\u4374\u4375\u4376\u4377\u4378\u4379\u437a\u437b\u437c\u437d\u437e\u4380\u4381\u4382\u4383\u4386\u4387\u4388\u4389\u438a\u438b\u438c\u438d\u438e\u438f\u4391\u4392\u4393\u4394\u4395\u4397\u4398\u4399\u439a\u439b' + '\u439c\u439d\u439e\u439f\u43a0\u43a1\u43a2\u43a3\u43a4\u43a5\u43a6\u43a7\u43a8\u43a9\u43ad\u43ae\u43af\u43b0\u43b4\u43b5\u43b6\u43b7\u43ba\u43bb\u43bc\u43bd\u43be\u43bf\u43c1\u43c2\u43c3\u43c4\u43c9\u43ca\u43cb\u43cc\u43ce\u43cf\u43d0\u43d1\u43d2\u43cd\u43d4\u43d5\u43d7\u43d8\u43d9\u43da\u43db\u43de\u43df\u43e1\u43e3\u43e5\u43e6\u43e7\u43e8\u43e9\u43eb\u43ec\u43ed\u43ee\u43ef\u43f0\u43f1\u43f2\u43f3\u43f5\u43f6\u43f9\u43fa\u43fb\u43fd\u43fe\u43ff\u4400\u4401\u4402\u4403\u4406\u4407\u4408\u4409\u440b\u440d\u440e\u440f\u4410\u4411\u4413\u4414\u4415\u4416\u4417\u4419\u441a\u441c\u441f\u4420\u4422\u4423\u4424\u4425\u4426\u4427\u4428\u4429\u442a\u442b\u442c\u442d\u442e\u442f\u4430\u4431\u4432\u4433\u4434\u4435\u4436\u4437\u4438\u4439\u443a\u443b\u443c\u443d\u4442' + '\u4443\u4444\u4445\u4446\u4447\u4448\u4449\u444a\u444b\u444c\u444d\u444e\u444f\u4450\u4451\u4452\u4453\u4454\u4455\u4456\u4457\u4458\u4459\u445a\u445f\u4460\u4461\u4462\u4463\u4464\u4465\u4466\u4467\u4468\u4469\u446a\u446b\u446c\u446d\u446e\u446f\u4470\u4471\u4472\u4473\u4474\u4475\u4476\u4477\u4478\u4479\u447a\u447b\u447c\u447d\u447e\u447f\u4480\u4481\u4482\u4483\u4484\u4485\u4486\u4487\u4488\u4489\u448a\u448b\u448c\u448d\u448e\u448f\u4490\u4491\u4492\u4493\u4494\u4495\u4496\u4497\u4498\u4499\u449a\u449b\u449c\u449d\u449e\u449f\u44a0\u44a1\u44a2\u44a3\u44b2\u44b3\u44b4\u44b5\u44b6\u44b7\u44b8\u44b9\u44ba\u44bb\u44bc\u44bd\u44be\u44bf\u44c0\u44c1\u44c2\u44c3\u44c4\u44c5\u44c6\u44c7\u44c8\u44c9\u44ca\u44cb\u44cc\u44cd\u44ce\u44cf\u44d0\u44d1\u44d2\u44d3\u44d4' + '\u44d5\u44d6\u44d7\u44d8\u44d9\u44da\u44db\u44dc\u44dd\u44de\u44df\u44e0\u44e1\u44e2\u44e3\u44e4\u44e5\u44e6\u44e7\u44e8\u44e9\u44ea\u44eb\u44ec\u44ed\u44ee\u44ef\u44f0\u44f1\u44f2\u44fc\u44fd\u44fe\u44ff\u4500\u4501\u4502\u4503\u4504\u4505\u4506\u4507\u4508\u4509\u450a\u450b\u450c\u450d\u450e\u450f\u4510\u4511\u4512\u4513\u4514\u4515\u4516\u4517\u4518\u4519\u451a\u451b\u451c\u451d\u451e\u451f\u4520\u4521\u4522\u4523\u4524\u4525\u4526\u4527\u4528\u4529\u452a\u452b\u452c\u452d\u452e\u452f\u4530\u4531\u4532\u4533\u4534\u4535\u4536\u4537\u4538\u4539\u453a\u453b\u453c\u453d\u454d\u454e\u454f\u4550\u4551\u4552\u4553\u4554\u4555\u4556\u4557\u4558\u4559\u455a\u455b\u455c\u455d\u455e\u455f\u4560\u4561\u4562\u4563\u4564\u4565\u4566\u4567\u4568\u4569\u456a\u456b\u456c' + '\u456d\u456e\u456f\u4570\u4571\u4572\u4573\u4574\u4575\u4576\u4577\u4578\u4579\u457a\u457b\u457c\u457d\u457e\u457f\u4580\u4581\u4582\u4583\u4584\u4585\u4586\u4587\u4588\u4589\u458a\u458b\u458c\u458d\u458e\u458f\u4590\u4591\u4592\u4593\u4594\u45a5\u45a6\u45a7\u45a8\u45a9\u45aa\u45ab\u45ac\u45ad\u45ae\u45af\u45b0\u45b1\u45b2\u45b3\u45b4\u45b5\u45b6\u45b7\u45b8\u45b9\u45ba\u45bb\u45bc\u45bd\u45be\u45bf\u45c0\u45c1\u45c2\u45c3\u45c4\u45c5\u45c6\u45c7\u45c8\u45c9\u45ca\u45cb\u45cc\u45cd\u45ce\u45cf\u45d0\u45d1\u45d2\u45d3\u45d4\u45d5\u460b\u45d6\u45d7\u45d8\u45d9\u45da\u5abc\u45db\u45dc\u45dd\u45de\u45df\u45e0\u45e1\u45e2\u45e3\u45e4\u45e5\u460c\u45e6\u45e7\u45e8\u45e9\u45ea\u45eb\u45ec\u45ed\u45ee\u45ef\u45f0\u45f1\u45f2\u45f3\u460d\u460e\u460f\u4610\u4611\u4612' + '\u4613\u4614\u4615\u4616\u4617\u4618\u4619\u461a\u461b\u461c\u461d\u461e\u461f\u466e\u4620\u4621\u4622\u4623\u4624\u4625\u4626\u4627\u4628\u4629\u462a\u462b\u462c\u462d\u462e\u462f\u4630\u4631\u4632\u4633\u4634\u4635\u4636\u4637\u4638\u4639\u463a\u463b\u463c\u463d\u463e\u463f\u4640\u4641\u4642\u4643\u4644\u4645\u4646\u4647\u4648\u4649\u464a\u464b\u464c\u464d\u464e\u464f\u4650\u466f\u4670\u4671\u4672\u4673\u4674\u4675\u4676\u4677\u4678\u4679\u467a\u467b\u467c\u467d\u467e\u467f\u4680\u4681\u4682\u4683\u4684\u4685\u4686\u4687\u4688\u4689\u468a\u468b\u468c\u468d\u468e\u468f\u4690\u4691\u4692\u4693\u4694\u4695\u4696\u4697\u4698\u4699\u469a\u469b\u469c\u469d\u469e\u469f\u46a0\u46a1\u46a2\u46a3\u46a4\u46a5\u46b6\u46b7\u46b8\u46b9\u46ba\u46bb\u46bc\u46bd\u46be\u46bf' + '\u46c0\u46c1\u46c2\u46c3\u46c4\u46c5\u46c6\u46c7\u46c8\u46c9\u46ca\u46cb\u46cc\u46cd\u46ce\u46cf\u46d0\u46d1\u46d2\u46d3\u46d4\u46d5\u46d6\u46d7\u46d8\u46d9\u46da\u46db\u46dc\u46dd\u46de\u46f7\u46f8\u46df\u46e0\u46e1\u46e2\u46e3\u46e4\u46e5\u46f9\u46fa\u46fb\u46fc\u46fd\u46fe\u46ff\u4700\u4701\u4702\u4703\u4704\u4705\u4706\u4707\u4708\u4709\u470a\u470b\u470c\u470d\u470e\u470f\u4710\u4711\u4712\u4713\u4714\u4715\u4651\u4716\u4717\u4718\u4719\u471a\u471b\u471c\u471d\u471e\u471f\u4720\u46e6\u4721\u46e7\u4722\u6b40\u4723\u4724\u4725\u4726\u4727\u4728\u4729\u472a\u4736\u4737\u4738\u4739\u473a\u473b\u473c\u473d\u473e\u473f\u4740\u4741\u4742\u4743\u4744\u4745\u4746\u4747\u4748\u4749\u474a\u474b\u472b\u474c\u474d\u474e\u474f\u4750\u4751\u4752\u4753\u475e\u475f\u4760' + '\u4761\u4762\u4763\u4764\u4765\u4766\u4767\u4768\u4769\u476a\u476b\u476c\u476d\u476e\u476f\u4770\u4771\u4772\u4773\u4774\u477a\u477b\u477c\u477d\u477e\u477f\u4780\u4781\u4782\u4783\u4784\u4785\u4786\u4787\u4788\u4789\u478a\u4792\u4793\u4794\u4795\u4796\u4797\u4798\u4799\u479a\u479b\u479c\u479d\u47a1\u47a2\u47a3\u47a4\u47a5\u47a6\u47a7\u47a8\u47a9\u4775\u47ac\u47ad\u47ae\u47af\u47b0\u47b1\u47b2\u47b3\u47b4\u47b5\u47ba\u47bb\u47bc\u47bd\u47be\u47bf\u47c0\u47c3\u47b6\u47c1\u47c4\u47c5\u47c8\u47c9\u47ca\u47cb\u47cc\u47cf\u47d0\u47d1\u47d2\u47d3\u47d4\u47d5\u47d8\u47d9\u47da\u47db\u47dc\u47dd\u47de\u47e0\u47e1\u47e2\u47e3\u47e4\u47e5\u47e6\u47e7\u47e8\u47e9\u47ea\u47eb\u47ec\u47ed\u47ee\u47ef\u47f0\u47f1\u47f2\u47f3\u47f4\u47f5\u47f6\u47f7\u47f8\u47f9\u47fa\u47fd' + '\u47fe\u4800\u4801\u4802\u4803\u4804\u4805\u4807\u4808\u4809\u480a\u480b\u480d\u480e\u480f\u480c\u4810\u4811\u4812\u4813\u4814\u4815\u4816\u4817\u4818\u4819\u481b\u481c\u481d\u481f\u4820\u4821\u4822\u4823\u4825\u4826\u4827\u4828\u4829\u482a\u482c\u482d\u482e\u482f\u4830\u4831\u4832\u4833\u4834\u4835\u4836\u4837\u4838\u4839\u483a\u483b\u483c\u483d\u483e\u483f\u4840\u4841\u4842\u4843\u4844\u4845\u4846\u4847\u4848\u4849\u484a\u484b\u484c\u484d\u484e\u484f\u4850\u4851\u4852\u4853\u4854\u4855\u4856\u4857\u4858\u4859\u485a\u485b\u485c\u485d\u485e\u485f\u4860\u4861\u4862\u4863\u4864\u486f\u4870\u4871\u4872\u4873\u4874\u4875\u4876\u4877\u4878\u4879\u487a\u487b\u487c\u487d\u487e\u487f\u4880\u4881\u4882\u4883\u4884\u4885\u4886\u4887\u4888\u4889\u488a\u488b\u488c\u488d' + '\u488e\u488f\u4890\u4891\u4892\u4893\u4894\u4895\u4896\u4897\u4898\u489e\u489f\u48a0\u48a1\u48a2\u48a3\u48a4\u48a5\u48a6\u48a7\u48a8\u48a9\u48aa\u48ab\u48ac\u48ad\u48ae\u48af\u48b0\u48b1\u48b2\u48b3\u48b4\u48b5\u48b6\u48b7\u48b8\u48b9\u48ba\u48bb\u48bc\u48bd\u48be\u48bf\u48c0\u48c1\u48c2\u48c3\u48c4\u48c5\u48c6\u48c7\u48c8\u48d4\u48d5\u48d6\u48d7\u48d8\u48d9\u48da\u48db\u48dc\u48dd\u48de\u48df\u48e0\u48e1\u48e2\u48e3\u48e4\u48e5\u48e6\u48e7\u48e8\u48e9\u48ea\u48eb\u48c9\u48ec\u48ed\u48ee\u48ef\u48f0\u48f1\u48f2\u48f3\u48f4\u48f5\u48f6\u48f7\u48f8\u4902\u4903\u4904\u4905\u4906\u4907\u4908\u4909\u490a\u490b\u490c\u490d\u490e\u490f\u4942\u4910\u4911\u4912\u4913\u4914\u4915\u4916\u4917\u4918\u4919\u491a\u491b\u491c\u491d\u491e\u491f\u4920\u4921\u4922\u4923\u4924' + '\u4925\u4926\u4927\u4928\u4929\u492a\u492b\u492c\u492d\u492e\u492f\u4930\u4931\u4932\u4933\u4934\u4935\u4936\u4937\u4938\u4939\u493a\u4943\u4944\u4945\u4946\u4947\u4948\u4949\u494a\u494b\u494c\u494d\u494e\u494f\u4950\u4951\u4952\u4953\u4954\u4955\u4956\u4957\u4958\u4959\u495a\u495b\u495c\u495d\u495e\u495f\u4960\u4961\u4962\u4963\u4964\u4965\u4966\u4967\u4968\u4969\u497f\u496a\u496b\u496c\u496d\u496e\u4980\u496f\u4970\u4971\u4972\u4973\u4981\u4982\u4983\u4984\u4985\u4986\u4987\u4988\u4989\u498a\u498b\u498c\u498d\u498e\u498f\u4990\u4991\u4992\u4993\u4994\u4995\u4996\u4997\u4998\u4999\u499a\u499b\u499c\u499d\u499e\u499f\u49a0\u49a1\u49a2\u49a3\u49a4\u49a5\u49a6\u49a7\u49a8\u49a9\u49b1\u49b2\u49b3\u49b4\u49b5\u49b6\u49b7\u49b8\u49b9\u49ba\u49bb\u49bc\u49bd\u49be' + '\u49bf\u49c0\u49c1\u49c2\u49c3\u49c4\u49c5\u49c6\u49c7\u49c8\u49c9\u49ca\u49cb\u49cc\u49cd\u49ce\u49cf\u49d0\u49d1\u49d2\u49d3\u49d4\u49d5\u49d6\u49d7\u49d8\u49d9\u49da\u49e2\u49e3\u49e4\u49e5\u49e6\u49e7\u49e8\u49e9\u49ea\u49eb\u49ec\u49ed\ua76c\u49ee\u49ef\u49f0\u49f1\u49f2\u49f3\u49f4\u49f5\u49f6\u49f7\u49f8\u49f9\u49fa\u49fb\u49fc\u49fd\u49fe\u4a08\u4a09\u4a0a\u4a0b\u4a0c\u4a0d\u4a0e\u4a0f\u4a10\u4a11\u4a12\u4a13\u4a14\u4a15\u4a16\u4a17\u4a18\u4a19\u4a1a\u4a23\u4a24\u4a25\u4a26\u4a27\u4a28\u4a29\u4a2a\u4a2b\u4a2c\u4a2d\u4a33\u4a34\u4a38\u4a39\u4a3a\u4a3b\u4a3c\u4a3d\u4a3e\u4421\u4a3f\u4a42\u4a43\u4a44\u4a46\u4a47\u4a48\u4a49\u4a4b\u4a4d\u4a4e\u4a4f\u4a50\u4a51\u4a52\u4a53\u4a54\u4a55\u4a56\u4a57\u4a58\u4a59\u4a5a\u4a5b\u4a5c\u4a5d\u4a5f\u4a60\u4a61\u4a62' + '\u4a63\u4a65\u4a66\u4a67\u4a68\u4a69\u4a6a\u4a6b\u4a6c\u4a71\u4a6e\u4a6f\u4a70\u4a72\u4a75\u4a76\u4a77\u4a78\u4a7a\u4a79\u4a7b\u4a7c\u4a7d\u4a7e\u4a7f\u4a81\u4a82\u4a83\u4a84\u4a85\u4a87\u4a88\u4a89\u4a8a\u4a8c\u4a8d\u4a8e\u4a8f\u4a92\u4a94\u4a95\u4a96\u4a97\u4a98\u4a99\u4a9a\u4a9b\u4a9c\u4a9d\u4a9e\u4a9f\u4aa0\u4aa1\u4aa3\u4aa4\u4aa5\u4aa6\u4aa7\u4aa8\u4aa9\u4aaa\u4aae\u4aaf\u4ab0\u4ab1\u4ab2\u4ab3\u4ab6\u4ab7\u4ab8\u4ab9\u4aba\u4abb\u4abc\u4ad1\u4abd\u4abe\u4abf\u4ac6\u4ac7\u4ac8\u4ac9\u4aca\u4acb\u4acc\u4acd\u4ace\u4ad2\u4ad3\u4ad4\u4ad5\u4ad6\u4ad7\u4ad8\u4ad9\u4add\u4ade\u4adf\u4ae0\u4ae1\u4ae2\u4ae3\u4ae5\u4ae6\u4ae7\u4ae9\u4aea\u4aeb\u4aec\u4aed\u4aee\u4aef\u4af1\u4af2\u4af3\u4af4\u4af5\u4af6\u4af7\u4af9\u4afa\u4afb\u4afc\u4afd\u4afe\u4aff\u4b00\u4b01' + '\u4b02\u4b03\u4b04\u4b05\u4b06\u4b07\u4b08\u4b09\u4b0a\u4b11\u4b12\u4b13\u4b14\u4b79\u4b15\u4b16\u4b17\u4b18\u4b19\u4b1a\u4b1b\u4b1c\u4b1d\u4b1e\u4b1f\u4b20\u4b21\u4b22\u4b23\u4b24\u4b25\u4b26\u4b27\u4b28\u4b29\u4b2a\u4b2b\u4b2c\u4b2d\u4b2e\u4b2f\u4b30\u4b31\u4b32\u4b3b\u4b3c\u4b3d\u4b3e\u4b3f\u4b40\u4b41\u4b42\u4b43\u4b44\u4b45\u4b46\u4b47\u4b48\u4b49\u4b4a\u4b4b\u4b4c\u4b4d\u4b4e\u4b4f\u4b50\u4b51\u4b52\u4b53\u4b54\u4b55\u4b56\u4b57\u4b58\u4b59\u4b5a\u4b5b\u4b5c\u4b5d\u4b5e\u4b5f\u4b60\u4b61\u4b62\u4b63\u4b64\u4b65\u4b66\u4b7a\u4b7b\u4b7c\u4b7d\u4b7e\u4b7f\u4b80\u4b81\u4b82\u4b83\u4b84\u4b85\u4b86\u4b87\u4b88\u4b89\u4b8a\u4b8b\u4b8c\u4b8d\u4bb4\u4b8e\u4b8f\u4b90\u4b91\u4b92\u4b93\u4b94\u4b95\u4b96\u4b97\u4b98\u4b99\u4b9a\u4b9b\u4b9c\u4b9d\u4b9e\u4b9f\u4ba0' + '\u4ba1\u4ba2\u4ba3\u4ba4\u79f2\u4ba5\u4ba6\u4ba7\u4ba8\u4bb5\u4bb6\u4bb7\u4bb8\u4bb9\u4bba\u4bbb\u4bbc\u4bbd\u4bbe\u4bbf\u4bc0\u4bc1\u4bc2\u4bc3\u4bc4\u4bc5\u4bc6\u4bc7\u4bc8\u4bc9\u4bca\u4bcb\u4bcc\u4bcd\u4bce\u4bcf\u4bd0\u4bd1\u4bd2\u4bd3\u4bd4\u4bd5\u4bd6\u4bd7\u4bf8\u4bd8\u4bd9\u4bda\u4bdb\u4bdc\u4bdd\u4bde\u4bdf\u4bf9\u4bfa\u4bfb\u4bfc\u4bfd\u4bfe\u4bff\u4c00\u4be0\u4c01\u4c02\u4c03\u4c04\u4c05\u4c06\u4c07\u4c08\u4c09\u4c0a\u4c0b\u4c0c\u4c0d\u4c0e\u4c0f\u4c10\u4c11\u4c12\u4c13\u4c14\u4c15\u4c16\u4c17\u4c18\u4c19\u4c1a\u4c1b\u4c1c\u4c1d\u4c1e\u4c1f\u4c20\u4c21\u4c22\u4c23\u4c24\u4c25\u4c26\u4c27\u4c28\u4c29\u4c2a\u4c2b\u4c2c\u4c2d\u4c2e\u4c2f\u4c30\u4c31\u4c32\u4c33\u4c34\u4c35\u4c36\u4c37\u4c38\u4c39\u4c43\u4c44\u4c45\u4c46\u4c47\u4c48\u4c49\u4c4a\u4c4b' + '\u4c4c\u4c4d\u4c4e\u4c4f\u4c50\u4c51\u4c52\u4c53\u4c54\u4c55\u4c56\u4c57\u4c58\u4c59\u4c5a\u4c5b\u4c93\u4c5c\u4c5d\u4c5e\u4c5f\u4c60\u4c61\u4c62\u4c63\u4c64\u4c65\u4c66\u4c67\u4c68\u4c69\u4c6a\u4c6b\u4c6c\u4c6d\u4c6e\u4c6f\u4c70\u4c71\u4c72\u4c73\u4c74\u4c75\u4c76\u4c77\u4c78\u4c79\u4c7a\u4c94\u4c95\u4c96\u4c97\u4c98\u4c99\u4c9a\u4c9b\u4c9c\u4c9d\u4c9e\u4c9f\u4ca0\u4ca1\u4ca2\u4ca3\u4ca4\u4ca5\u4ca6\u4ca7\u4ca8\u4ca9\u4caa\u4cab\u4cac\u4cad\u4cae\u4caf\u4cb0\u4cb1\u4cb2\u4c7b\u4cb3\u4cb4\u4cb5\u4cb6\u4cb7\u4cc8\u4cc9\u4cca\u4ccb\u4ccc\u4ccd\u4cce\u4ccf\u4cd0\u4cd1\u4cd2\u4cd3\u4cd4\u4cd5\u4cd6\u4cd7\u4cd8\u4cd9\u4cda\u4cdb\u4cdc\u4cdd\u4cde\u4cdf\u4ce0\u4ce1\u4ce2\u4ce3\u4ce4\u4ce5\u4cf4\u4cf5\u4cf6\u4cf7\u4cf8\u4cf9\u4cfa\u4cfb\u4cfc\u4cfd\u4cfe\u4cff\u4d00' + '\u4d01\u4d02\u4d03\u4d04\u4d05\u4d06\u4d07\u4d08\u4d09\u4d0a\u4d0b\u4d0c\u4d0d\u4d0e\u4d0f\u4d10\u4d17\u4d18\u4d19\u4d1a\u4d1b\u4d1c\u4d1d\u4d1e\u4d1f\u4d20\u4d21\u4d22\u4d23\u4d24\u4d25\u4d26\u4d27\u4d28\u4d29\u4d30\u4d31\u4d32\u4d33\u4d34\u4d35\u4d36\u4d37\u4d38\u4d39\u4d3a\u4d3b\u4d3c\u4d3d\u4d3e\u4d3f\u4d40\u4d2a\u4d41\u4d42\u4d43\u4d46\u4d4d\u4d47\u4d48\u4d49\u4d4a\u4d4e\u4d4f\u4d54\u4d55\u4d56\u4d57\u4d58\u4d59\u4d5a\u4d5e\u4d5f\u4d60\u4d62\u4d63\u4d64\u4d66\u4d67\u4d68\u4d6b\u4d6c\u4d6d\u4d6e\u4d6f\u4d70\u4d71\u4d72\u4d73\u4d74\u4d76\u4d77\u4d78\u4d79\u4d7a\u4d7d\u4d7e\u4d7f\u4d80\u4d81\u4d82\u4d83\u4d84\u4d85\u4d87\u4d88\u4d89\u4d8b\u4d8c\u4d8d\u4d90\u4d91\u4d92\u4d94\u4d93\u4d96\u4d97\u4d99\u4d9a\u4d9b\u4d9d\u4d9e\u4da0\u4da1\u4da2\u4da3\u4da4\u4da5' + '\u4da7\u4da9\u4daa\u4dab\u4dac\u4daf\u4db0\u4db1\u4db2\u4db3\u4db6\u4db7\u4db8\u4db9\u4dba\u4dbb\u4dbc\u4dbd\u4dbe\u4dc3\u4dc4\u4dc5\u4dc6\u4dc7\u4dc8\u4dc9\u4dca\u4dcb\u4dcc\u4dcd\u4dce\u4dcf\u4dd0\u4dd1\u4dd3\u4dd4\u4dd5\u4dd6\u4dd7\u4de0\u4dd8\u4dd9\u4dda\u4ddb\u4de1\u4de2\u4de3\u4de4\u4de5\u4de6\u4de7\u4de8\u4de9\u4dea\u4deb\u4dec\u4ded\u4dee\u4def\u4df0\u4df1\u4df2\u4df3\u4df8\u4df9\u4dfa\u4dfb\u4dfc\u4dfd\u4dfe\u4dff\u4e00\u4e01\u4e02\u4e0a\u4e0b\u4e0c\u4e0d\u4e0e\u4e0f\u4e10\u4e11\u4e12\u4e13\u4e14\u4e15\u4e19\u4e1a\u4e1b\u4e1c\u4e1d\u4e1e\u4e1f\u4e20\u4e26\u4e27\u4e28\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f\u4e30\u4e35\u4e16\u4e36\u4e37\u4e38\u4e39\u4e3d\u4e3e\u4e3f\u4e40\u4e42\u4e43\u4e44\u4e45\u4e46\u4e49\u4e4a\u4e4c\u4e4d\u4e4e\u4e4f\u4e50\u4e51' + '\u4e53\u4e55\u4e56\u4e58\u4e59\u4e5a\u4e57\u4e5c\u4e5d\u4e5e\u4e5f\u4e60\u4e61\u4e63\u4e64\u4e65\u4e66\u4e67\u4e68\u4e69\u4e6a\u4e6b\u4e6d\u4e6e\u4e6f\u4e70\u4e71\u4e72\u4e73\u4e74\u4e76\u4e78\u4e79\u4e7a\u4e7d\u4e7e\u4e7f\u4e80\u4e81\u4e82\u4e84\u4e85\u4e86\u4e87\u4e88\u4e8a\u4e8d\u4e8e\u4e96\u4e97\u4e9a\u4e9b\u4e9c\u4e9f\u4ea0\u4ea1\u4ea2\u3d25\u4ea3\u4ea4\u4ea5\u4ea7\u4eaa\u4eab\u4eac\u4ead\u4eae\u4eaf\u4eb1\u4eb2\u4eb3\u4eb4\u4eb5\u4eb6\u4eb7\u4ebb\u4ebc\u4ebd\u4ebe\u4ebf\u4ec5\u4ec6\u4ec7\u4ec8\u4ec9\u4eca\u4ecb\u4ecc\u4ecd\u4ecf\u4ed0\u4ed1\u4ed2\u4ed3\u4ed6\u4ed7\u4ed8\u4ed9\u4edc\u4edd\u4ee1\u4ee2\u4ee3\u4ee4\u4ee7\u4ee8\u4ee9\u4eea\u4eec\u4eed\u4eee\u4ef0\u4ef2\u4ef5\u4ef7\u4ef8\u4ef9\u4efa\u4efb\u4efc\u4f01\u4f02\u4f03\u4f04\u4f05\u4f06\u4f07\u4f08' + '\u4f09\u4f0a\u4f0b\u4f0c\u4f15\u4f16\u4f17\u4f18\u4f19\u4f1a\u4f1b\u4f1c\u4f1d\u4f69\u4f1e\u4f1f\u4f20\u4f21\u4f22\u4f23\u4f24\u4f25\u4f26\u4f27\u4f28\u4f29\u4f2a\u4f2b\u4f2c\u4f35\u4f36\u4f37\u4f38\u4f39\u4f3a\u4f3b\u4f3c\u4f3d\u4f3e\u4f3f\u4f40\u4f41\u4f42\u4f43\u4f44\u4f45\u4f46\u4f47\u4f48\u4f49\u4f4a\u4f4b\u4f4c\u4f4d\u4f4e\u4f4f\u4f50\u4f51\u4f52\u4f53\u4f54\u4f55\u4f56\u4f57\u4f58\u4f59\u4f5a\u4f5b\u4f5c\u4f5d\u4f6a\u4f6b\u4f6c\u4f6d\u4f6e\u4f6f\u4f70\u4f71\u4f72\u4f73\u4f74\u4f75\u4f76\u4f77\u4f78\u4f79\u4f7a\u4f7b\u4f7c\u4f7d\u4f7e\u4f7f\u4f80\u4f81\u4f82\u4f83\u4f84\u4f85\u4f86\u4f87\u4f88\u4f89\u4f8a\u4f8b\u4f8f\u4f90\u4f91\u4f92\u4f93\u4f94\u4f95\u4f96\u4f97\u4f98\u4f99\u4f9a\u4f9b\u4f9c\u4f9d\u4f9e\u4f9f\u4fa0\u4fa1\u4fa2\u4fa3\u4fa4\u4fa5\u4fa6' + '\u4fa7\u4fa8\u4fa9\u4faa\u4fab\u4fac\u4fbd\u4fbe\u4fbf\u4fc0\u4fc1\u4fc2\u4fc3\u4fc4\u4fc5\u4fc6\u4fc7\u4fc8\u4fc9\u4fca\u4fcb\u4fcc\u4fcd\u4fce\u4fcf\u4fd0\u4fd1\u4fd2\u4fd3\u4fd4\u4fd5\u4fd6\u4fd7\u4fd8\u4fd9\u4fda\u4fdb\u4fdc\u4fdd\u4fde\u4fdf\u4fe0\u4fe1\u4fe2\u4fe3\u4fe4\u4fe5\u4fe6\u4fe7\u4ff7\u4ff8\u4ff9\u4ffa\u4ffb\u4ffc\u4ffd\u4ffe\u4fff\u5000\u5001\u5002\u5003\u5004\u5005\u5006\u5007\u5008\u5009\u500a\u500b\u500c\u500d\u500e\u500f\u502e\u5010\u5011\u5012\u5013\u5014\u5015\u5016\u5017\u5018\u5019\u501a\u501b\u501c\u501d\u501e\u501f\u5020\u5021\u5022\u502f\u5030\u5031\u5032\u5033\u5034\u5035\u5036\u5037\u5038\u5039\u503a\u503b\u503c\u503d\u503e\u503f\u5040\u5041\u5042\u5043\u5044\u5045\u5046\u5047\u5051\u5052\u5053\u5054\u5055\u5056\u5057\u5058\u5059' + '\u505a\u505b\u505c\u505d\u505e\u505f\u5060\u5061\u5062\u5063\u5064\u5065\u5066\u5067\u5068\u5075\u5076\u5077\u5078\u5079\u507a\u507b\u507c\u507d\u507e\u507f\u5080\u5081\u5082\u5083\u5084\u5085\u5086\u5087\u5088\u5089\u508a\u508b\u5098\u5099\u509a\u509b\u509c\u509d\u509e\u509f\u50a0\u50a1\u50a2\u50a3\u50a4\u50a5\u50a6\u50a7\u50a8\u50b0\u50b1\u50b2\u50b3\u50c3\u50b4\u50b5\u50b6\u50b7\u50bd\u50be\u50bf\u50c4\u50c5\u50c6\u50cb\u50cc\u50cd\u50ce\u50cf\u50d0\u50d1\u50d3\u50d8\u50d4\u50d5\u50d9\u50da\u50db\u50dc\u50dd\u50e0\u50de\u50e1\u50e2\u50e3\u50e5\u50e6\u50e7\u50e8\u50e9\u50eb\u9366\u50ec\u50ed\u50ee\u50ef\u50f1\u50f2\u50f3\u50f4\u50f5\u50f6\u50f7\u50f8\u50f9\u50fc\u50fe\u5100\u5101\u5102\u5103\u5105\u5106\u5108\u5109\u510a\u510b\u510c\u510d\u510f\u5110\u5111' + '\u5112\u5113\u5114\u5115\u5116\u5117\u511a\u511b\u511c\u5120\u5121\u5122\u5123\u5124\u5125\u5126\u5127\u512e\u512f\u5130\u5131\u5132\u5133\u5134\u5135\u5136\u5137\u5138\u5139\u513e\u513f\u5140\u5141\u5142\u5143\u5144\u5145\u5146\u5147\u5148\u514f\u5150\u5151\u5152\u5153\u5154\u5155\u5156\u5157\u5158\u515b\u515c\u515d\u515e\u515f\u5160\u5161\u5162\u5163\u5164\u5165\u516c\u516d\u516e\u516f\u5170\u5171\u5172\u5173\u5174\u5175\u5176\u518b\u5177\u5180\u5181\u5182\u5183\u5184\u5185\u8073\u518c\u518d\u518e\u518f\u5190\u5191\u5192\u5193\u5194\u519a\u5195\u519b\u519c\u519d\u519e\u519f\u51a0\u51a1\u51a2\u51a3\u51a4\u51a7\u51a8\u51a9\u51aa\u51ae\u51af\u51b0\u51b3\u51b4\u51b5\u51b6\u51b7\u51bc\u51bd\u51be\u51bf\u51c0\u51c1\u51c2\u51c3\u51c5\u51c6\u51c7\u51c8\u51c9\u51ce' + '\u51cf\u51d0\u51d1\u51d2\u51d3\u51d4\u51d5\u51d9\u51da\u51db\u51dc\u51dd\u51de\u51df\u51e0\u51e1\u51e2\u51e3\u51e4\u51e5\u51e6\u51ea\u51eb\u51ec\u51e7\u51ed\u51ee\u51ef\u51f0\u51f1\u51f2\u51f3\u51fc\u51fd\u51fe\u51ff\u5200\u5201\u5202\u520a\u520b\u520c\u520d\u520e\u520f\u5210\u5211\u5212\u5218\u5219\u521a\u521b\u521c\u521d\u521e\u521f\u5220\u5221\u5222\u5223\u5224\u522a\u522b\u522c\u522d\u522e\u522f\u5230\u523f\u5236\u5237\u5238\u5239\u523a\u5231\u523b\u523c\u5240\u5241\u5242\u5243\u5244\u5245\u5246\u5247\u5248\u5249\u524a\u524b\u5250\u5251\u5252\u5253\u5254\u5255\u5256\u5257\u5258\u5259\u525a\u525b\u525f\u5260\u5261\u5262\u5263\u5264\u5269\u526a\u526b\u526e\u526f\u5270\u5272\u5273\u5274\u5275\u5276\u5278\u5279\u527a\u527b\u527d\u527e\u527f\u5280\u5281\u5282' + '\u5283\u5284\u5285\u5286\u5287\u5288\u5289\u528a\u528b\u528c\u528e\u5291\u5292\u5293\u5294\u5295\u5296\u5299\u529a\u529c\u529d\u529e\u529f\u52a1\u52a2\u52a3\u52a4\u52a5\u52a6\u52ab\u52ac\u52ad\u52ae\u52b1\u52b2\u52b3\u52b4\u52b5\u52b6\u52b7\u52b8\u52b9\u52ba\u52bf\u52c0\u52c1\u52c2\u52c3\u52ca\u52cb\u52cc\u52cd\u52d0\u52d1\u52d2\u52d3\u52d4\u52d5\u52d9\u52da\u52db\u52dc\u52dd\u52e1\u52e2\u52e3\u52e4\u52e8\u52e9\u52ea\u52ec\u52ed\u52ee\u52ef\u52f2\u52f3\u52f4\u52f5\u52f7\u52f8\u52f9\u52fa\u52fb\u52fc\u52fd\u52fe\u5300\u5301\u5302\u5303\u5304\u5305\u5306\u5308\u5309\u530a\u530b\u530c\u530e\u530f\u5310\u5315\u5316\u5317\u5318\u5319\u86ef\u531a\u531b\u531d\u5320\u5321\u5322\u5324\u5327\u5328\u532a\u532b\u532d\u532e\u532f\u5330\u5331\u5332\u5337\u5338\u5339\u533a' + '\u533b\u533c\u533d\u533e\u533f\u5342\u5343\u5344\u5345\u5346\u5347\u5348\u5349\u534a\u5354\u5355\u5356\u5357\u5358\u5359\u534b\u535a\u535d\u535e\u535f\u5360\u5371\u5361\u5362\u5363\u5364\u5365\u5366\u5367\u5368\u5369\u536a\u5372\u5373\u5374\u5375\u5376\u5377\u5378\u537e\u537f\u5380\u5381\u5382\u5384\u538a\u5385\u5386\u538b\u538c\u538d\u538e\u538f\u5390\u5393\u5394\u5397\u5398\u539a\u539c\u539d\u539f\u53a0\u53a1\u53a2\u53a3\u53a5\u53a6\u53a7\u53a8\u53aa\u53ab\u53ac\u53ad\u53ae\u53af\u53b0\u53b1\u53b2\u53b3\u53b4\u53b5\u53b6\u53b7\u53b8\u53b9\u53ba\u53bb\u53c1\u53c2\u53c3\u53c4\u53c5\u53c6\u53c7\u53c8\u53c9\u53ca\u53cb\u53cc\u53cd\u53ce\u53cf\u53d0\u53d1\u53d2\u53d3\u53d4\u53d5\u53d6\u53d7\u53d8\u53d9\u53da\u53db\u53dc\u53dd\u53de\u53df\u53e0\u53e1\u53e2\u53e3' + '\u53e4\u53e5\u53e6\u53e7\u53e8\u53e9\u53ea\u53f6\u53f7\u53f8\u53f9\u53fa\u53fb\u53fc\u53fd\u53fe\u53ff\u5400\u5401\u5402\u5403\u5404\u5405\u5406\u5407\u5408\u5409\u540a\u540b\u540c\u540d\u540e\u540f\u5410\u5411\u5412\u5413\u5414\u5415\u5416\u5417\u5418\u5419\u541a\u541b\u541c\u541d\u541e\u541f\u5420\u5421\u5422\u5423\u5424\u5425\u5426\u5427\u5428\u5429\u542a\u542b\u542c\u542d\u542e\u543d\u543e\u543f\u5440\u5441\u5442\u5443\u5444\u5445\u5446\u5447\u5448\u5449\u544a\u544b\u544c\u544d\u544e\u544f\u5450\u5451\u5452\u5453\u5454\u5455\u5456\u5457\u5458\u5459\u545a\u545b\u545c\u545d\u545e\u545f\u5460\u5461\u5462\u5463\u5464\u5465\u5466\u5467\u5468\u5469\u546a\u546b\u546c\u546d\u546e\u546f\u5470\u5471\u5472\u5473\u5474\u5475\u5476\u5477\u5478\u5479\u547a\u5490\u5491' + '\u5492\u5493\u5494\u5495\u5496\u5497\u5498\u5499\u549a\u549b\u549c\u549d\u549e\u549f\u54a0\u54a1\u54a2\u54a3\u54a4\u54a5\u54a6\u54a7\u54a8\u54a9\u54aa\u54ab\u54ac\u54ad\u54ae\u54af\u54b0\u54b1\u54b2\u54b3\u54b4\u54b5\u54b6\u54b7\u54b8\u54b9\u54ba\u54bb\u54bc\u54bd\u54be\u54bf\u54c0\u54c1\u54d1\u54d2\u54d3\u54d4\u54d5\u54d6\u54d7\u54d8\u54d9\u54da\u54db\u54dc\u54dd\u54de\u54df\u54e0\u54e1\u54e2\u54e3\u54e4\u54e5\u54e6\u54e7\u54e8\u54e9\u54ea\u54eb\u54ec\u54ed\u54ee\u54ef\u54f0\u54f1\u54f2\u54f3\u54f4\u54f5\u54f6\u54f7\u54f8\u54f9\u54fa\u54fb\u54fc\u54fd\u54fe\u54ff\u5500\u5501\u5502\u5503\u5504\u5505\u5506\u5507\u5508\u5509\u550a\u550b\u550c\u550d\u550e\u550f\u5510\u552d\u552e\u552f\u5530\u5531\u5532\u5533\u5534\u5535\u5536\u5537\u5538\u5539\u553a\u553b\u553c' + '\u553d\u553e\u553f\u5540\u5541\u5542\u5543\u5544\u5545\u5546\u5547\u5548\u5549\u554a\u554b\u554c\u554d\u554e\u554f\u5550\u5551\u5552\u5553\u5554\u5555\u5556\u5557\u5558\u5559\u555a\u555b\u555c\u555d\u555e\u555f\u5560\u5561\u5562\u5563\u5577\u5578\u5579\u557a\u557b\u557c\u557d\u557e\u557f\u5580\u5581\u5582\u5583\u5584\u5585\u5586\u5587\u5588\u5589\u558a\u558b\u558c\u558d\u558e\u558f\u5590\u5591\u5592\u5593\u5594\u5595\u5596\u5597\u5598\u5599\u559a\u559b\u559c\u559d\u559e\u559f\u55a0\u55a1\u55b2\u55b3\u55b4\u55b5\u55b6\u55b7\u55b8\u55b9\u55ba\u55bb\u55bc\u55bd\u55be\u55bf\u55c0\u55c1\u55c2\u55c3\u55c4\u55c5\u55c6\u55c7\u5564\u55c8\u55c9\u55ca\u55cb\u55f4\u55cc\u55cd\u55ce\u55cf\u55d0\u55d1\u55d2\u55d3\u55d4\u55d5\u55d6\u55d7\u55d8\u55d9\u55da\u55db\u55dc\u55dd' + '\u55de\u55df\u55e0\u55e1\u55e2\u55e3\u55e4\u55e5\u55e6\u55f5\u55f6\u55f7\u55f8\u55f9\u55fa\u55fb\u55fc\u55fd\u55fe\u55ff\u5600\u5601\u5602\u5603\u5604\u5605\u5606\u5607\u5608\u5609\u560a\u560b\u560c\u560d\u560e\u560f\u5610\u5611\u5612\u5613\u5614\u5615\u5616\u5617\u5618\u5619\u561a\u561b\u561c\u561d\u561e\u561f\u562e\u562f\u5630\u5631\u5632\u5633\u5634\u5651\u5635\u5636\u5637\u5638\u5639\u563a\u563b\u563c\u563d\u563e\u563f\u5640\u5641\u5642\u5643\u5644\u5645\u5646\u5647\u5648\u5649\u564a\u564b\u564c\u564d\u5652\u5653\u5654\u5655\u5656\u5657\u5658\u5659\u565a\u565b\u565c\u565d\u565e\u565f\u5660\u5661\u5662\u5663\u5664\u5665\u566d\u566e\u566f\u5670\u5671\u5672\u5673\u5674\u5675\u5676\u5677\u5678\u567b\u567c\u567d\u567e\u5684\u5685\u5686\u568a\u568b\u568c\u568d' + '\u568e\u568f\u5690\u5693\u5694\u5695\u5696\u5697\u5699\u569a\u569b\u569c\u569d\u569e\u569f\u56a0\u56a1\u56a2\u56a3\u56a4\u56a7\u56a8\u56a9\u56aa\u56ae\u56b0\u56b4\u56b5\u56b6\u56b7\u56b9\u56ba\u56bd\u56be\u56bf\u56c0\u56c1\u56c2\u56bb\u56c4\u56c5\u56c6\u56c7\u56c8\u56c9\u56cb\u56cc\u56cd\u56d1\u56ce\u56d2\u56d5\u56d3\u56d6\u56d7\u56d8\u56d9\u56da\u56db\u56dc\u56dd\u56df\u56e0\u56e1\u56e2\u56e5\u56e6\u56e7\u56ea\u56eb\u56ec\u56ed\u56ee\u56ef\u56f0\u56f1\u56f2\u56f3\u56f5\u5700\u56f6\u56f7\u56f8\u56f9\u56fa\u56fb\u56fc\u5701\u5702\u5703\u5704\u5705\u5706\u5707\u5708\u5719\u5709\u570a\u570b\u570c\u570d\u570e\u570f\u5710\u5711\u5712\u5713\u5714\u5715\u571a\u571b\u571c\u571d\u571e\u571f\u5720\u5721\u5722\u5723\u5724\u575b\u5725\u5726\u5727\u5728\u5729\u572a\u572b' + '\u572c\u572d\u572e\u572f\u5730\u5731\u5732\u5733\u5734\u5735\u5736\u5737\u5738\u5739\u573a\u573b\u573c\u573d\u573e\u573f\u5740\u5741\u5742\u5743\u5744\u5745\u5746\u5747\u5748\u5749\u574a\u574b\u574c\u574d\u574e\u574f\u5750\u5751\u575c\u575d\u575e\u575f\u5760\u5761\u5762\u5763\u5764\u5765\u5766\u5767\u5768\u5769\u576a\u576b\u576c\u576d\u576e\u576f\u5770\u5771\u5772\u5773\u5774\u5775\u5776\u5777\u5778\u5779\u577a\u577b\u577c\u577d\u577e\u577f\u5780\u5781\u5782\u5783\u5784\u5785\u5786\u5787\u5788\u5789\u578a\u578b\u578c\u578d\u578e\u578f\u5790\u5791\u5792\u5793\u5794\u5795\u5796\u5797\u5798\u5799\u579a\u579b\u579c\u579d\u579e\u579f\u57af\u57b0\u57b1\u57b2\u57b3\u57b4\u57b5\u57b6\u57b7\u57b8\u57b9\u57ba\u57bb\u57bc\u57bd\u57be\u57bf\u57c0\u57c1\u57c2\u57c3\u57c4' + '\u57c5\u57c6\u57c7\u57c8\u57c9\u57ca\u57cb\u57cc\u57cd\u57ce\u57cf\u57d0\u57d1\u57d2\u57d3\u57d4\u57d5\u57d6\u57d7\u57d8\u57d9\u57da\u57db\u57dc\u57dd\u57de\u57df\u57e0\u57e1\u57e2\u57e3\u57e4\u57e5\u57e6\u57e7\u57e8\u57e9\u57ea\u57eb\u57ec\u57fe\u57ff\u5800\u5801\u5802\u5803\u5804\u5805\u5806\u5807\u5808\u5809\u580a\u580b\u580c\u580d\u580e\u580f\u5810\u5811\u5812\u5813\u5814\u5815\u5816\u5817\u5818\u5819\u581a\u581b\u581c\u581d\u581e\u581f\u5820\u5821\u5822\u5823\u5824\u5825\u5826\u5827\u5828\u5829\u582a\u582b\u582c\u582d\u582e\u582f\u5830\u5831\u5832\u5833\u5834\u5835\u5836\u5837\u5838\u5839\u583a\u5842\u5843\u5844\u5845\u5846\u5847\u5848\u5849\u584a\u584b\u584c\u584d\u584e\u584f\u5850\u5851\u5852\u5853\u5854\u5855\u5856\u5857\u5858\u5859\u585a\u585b\u585c' + '\u585d\u585e\u585f\u5860\u5861\u5862\u5863\u5864\u5865\u5866\u5867\u5868\u5869\u586a\u586b\u586c\u586d\u586e\u586f\u5870\u5871\u5872\u5873\u5874\u5875\u5876\u5877\u5878\u5879\u587a\u587b\u587c\u587d\u587e\u587f\u5880\u5881\u5882\u5883\u5884\u5885\u5886\u5887\u5888\u5889\u588a\u588b\u588c\u588d\u588e\u58aa\u588f\u5890\u5891\u5892\u5893\u5894\u5895\u5896\u5897\u5898\u5899\u58ab\u58ac\u58ad\u58ae\u58af\u58b0\u58b1\u595b\u58b2\u58b3\u58b4\u58b5\u58b6\u58b7\u58b8\u58b9\u58ba\u58bb\u58bc\u58bd\u58be\u58bf\u58c0\u58c1\u58c2\u58c3\u58c4\u58c5\u58c6\u58c7\u58c8\u58c9\u58ca\u58cb\u58cc\u58cd\u58ce\u58cf\u58d0\u58d1\u58d2\u58d3\u58d4\u58d5\u58d6\u58d7\u58d8\u58d9\u58da\u58db\u58dc\u58dd\u58de\u58df\u58e0\u58e1\u58e2\u58e3\u58e4\u58e5\u58e6\u58e7\u58e8\u58e9\u58ea\u58eb' + '\u58ec\u58ed\u58ee\u58ef\u58f0\u58f1\u5902\u5903\u5904\u5905\u5906\u5907\u5908\u5909\u590a\u590b\u590c\u590d\u590e\u590f\u5910\u5911\u5912\u5913\u5914\u5915\u5916\u5917\u5918\u5919\u591a\u591b\u591c\u591d\u591e\u591f\u5920\u5921\u5922\u5923\u5924\u5925\u5926\u5927\u5928\u5929\u592a\u592b\u592c\u59ab\u592d\u592e\u592f\u5930\u5931\u5932\u5933\u5934\u5935\u5936\u5937\u5938\u5939\u593a\u593b\u593c\u593d\u593e\u593f\u5940\u5941\u5942\u5943\u5944\u5945\u595c\u595d\u595e\u595f\u5960\u5961\u5962\u58f2\u5963\u5964\u5965\u59ac\u5966\u5967\u5968\u5969\u596a\u596b\u596c\u596d\u596e\u596f\u5970\u5971\u5972\u5973\u5974\u5975\u5976\u5977\u5978\u5979\u597a\u597b\u597c\u597d\u597e\u597f\u5980\u5981\u5982\u5983\u5984\u5985\u5986\u5987\u5988\u5989\u598a\u598b\u598c\u598d\u598e' + '\u598f\u5990\u5991\u5992\u5993\u59ad\u59ae\u5994\u59af\u59b0\u59b1\u59b2\u59b3\u59b4\u59b5\u59b6\u59b7\u59b8\u59b9\u59ba\u59bb\u59bc\u59bd\u59be\u59bf\u59c0\u59c1\u59c2\u59c3\u59c4\u59c5\u59c6\u59c7\u59c8\u59c9\u59ca\u59cb\u59cc\u59cd\u59ce\u59cf\u59d0\u59d1\u59d2\u59d3\u59d4\u59d5\u59d6\u59d7\u59d8\u59d9\u59da\u59db\u59dc\u59dd\u59de\u59df\u59e0\u59e1\u59f2\u59f3\u59f4\u59f5\u59f6\u59f7\u59f8\u59f9\u59fa\u59fb\u59fc\u59e2\u59fd\u59fe\u59ff\u5a00\u5a01\u5a02\u5a03\u5a04\u5a05\u5a06\u5a07\u5a08\u5a09\u5a0a\u5a0b\u5a0c\u5a0d\u5a0e\u5a0f\u5a10\u5a11\u5a12\u5a13\u5a14\u5a1d\u5a1e\u5a1f\u5a20\u5a21\u5a22\u5a35\u5a23\u5a24\u5a25\u5a26\u5a27\u5a28\u5a29\u5a2a\u5a2b\u5a2c\u5a2d\u5a2e\u5a36\u5a37\u5a38\u5a39\u5a3a\u5a3b\u5a3c\u5a3d\u5a3e\u5a3f\u5a40\u5a41\u5a42\u5a43' + '\u5a44\u5a45\u5a46\u5a47\u5a48\u5a49\u5a4a\u5a57\u5a58\u5a59\u5a5a\u5a5b\u5a5c\u5a5d\u5a5e\u5a5f\u5a60\u5a64\u5a61\u5a65\u5a66\u5a67\u5a68\u5a69\u5a6a\u5a6b\u5a6c\u5a70\u5a71\u5a72\u5a77\u5a78\u5a79\u5a7a\u5a7b\u5a7c\u5a7d\u5a81\u5a7e\u5a7f\u5a82\u5a83\u5a84\u5a85\u5a89\u5a8a\u5a8b\u5a8e\u5a8f\u5a91\u5a95\u5a97\u5a98\u5a99\u5a9a\u5a9b\u5a9c\u5a9d\u5a9e\u5a9f\u5aa0\u5aa3\u5aa4\u5aa5\u5aa8\u5aa9\u5aaa\u5aab\u5aac\u5aad\u5ab2\u5ab3\u5ab4\u5ab5\u5ab6\u5ab7\u5ab8\u5abd\u5abe\u5abf\u5ac0\u5ac1\u5ac2\u5ac3\u5ac4\u5ac5\u5ac6\u5ac7\u5ac8\u5ac9\u5aca\u5acb\u5ad1\u5ad2\u5ad3\u5ad4\u5ad5\u5ad6\u5ad7\u5ad8\u5ad9\u5ada\u5adb\u5adc\u5add\u5ade\u5adf\u5aed\u5aee\u5ae0\u5aef\u5af0\u5af1\u5af7\u5af8\u5af9\u5afe\u5aff\u5b00\u5b01\u5b02\u5b03\u5b04\u5b05\u5b09\u5b0a\u5b0b\u5b0c' + '\u5b11\u5b12\u5b13\u5b14\u5b18\u5b19\u5b1a\u5b1d\u5b1e\u5b20\u5b21\u5b22\u5b24\u5b25\u5b26\u5b27\u5b28\u5b29\u5b2a\u5b2c\u5b2d\u5b2e\u5b2f\u5b30\u5b33\u5b36\u5b37\u5b39\u5b3a\u5b3c\u5b3e\u5b3f\u5b40\u5b41\u5b43\u5b44\u5b47\u5b48\u5b49\u5b4a\u5b4b\u5b4c\u5b4d\u5b4e\u5b53\u5b54\u5b55\u5b56\u5b58\u5b59\u5b5a\u5b5c\u5b5e\u5b5f\u5b60\u5b61\u5b62\u5b63\u5b65\u5b66\u5b67\u5b69\u5b6a\u5b6b\u5b6c\u5b6e\u5b6f\u5b70\u5b71\u5b72\u5b73\u5b79\u5b7a\u5b7b\u5b74\u5b7c\u5b7d\u5b7e\u5b7f\u5b80\u5b84\u5b85\u5b87\u5b88\u5b89\u5b8a\u5b8b\u5b8c\u5b8f\u5b90\u5b91\u5b92\u5b93\u5b94\u5b95\u5b96\u5b97\u5b98\u5b99\u5b9a\u5b9b\u5b9c\u5b9d\u5b9e\u5b9f\u5ba0\u5ba1\u5ba2\u5ba3\u5ba4\u5ba5\u5ba6\u5ba9\u5baa\u5bab\u5bac\u5bad\u5bae\u5baf\u5bb0\u5bb1\u5bb8\u5bb9\u5bba\u5bbb\u5bbc\u5bbd\u5bbe' + '\u5bbf\u5bc0\u5bc1\u5bc2\u5bc3\u5bc4\u5bc5\u5bc6\u5bc7\u5bc8\u5bc9\u5bca\u5bcb\u5bcc\u5bcd\u5bce\u5bcf\u5bd0\u5bd1\u5bd2\u5bd3\u5bd4\u5bd5\u5bd6\u5bd7\u5bd8\u5bdc\u5bdd\u5bde\u5bdf\u5be0\u5be1\u5be2\u5be3\u5be4\u5be5\u5be6\u5be7\u5be8\u5be9\u5bea\u5beb\u5bec\u5bed\u5bee\u5bef\u5bf0\u5bf1\u5bf2\u5bf3\u5bf4\u5bf5\u5bf6\u5bf7\u5bf8\u5bf9\u5bfa\u5bfb\u5bfc\u5bfd\u5bfe\u5bff\u5c00\u5c01\u5c0c\u5c0d\u5c0e\u5c0f\u5c10\u5c11\u5c12\u5c13\u5c14\u5c15\u5c16\u5c17\u5c18\u5c19\u5c1a\u5c1b\u5c1c\u5c1d\u5c1e\u5c1f\u5c20\u5c21\u5c22\u5c2e\u5c2f\u5c30\u5c31\u5c32\u5c33\u5c34\u5c35\u5c36\u5c37\u5c38\u5c39\u5c3a\u5c3b\u5c3c\u5c3d\u5c3e\u5c3f\u5c40\u5c48\u5c49\u5c4a\u5c4b\u5c4c\u5c4d\u5c4e\u5c4f\u5c50\u5c51\u5c52\u5c53\u5c54\u5c55\u5c56\u5c57\u5c58\u5c59\u5c5a\u5c5b\u5c5c\u5c5d' + '\u5c5e\u5c5f\u5c60\u5c61\u5c6f\u5c70\u5c71\u5c72\u5c73\u5c74\u5c75\u5c76\u5c77\u5c78\u5c79\u5c7a\u5c7b\u5c62\u5c7c\u5c7d\u5c7e\u5c7f\u5c80\u5c81\u5c82\u5c83\u5c90\u5c91\u5c92\u5c93\u5c94\u5c95\u5c96\u5c97\u5c98\u5c99\u5c9a\u5c9b\u5c9c\u5c9d\u5c9e\u5ca3\u5ca4\u5ca5\u5ca6\u5ca7\u5ca8\u5ca9\u5caa\u5cab\u5cac\u5cad\u5cae\u5caf\u5cb0\u5cb1\u5cb8\u5cb9\u5cba\u5cbb\u5cbc\u5cbd\u5cbe\u5cbf\u5cc0\u5cc1\u5cc2\u5cc3\u5cc4\u5cc5\u5cc6\u5cc7\u5cc8\u5cc9\u5cca\u5ccb\u5ccc\u5ccd\u5cd8\u5cd9\u5cda\u5cdb\u5cdc\u5cdd\u5cde\u5cdf\u5ce0\u5ce1\u5ce6\u5ce7\u5ce2\u5ce8\u5ce9\u5cf0\u5cf1\u5cf2\u5cf3\u5cf4\u5cf5\u5cf8\u5cf9\u5cfa\u5cfb\u5cfc\u5cfd\u5cff\u5d02\u5d03\u5d04\u5d06\u5d07\u5d08\u5d0a\u5d0b\u5d0c\u5d0d\u5d0f\u5d10\u5d11\u5d12\u5d13\u5d15\u5d14\u5d16\u5d17\u5c41\u5d18\u5d19' + '\u412a\u5d1a\u5d1b\u5d1c\u5d1d\u5d1e\u5d1f\u5d22\u5d23\u5d24\u5d25\u5d26\u5d27\u5d28\u5d2c\u5d2d\u5d2e\u7fa9\u5d2f\u5d30\u5d31\u5d32\u5d35\u5d36\u5d37\u5d38\u5d39\u5d3a\u5d3d\u5d3e\u5d3f\u5d40\u5d43\u8074\u5d45\u5d4c\u5d4d\u80e6\u5d50\u5d51\u5d52\u5d53\u5d54\u5d55\u5d56\u5d57\u5d58\u5d59\u5d5a\u5d5b\u5d5c\u5d5d\u5d5e\u5d5f\u5d60\u5d61\u5d62\u5d63\u5d64\u5d65\u5d66\u5d67\u5d68\u5d69\u5d6a\u5d6b\u5d6c\u5d6d\u5d6f\u5d70\u5d71\u5d72\u5d73\u5d74\u5d75\u5d76\u5d77\u5d78\u5d79\u5d7a\u5d7b\u5d7c\u5d7d\u5d7e\u5d7f\u5d80\u5d81\u5d82\u5d83\u5d84\u5d85\u5d86\u5d87\u5d88\u5d89\u5d8a\u5d8b\u5d8c\u5d8d\u5d8e\u5d8f\u5d90\u5d91\u5d92\u5d93\u5d94\u5d9b\u5d9c\u5d9d\u5d9e\u5d9f\u5da0\u5da1\u5da2\u5da3\u5da4\u5da5\u5da6\u5da7\u5da8\u5da9\u5daa\u5dab\u5dac\u5dad\u5dae\u5daf\u5db0' + '\u5db1\u5d95\u5db2\u5db3\u5db4\u5db5\u5db6\u5db7\u5db8\u5db9\u5dba\u5dbb\u5dbc\u5dbd\u5dbe\u5dbf\u5dc0\u5dc1\u5dc2\u5dc3\u5dc4\u5dc5\u5dc6\u5dc7\u5dc8\u5dc9\u5dca\u5dcb\u5dcc\u5dcd\u5dce\u5dcf\u5dd0\u5dd1\u5dd2\u5dd3\u5dd4\u5dd5\u5dd6\u5dd7\u5dd8\u5dd9\u5dda\u5ddb\u5ddc\u5ddd\u5de8\u5de9\u5dea\u5deb\u5dec\u5ded\u5dee\u5def\u5df0\u5df1\u5df2\u5df3\u5df4\u5df5\u5df6\u5df7\u5df8\u5df9\u5dfa\u5dfb\u5dfc\u5dfd\u5dfe\u5dff\u5e00\u5e01\u5e02\u5e03\u5e04\u5e05\u5e06\u5e07\u5e08\u5e09\u5e0a\u5e0b\u5e0c\u5e0d\u5e0e\u5e0f\u5e10\u5e11\u5e12\u5e13\u5e14\u5e15\u5e16\u5e17\u5e18\u5e19\u5e1a\u5e1b\u5e1c\u5e1d\u5e1e\u5e1f\u5e20\u5e21\u5e22\u5e23\u5e24\u5e25\u5e26\u5e27\u5e28\u5e29\u5e2a\u5e2b\u5e2c\u5e2d\u5e2e\u5e2f\u5e30\u5e31\u5e32\u5e33\u5e34\u5e35\u5e36\u5e37\u5e38\u5e39' + '\u5e3a\u5e3b\u5e3c\u5e3d\u5e3e\u5e3f\u5e40\u5e41\u5e42\u5e43\u5e44\u5e45\u5e46\u5e47\u5e48\u5e49\u5e4a\u5e4b\u5e53\u5e54\u5e55\u5e56\u5e57\u5e58\u5e59\u5e5a\u5e5b\u5e5c\u5e5d\u5e5e\u5e5f\u5e60\u5e61\u5e62\u5e63\u5e64\u5e65\u5e66\u5e67\u5e68\u5e69\u5e6a\u5e6b\u5e6c\u5e6d\u5e6e\u5e6f\u5e70\u5e71\u5e72\u5e73\u5e74\u5e75\u5e76\u5e77\u5e78\u5e79\u5e7a\u5e7b\u5e7c\u5e7d\u5e7e\u5e7f\u5e80\u5e81\u5e82\u5e83\u5e84\u5e85\u5e86\u5e87\u5e88\u5e89\u5e8a\u5e8b\u5e8c\u5e8d\u5e8e\u5e8f\u5e90\u5e91\u5e92\u5e93\u5e94\u5e95\u5e96\u5e97\u5e98\u5e99\u5e9a\u5e9b\u5e9c\u5e9d\u5e9e\u5e9f\u5ea0\u5ea1\u5ea2\u5ea3\u5ea4\u5ea5\u5ea6\u5ea7\u5ea8\u5ea9\u5eaa\u5eab\u5ebd\u5ebe\u5ebf\u5ec0\u5ec1\u5ec2\u5ec3\u5ec4\u5ec5\u5ec6\u5ec7\u5ec8\u5ec9\u5eca\u5ecb\u5ecc\u5ecd\u5ece\u5ecf\u5ed0\u5ed1' + '\u5ed2\u5ed3\u5ed4\u5ed5\u5ed6\u5ed7\u5ed8\u5ed9\u5eda\u5edb\u5edc\u5edd\u5ede\u5edf\u5ee0\u5ee1\u5ee2\u5ee3\u5ee4\u5ee5\u5ee6\u5ee7\u5ee8\u5ee9\u5eea\u5eeb\u5eec\u5eed\u5eee\u5eef\u5ef0\u5ef1\u5ef2\u5ef3\u5ef4\u5ef5\u5ef6\u5ef7\u5ef8\u5ef9\u5efa\u5efb\u5efc\u5efd\u5efe\u5eff\u5f00\u5f01\u5f02\u5f03\u5f04\u5f05\u5f06\u5f07\u5f08\u5f09\u5f0a\u5f0b\u5f0c\u5f0d\u5f0e\u5f0f\u5f10\u5f11\u5f12\u5f13\u5f14\u5f24\u5f25\u5f26\u5f27\u5f28\u5f29\u5f2a\u5f2b\u5f2c\u5f2d\u5f2e\u5f2f\u5f30\u5f31\u5f32\u5f33\u5f34\u5f35\u5f36\u5f37\u5f38\u5f39\u5f3a\u5f3b\u5f3c\u5f3d\u5f3e\u5f3f\u5f40\u5f41\u5f42\u5f43\u5f44\u5f45\u5f46\u5f47\u5f48\u5f49\u5f4a\u5f4b\u5f4c\u5f4d\u5f4e\u5f4f\u5f50\u5f51\u5f52\u5f53\u5f54\u5f55\u5f56\u5f57\u5f58\u5f59\u5f5a\u5f5b\u5f5c\u5f5d\u5f5e\u5f5f\u5f60' + '\u5f61\u5f62\u5f63\u5f64\u5f65\u5f66\u5f67\u5f68\u5f69\u5f6a\u5f6b\u5f6c\u5f6d\u5f6e\u5f6f\u5f70\u5f71\u5f72\u5f73\u5f74\u5f75\u5f76\u5f77\u5f78\u5f79\u5f7a\u5f7b\u5f7c\u5f7d\u5f7e\u5f7f\u5f80\u5f81\u5f82\u5f83\u5f84\u5f85\u5f86\u5f87\u5f88\u5f89\u5f8a\u5f8b\u5f8c\u5f8d\u5f8e\u5f8f\u5fa4\u5fa5\u5fa6\u5fa7\u5fa8\u5fa9\u5faa\u5fab\u5fac\u5fad\u5fae\u5faf\u5fb0\u5fb1\u5fb2\u5fb3\u5fb4\u5fb5\u5fb6\u5fb7\u5fb8\u5fb9\u5fba\u5fbb\u5fbc\u5fbd\u5fbe\u5fbf\u5fc0\u5fc1\u5fc2\u5fc3\u5fc4\u5fc5\u5fc6\u5fc7\u5fc8\u5fc9\u5fca\u5fcb\u5fcc\u5fcd\u5fce\u5fcf\u5fd0\u5fd1\u5fd2\u5fd3\u5fd4\u5fd5\u5fd6\u5fd7\u5fd8\u5fd9\u5fda\u5fdb\u5fdc\u5fdd\u5fde\u5fdf\u5fe0\u5fe1\u5fe2\u5fe3\u5fe4\u5fe5\u5fe6\u5fe7\u5fe8\u5fe9\u5fea\u5feb\u5fec\u5fed\u5fee\u5fef\u5ff0\u5ff1\u5ff2\u5ff3\u5ff4' + '\u5ff5\u5ff6\u5ff7\u5ff8\u5ff9\u5ffa\u5ffb\u5ffc\u5ffd\u5ffe\u601c\u5fff\u6000\u601d\u601e\u601f\u6020\u6021\u6022\u6023\u6001\u6024\u6025\u6026\u6002\u6027\u6028\u6029\u602a\u602b\u602c\u602d\u602e\u602f\u6030\u6031\u6032\u6033\u6034\u6035\u6036\u6037\u6038\u6039\u603a\u603b\u603c\u603d\u603e\u603f\u6040\u6041\u6042\u6043\u6044\u6045\u6046\u6047\u6048\u6049\u604a\u604b\u604c\u604d\u604e\u604f\u6050\u6051\u6052\u6053\u6054\u6055\u6056\u6057\u6058\u6059\u605a\u605b\u605c\u605d\u605e\u605f\u6060\u6061\u6062\u6063\u6064\u6065\u6066\u6067\u6068\u6069\u606a\u606b\u606c\u606d\u606e\u606f\u608d\u608e\u608f\u6090\u6091\u6092\u6093\u6070\u6094\u6095\u6096\u6097\u6098\u6099\u609a\u609b\u609c\u609d\u609e\u609f\u60a0\u60a1\u60a2\u60a3\u60a4\u60a5\u60a6\u60a7\u60a8\u60a9' + '\u60aa\u60ab\u60ac\u60ad\u60ae\u60af\u60b0\u60b1\u60b2\u60b3\u60b4\u60b5\u60b6\u60b7\u60b8\u60b9\u60ba\u60bb\u60bc\u60bd\u60be\u60bf\u60c0\u60c1\u60c2\u60c3\u60c4\u60c5\u60c6\u60c7\u60c8\u60c9\u60ca\u60cb\u60cc\u60cd\u60ce\u60cf\u60d0\u60d1\u60d2\u60d3\u60d4\u60d5\u60d6\u60d7\u6071\u60d8\u60d9\u60da\u60f2\u60f3\u60f4\u60f5\u60f6\u60f7\u60f8\u60f9\u60fa\u60fb\u60fc\u60fd\u60fe\u60ff\u6100\u6101\u6102\u6103\u6104\u6105\u6106\u6107\u6108\u6109\u610a\u610b\u610c\u610d\u610e\u610f\u6110\u6111\u6112\u6113\u6114\u6115\u6116\u6117\u6118\u6119\u611a\u611b\u611c\u611d\u611e\u611f\u6120\u6121\u6122\u6123\u6124\u60db\u6125\u6126\u6127\u6128\u6129\u612a\u612b\u612c\u612d\u612e\u612f\u6130\u6131\u6132\u60dc\u6133\u6134\u6135\u6136\u6137\u6138\u6139\u613a\u6152\u6153\u6154' + '\u6155\u6156\u6157\u6158\u6159\u615a\u615b\u615c\u615d\u615e\u615f\u6160\u6161\u6162\u6163\u6164\u6165\u6166\u6167\u6168\u6169\u616a\u616b\u616c\u616d\u616e\u616f\u6170\u6171\u6172\u6173\u6174\u6175\u6176\u6177\u6178\u6179\u617a\u617b\u617c\u617d\u617e\u617f\u618d\u618e\u618f\u6190\u6191\u6192\u6193\u6194\u6195\u6196\u6197\u6198\u6199\u619a\u619b\u619c\u619d\u619e\u619f\u61a0\u61a1\u61a2\u61a3\u61a4\u61a5\u61a6\u61a7\u61a8\u61a9\u61aa\u61ab\u61ac\u61b4\u61b5\u61b6\u61b7\u61b8\u61b9\u61ba\u61bb\u61bc\u61bd\u61be\u61bf\u61c0\u61c1\u61c2\u61c3\u61c4\u61c5\u61c6\u61c7\u61c8\u61c9\u61ca\u61cb\u61cc\u61cd\u61ce\u61cf\u61dd\u61de\u61df\u61e0\u61d0\u61e1\u61d1\u61e2\u61e3\u61e4\u61e5\u61e6\u61e7\u61e8\u61e9\u61ea\u6208\u61f7\u61f8\u61f9\u61fa\u61fb\u61fc\u61fd\u61fe' + '\u61ff\u6200\u6201\u6202\u6203\u6204\u6209\u620a\u620b\u620c\u620d\u620e\u6205\u620f\u6210\u6213\u6214\u6215\u6216\u621b\u621c\u621d\u621e\u621f\u6220\u6221\u6222\u6223\u6224\u6225\u6227\u6228\u6229\u622a\u622b\u622f\u622c\u6230\u6231\u6232\u6237\u6238\u6239\u6240\u6241\u6242\u6243\u6244\u6245\u6246\u624d\u624e\u624f\u6250\u6251\u6252\u6253\u6258\u6259\u625a\u625b\u625c\u625d\u625e\u6265\u6266\u6267\u6268\u6269\u626a\u626b\u626c\u626d\u6274\u6275\u6276\u6277\u6278\u627c\u627d\u627e\u627f\u6280\u6281\u6284\u6285\u6286\u6287\u6288\u6289\u628a\u628d\u628e\u628f\u6294\u6295\u6296\u6299\u629d\u629f\u62a0\u62a2\u62a5\u62a6\u62a7\u62a8\u62a9\u62aa\u62ae\u62af\u62b0\u62b1\u62b4\u62b5\u62b6\u62b7\u62b8\u62ba\u62bb\u62bc\u62bd\u62bf\u62c0\u62c1\u62c3\u62c4\u62c5\u62c6' + '\u62c7\u62c8\u62cd\u62ce\u62cf\u62d0\u62d1\u62d2\u62d6\u62d7\u62d8\u62d9\u62dc\u62dd\u62de\u62df\u62e0\u62e1\u62e2\u62e3\u62e6\u62e7\u62e8\u62e9\u62ea\u62eb\u62ec\u62f3\u62f4\u62fa\u62fb\u62fc\u62fd\u62fe\u6303\u6304\u6305\u6306\u6307\u630a\u630b\u630c\u630d\u630e\u6312\u6313\u6314\u6316\u6317\u6318\u631c\u631e\u6320\u6322\u6323\u6324\u6327\u6328\u6329\u632a\u632c\u632d\u6331\u6332\u6333\u6334\u6335\u6337\u6338\u633a\u633b\u633c\u633d\u6343\u6344\u6345\u6346\u6347\u6348\u6349\u634a\u634b\u634c\u634d\u634e\u634f\u6350\u6351\u6352\u6354\u6355\u6356\u6358\u6359\u635a\u635b\u6361\u6362\u6364\u6365\u6366\u6367\u6368\u6369\u636a\u636b\u636c\u636e\u636f\u6370\u6371\u6379\u637a\u637b\u637c\u637d\u637e\u637f\u6380\u6385\u6386\u6387\u6388\u6389\u638a\u638b\u6391\u6398' + '\u6399\u639a\u639b\u639c\u639d\u639e\u639f\u63a0\u63a6\u63a7\u63a8\u63a9\u63ac\u63ae\u63af\u63b1\u63b2\u63b3\u63b4\u63b5\u63b7\u63b8\u63b9\u63ba\u63bb\u63bc\u63bd\u63be\u63bf\u63c0\u63c2\u63c3\u63c4\u63c5\u63c6\u63c7\u63c8\u63c9\u63ca\u63cb\u63cc\u63cd\u63ce\u63cf\u63d1\u63d2\u63d3\u63d4\u63d5\u63d6\u63d7\u63d8\u63dc\u63dd\u63df\u63e0\u63e1\u63e2\u63de\u63e4\u63e5\u40f0\u63e6\u63e7\u63e8\u63e9\u63ea\u63eb\u63ec\u63ed\u63ee\u63ef\u63f0\u63f1\u63f6\u63f7\u63f8\u63f9\u63fa\u63fb\u63fc\u63fd\u63fe\u63ff\u6400\u6401\u6402\u6403\u6404\u6405\u6406\u6407\u6408\u6409\u640a\u640b\u640c\u640d\u640e\u640f\u6410\u6414\u6415\u6416\u6417\u6418\u6419\u641a\u641b\u641c\u641d\u641e\u641f\u6420\u6421\u6422\u6423\u6424\u6425\u6426\u6427\u6428\u6429\u642a\u642b\u642c\u642d\u642e' + '\u642f\u6430\u6431\u6432\u6433\u6434\u6435\u6436\u6437\u6438\u6439\u643a\u643b\u643c\u643d\u643e\u643f\u6440\u6441\u6442\u6443\u6444\u6445\u6446\u6447\u6448\u6449\u644a\u644b\u644c\u644d\u644e\u644f\u6450\u6451\u6452\u6453\u6454\u6455\u6456\u6457\u6458\u6459\u6465\u6466\u6467\u6468\u6469\u646a\u646b\u646c\u646d\u646e\u646f\u6470\u6471\u6472\u6473\u6474\u6475\u6476\u6477\u6478\u6479\u647a\u647b\u647c\u647d\u647e\u647f\u6480\u6481\u6482\u6483\u6484\u6485\u6486\u6487\u6488\u6489\u648a\u648b\u648c\u648d\u648e\u648f\u6490\u6491\u6492\u6493\u64c2\u6494\u6495\u6496\u6497\u6498\u6499\u649a\u649b\u649c\u649d\u649e\u649f\u64a0\u64a1\u64a2\u64a3\u64a4\u64a5\u64a6\u64a7\u64a8\u64a9\u64aa\u64ab\u64ac\u64ad\u7396\u64ae\u64af\u64b0\u64b1\u64b2\u64b3\u64b4\u64b5\u64b6\u64c3' + '\u64c4\u64c5\u64c6\u64c7\u64c8\u64c9\u64ca\u64cb\u64cc\u64cd\u64ce\u64cf\u64d0\u6526\u64d1\u64d2\u64d3\u64d4\u64d5\u64d6\u64d7\u64d8\u6527\u64d9\u64da\u64db\u64dc\u64dd\u64de\u64df\u64e0\u64e1\u64e2\u64e3\u64e4\u64e5\u64e6\u64e7\u64e8\u64e9\u64ea\u64eb\u64ec\u64ed\u64ee\u64ef\u64f0\u64f1\u64f2\u64f3\u64f4\u64f5\u64f6\u64f7\u64f8\u64f9\u64fa\u64fb\u64fc\u64fd\u64fe\u64ff\u6500\u6501\u6502\u6503\u6504\u6505\u6506\u6507\u6508\u6509\u650a\u650b\u650c\u650d\u650e\u650f\u6510\u6511\u6512\u6513\u6514\u6515\u6516\u6517\u6528\u6529\u652a\u652b\u652c\u652d\u652e\u652f\u6530\u6531\u6532\u6533\u6534\u6535\u6536\u6537\u6538\u6539\u653a\u653b\u653c\u653d\u653e\u653f\u6540\u6541\u6542\u6543\u6544\u6545\u6546\u6547\u6548\u6549\u654a\u654b\u654c\u654d\u654e\u654f\u6550\u6551' + '\u6552\u6553\u6554\u6555\u6556\u6557\u6558\u6559\u655a\u655b\u655c\u655d\u655e\u655f\u6560\u6561\u6562\u6563\u6564\u6565\u6566\u6567\u6568\u6569\u656a\u658b\u656b\u656c\u656d\u656e\u656f\u6570\u6571\u6572\u6573\u6574\u6575\u6576\u6577\u6578\u6579\u657a\u658c\u658d\u658e\u658f\u6590\u6591\u6592\u6593\u6594\u6595\u6596\u6597\u6598\u6599\u659a\u659b\u659c\u659d\u659e\u659f\u65a0\u65a1\u65a2\u65a3\u65a4\u65a5\u65a6\u65a7\u65a8\u65a9\u65aa\u65ab\u65ac\u65ad\u65ae\u65af\u65b0\u65b1\u65b2\u65b3\u65b4\u65b5\u65b6\u65b7\u65b8\u65b9\u65ba\u65bb\u65bc\u65bd\u65be\u65bf\u65c0\u65c1\u65c2\u65c3\u65c4\u65c5\u65c6\u65c7\u65c8\u65c9\u65ca\u65cb\u65cc\u65cd\u65ce\u65cf\u65d0\u65d1\u65d2\u65d3\u65d4\u65d5\u65d6\u65d7\u65d8\u65d9\u65da\u65db\u65dc\u65dd\u65de\u65df\u65e0\u65e1' + '\u65e2\u65e3\u65e4\u6616\u65e5\u65e6\u65e7\u65e8\u65e9\u65ea\u65eb\u65ec\u65ed\u65ee\u65ef\u65f0\u65f1\u65f2\u65f3\u65f4\u65f5\u65f6\u65f7\u65f8\u6617\u6618\u65f9\u6619\u661a\u661b\u661c\u661d\u661e\u661f\u6620\u6621\u6622\u6623\u6624\u6625\u6626\u6627\u6628\u6629\u662a\u662b\u662c\u662d\u662e\u662f\u6630\u6631\u6632\u6633\u6634\u6635\u6636\u6637\u6638\u6639\u663a\u663b\u663c\u663d\u663e\u663f\u6640\u6641\u6642\u6643\u6644\u6645\u6646\u6647\u6648\u6649\u664a\u664b\u664c\u664d\u664e\u664f\u6650\u6651\u6652\u6653\u6654\u6655\u6656\u6657\u6658\u6659\u665a\u665b\u665c\u665d\u665e\u665f\u6660\u6661\u6662\u6663\u6664\u6665\u6666\u6667\u6668\u6669\u666a\u666b\u666c\u666d\u666e\u666f\u6670\u6671\u65fa\u6672\u6673\u6674\u6675\u6676\u6677\u6678\u6679\u667a\u667b\u667c' + '\u667d\u667e\u667f\u6680\u6681\u6682\u6683\u6684\u6685\u6686\u6687\u6688\u6689\u66a3\u66a4\u66a5\u66a6\u66a7\u66a8\u66a9\u66aa\u66ab\u66ac\u66ad\u66ae\u66af\u66b0\u66b1\u66b2\u66b3\u66b4\u66b5\u66b6\u66b7\u66b8\u66b9\u66ba\u66bb\u66bc\u66bd\u66be\u66bf\u66c0\u66c1\u66c2\u66c3\u66c4\u66c5\u66c6\u66c7\u66c8\u66c9\u66ca\u66cb\u66cc\u66cd\u66ce\u66cf\u66d0\u66d1\u66d2\u66d3\u66d4\u66d5\u66d6\u66d7\u66d8\u66d9\u66da\u66db\u66dc\u66dd\u66de\u66df\u66e0\u668a\u6715\u66e1\u66e2\u66e3\u66e4\u66e5\u66e6\u66e7\u66e8\u66e9\u66ea\u66eb\u66ec\u66ed\u6716\u66ee\u66ef\u66f0\u668b\u66f1\u66f2\u66f3\u66f4\u66f5\u66f6\u66f7\u66f8\u66f9\u66fa\u66fb\u66fc\u6717\u6718\u6719\u671a\u671b\u671c\u671d\u671e\u671f\u6720\u6721\u6722\u6723\u6724\u6725\u6726\u6727\u6728\u6729\u672a\u672b' + '\u672c\u672d\u672e\u672f\u6730\u6731\u6732\u6733\u6734\u6735\u6736\u6785\u6737\u6738\u6739\u673a\u673b\u673c\u673d\u66fd\u673e\u673f\u6740\u6741\u6742\u6743\u6744\u6745\u6746\u6747\u6748\u6749\u674a\u674b\u674c\u674d\u674e\u674f\u6750\u6751\u6752\u6753\u6754\u6755\u6756\u6757\u6758\u6759\u675a\u675b\u675c\u675d\u675e\u675f\u6760\u6761\u6762\u6763\u6764\u6765\u6766\u6786\u6767\u6768\u6769\u676a\u676b\u676c\u676d\u676e\u676f\u6770\u6771\u6772\u6773\u6774\u6775\u6776\u6787\u6788\u6789\u678a\u678b\u678c\u678d\u678e\u678f\u6790\u6791\u6792\u6793\u6794\u6795\u6796\u6797\u6798\u6799\u679a\u679b\u679c\u679d\u679e\u679f\u67a0\u67a1\u67a2\u67a3\u67a4\u67a5\u67a6\u67a7\u67a8\u67a9\u67aa\u67ab\u67ac\u67ad\u67ae\u67af\u67b0\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7\u67b8' + '\u6833\u67b9\u67ba\u67bb\u67bc\u67bd\u67be\u67bf\u67c0\u67c1\u67c2\u67c3\u67c4\u67c5\u67c6\u67c7\u67c8\u67c9\u67ca\u67cb\u67cc\u67cd\u67ce\u67cf\u67d0\u67ea\u67d1\u67d2\u67d3\u67d4\u67eb\u67ec\u67ed\u67ee\u67ef\u67f0\u67f1\u67f2\u67f3\u67f4\u67f5\u67f6\u67f7\u67f8\u67f9\u67fa\u67fb\u67fc\u67fd\u67fe\u67ff\u6800\u6801\u6802\u6803\u6804\u6805\u6806\u6807\u6808\u6809\u680a\u680b\u680c\u680d\u680e\u680f\u6810\u6811\u6812\u6813\u6814\u6815\u6816\u6817\u6818\u6819\u681a\u681b\u681c\u67d5\u681d\u681e\u681f\u6834\u6835\u6820\u6836\u6837\u6838\u6839\u683a\u683b\u683c\u683d\u683e\u683f\u6840\u6841\u6842\u6843\u6844\u6845\u6846\u6847\u6848\u6849\u684a\u684b\u684c\u684d\u684e\u684f\u6850\u6851\u6888\u6852\u6853\u6854\u6855\u6856\u6865\u6866\u6867\u6868\u6869\u686a\u686b' + '\u686c\u686d\u686e\u686f\u6870\u6871\u6872\u6873\u6874\u6875\u6876\u6877\u6878\u6879\u687a\u687b\u687c\u687d\u687e\u687f\u6880\u6889\u688a\u688b\u688c\u688d\u688e\u688f\u6890\u6891\u6892\u6893\u6894\u6895\u6896\u6897\u6898\u6899\u689a\u689b\u689c\u689d\u689e\u689f\u68a0\u68a1\u68a2\u68ab\u68ac\u68ad\u68ae\u68af\u68b0\u68b1\u68b2\u68b3\u68b4\u68b5\u68b6\u68b7\u68b8\u68b9\u68ba\u68bb\u68bc\u68bd\u68c4\u68c5\u68c6\u68c7\u68c8\u68c9\u68ca\u68cb\u68cc\u68cd\u68ce\u68cf\u68f3\u68d0\u68d1\u68d9\u68da\u68db\u68dc\u68dd\u68de\u68df\u68e0\u68e3\u68e4\u68e5\u68e6\u68ea\u68eb\u68ec\u68ed\u68ee\u68f1\u68f2\u68f4\u68f5\u68f6\u68f8\u68f9\u68fa\u68fb\u68fc\u68fd\u68fe\u68ff\u6900\u6901\u6902\u6903\u6904\u6908\u6909\u690a\u690b\u690c\u690d\u690e\u690f\u6910\u6911\u6912\u6913' + '\u6914\u6918\u6919\u691a\u691b\u691c\u691d\u6905\u691e\u691f\u6920\u6921\u6922\u6923\u6924\u6925\u6926\u6927\u6928\u6929\u692a\u692b\u692c\u692d\u692e\u692f\u6930\u6931\u6932\u6933\u6934\u693a\u693b\u693c\u693d\u693e\u693f\u6940\u6941\u6942\u6943\u6944\u6945\u6946\u6947\u6948\u6949\u694a\u694b\u694c\u694d\u694e\u694f\u6950\u6951\u6952\u6953\u6954\u6955\u6956\u6957\u6958\u6959\u695a\u695b\u695c\u695d\u695e\u6967\u6968\u6969\u696a\u696b\u696c\u696d\u696e\u696f\u6970\u6971\u6972\u6973\u6974\u6975\u6976\u6977\u6978\u6979\u697a\u697b\u697c\u697d\u697e\u697f\u6980\u6981\u6982\u6983\u6984\u6985\u6986\u6987\u6988\u6989\u698a\u698b\u698c\u698d\u698e\u698f\u6990\u6991\u6998\u6999\u699a\u699b\u699c\u699d\u699e\u699f\u69a0\u69a1\u69a2\u69a3\u69a4\u69a5\u69a6\u69a7\u69a8' + '\u69a9\u69aa\u69ab\u69ac\u69ad\u69ae\u69af\u69b0\u69b1\u69b2\u69b3\u69b4\u69b5\u69b6\u69b7\u69b8\u69b9\u69ba\u69bb\u69bc\u69ca\u69bd\u69be\u69bf\u69c0\u69cb\u69cc\u69cd\u69ce\u69cf\u69d0\u69d1\u69d2\u69d3\u69d4\u69d5\u69d6\u69d7\u69d8\u69d9\u69da\u69db\u69dc\u69dd\u69de\u69df\u69e0\u69e1\u69e2\u69e3\u69e4\u69e5\u69e6\u69e7\u69e8\u69e9\u69ea\u69eb\u69ec\u69ed\u69ee\u69ef\u69f0\u69f1\u69f2\u6a00\u6a01\u6a02\u6a03\u6a04\u6a05\u6a06\u6a07\u6a08\u6a09\u6a0a\u6a0b\u6a0c\u6a0d\u6a0e\u6a0f\u6a10\u6a11\u6a12\u6a13\u6a14\u6a15\u6a16\u6a17\u6a18\u6a19\u6a1a\u6a1b\u6a1c\u6a1d\u6a1e\u6a1f\u6a20\u6a21\u6a22\u6a23\u6a24\u6a25\u6a26\u6a27\u6a28\u6a29\u6a2a\u6a2b\u6a2c\u69f3\u6a2d\u6a2e\u6a2f\u6a30\u6a31\u6a32\u6a33\u6a34\u6a35\u6a36\u6a4a\u6a37\u6a4b\u6a4c\u6a4d\u6a4e\u6a4f' + '\u6a50\u6a51\u6a52\u6a53\u6a54\u6a55\u6a56\u6a57\u6a58\u6a59\u6a5a\u6a5b\u6a5c\u6a5d\u6a5e\u6a5f\u6a60\u6a61\u6a62\u6a63\u6a64\u6a65\u6a66\u6a67\u6a68\u6a69\u6a78\u6a79\u6a7a\u6a7b\u6a7c\u6a7d\u6a7e\u6a7f\u6a80\u6a81\u6a82\u6a83\u6a6a\u6a84\u6a85\u6a86\u6a87\u6a88\u6a89\u6a8a\u6a8b\u6a8c\u6a8d\u6a8e\u6a8f\u6a90\u6a91\u6a92\u6a9e\u6a9f\u6aa0\u6aa1\u6aa2\u6aa3\u6aa4\u6aa5\u6aa6\u6aa7\u6aa8\u6aa9\u6aaa\u6aab\u6aac\u6aad\u6aae\u6aaf\u6ab0\u6ab1\u6ab2\u6ab3\u6ab4\u6ab5\u6ab6\u6ab7\u6ab8\u6ab9\u6aba\u6abb\u6abc\u6abd\u6abe\u6abf\u6ac0\u6ac1\u6ac2\u6ac3\u6ac4\u6ac5\u6ac6\u6ad2\u6ad3\u6ad4\u6ad5\u6ad6\u6ad7\u6ad8\u6ad9\u6ada\u6adb\u6adc\u6add\u6ade\u6adf\u6ae0\u6ae1\u6ae2\u6ae3\u6ae4\u6ae5\u6ae6\u6ae7\u6ae8\u6ae9\u6aea\u6af2\u6af3\u6af4\u6af5\u6af6\u6af7\u6af8\u6af9' + '\u6afa\u6afb\u6afc\u6afd\u6b00\u6b01\u6b02\u6b03\u6b04\u6b05\u6b06\u6b0e\u6b07\u6b08\u6b09\u6b0f\u6b10\u6b11\u6b12\u6b13\u6b14\u6b0a\u6b15\u6b16\u6b17\u6b1c\u6b1d\u6b1e\u6b20\u6b21\u6b22\u6b23\u6b24\u6b26\u6b27\u6b2a\u6b2b\u6b2c\u6b2d\u6b2f\u6b30\u6b31\u6b32\u6b33\u6b34\u6b35\u6b38\u6b39\u6b3a\u6b3b\u6b3d\u6b3e\u6b41\u6b42\u6b43\u6b44\u6b45\u6b47\u6b4a\u6b4c\u6b4e\u6b4f\u6b50\u6b52\u6b53\u6b54\u6b56\u6b58\u6b59\u6b5a\u6b5b\u6b5c\u6b5d\u6b60\u6b61\u6b65\u6b66\u6b67\u6b68\u6b69\u6b6a\u6b6b\u6b6c\u6b6d\u6b6e\u6b70\u6b71\u6b72\u6b75\u6b77\u6b7a\u6b7b\u6b7c\u6b7d\u6b7e\u6b7f\u6b81\u6b82\u6b83\u6b84\u6b85\u6b88\u6b89\u6b8a\u6b8b\u6b8c\u6b8d\u6b8e\u6b8f\u6b95\u6b96\u6b97\u6b98\u6b99\u6b9a\u6b9b\u6b9c\u6b9d\u6ba3\u6ba4\u6ba5\u6ba6\u6ba7\u6bac\u6bad\u6bae\u6baf\u6bb0' + '\u6bba\u6bb1\u6bbb\u6bbc\u6bbd\u6bbe\u6bbf\u6bc0\u6bc1\u6bc2\u6bc3\u6bc4\u6bc8\u6bc9\u6bca\u6bcb\u6bcc\u6bcd\u6bd2\u6bd3\u6bd4\u6bd5\u6bd6\u6bd7\u6bdc\u6bdd\u6bde\u6bdf\u6be5\u6be6\u6be7\u6be8\u6bec\u6bf2\u6bf3\u6bf4\u6bf5\u6bf6\u6bf7\u6bf9\u6bfa\u6bfc\u6bfe\u6bff\u6c00\u6c01\u6c02\u6c03\u6c04\u6c05\u6c06\u6c07\u6c08\u6c09\u6c0a\u6c0b\u6c0c\u6c0d\u6c0e\u6c0f\u6c10\u6c11\u6c12\u6c13\u6c14\u6c15\u6c16\u6c17\u6c18\u6c19\u6c1a\u6c1b\u6c1c\u6c22\u6c23\u6c24\u6c25\u6c26\u6c27\u6c28\u6c29\u6c2a\u6c2b\u6c2c\u6c2d\u6c2e\u6c2f\u6c30\u6c31\u6c32\u6c33\u6c34\u6c35\u6c36\u6c37\u6c42\u6c43\u6c44\u6c45\u6c46\u6c47\u6c48\u6c49\u6c4a\u6c4b\u6c4c\u6c4d\u6c4e\u6c4f\u6c50\u6c51\u6c52\u6c53\u6c54\u6c55\u6c5c\u6c5d\u6c5e\u6c5f\u6c60\u6c61\u6c62\u6c63\u6c64\u6c65\u6c66\u6c67\u6c68' + '\u6c69\u6c6a\u6c6b\u6c6c\u6c75\u6c76\u6c77\u6c78\u6c79\u6c7a\u6c7b\u6c7c\u6c7d\u6c7e\u6c7f\u6c80\u6c81\u6c82\u6c83\u6c84\u6c85\u6c86\u6c87\u6c88\u6c89\u6c8a\u6c8b\u6c8c\u6c8d\u6c8e\u6c8f\u6c90\u6c91\u6c92\u6c9d\u6c9e\u6c9f\u6ca0\u6ca1\u6ca2\u6ca3\u6ca4\u6c93\u6c94\u6ca5\u6ca6\u6ca7\u6ca8\u6ca9\u6caa\u6cab\u6cac\u6cad\u6cae\u6caf\u6cb0\u6cb1\u6cb2\u6cba\u6cbb\u6cbc\u6cbd\u6cbe\u6cbf\u6cc0\u6cb3\u6cc1\u6cc2\u6cd0\u6cc3\u6cc4\u6cc5\u6cc6\u6cc7\u6cc8\u6ce1\u6cd1\u6cd2\u6cd3\u6cd4\u6cd5\u6cd6\u6cd7\u6cd8\u6cd9\u6cda\u6ce2\u6ce3\u6ce4\u6ce5\u6ce6\u6ce7\u6ce8\u6ce9\u6cea\u6ceb\u6cec\u6ced\u6cee\u6cef\u6cf0\u6cf7\u6cf8\u6cf9\u6cfa\u6cfb\u6cfc\u6cfd\u6cfe\u6cff\u6d03\u6d04\u6d05\u6d06\u6d07\u6d08\u6d09\u6d0d\u6d0e\u6d0f\u6d10\u6d12\u6d13\u6d14\u6d17\u6d18\u6d1d\u6d1e' + '\u6d1f\u6d20\u6d21\u6d22\u6d24\u6d25\u6d26\u6d27\u6d28\u6d29\u6d2b\u6d2a\u6d2c\u6d2d\u6d2e\u6d2f\u6d30\u6d31\u6d36\u6d37\u6d38\u6d39\u6d3a\u6d3b\u6d3c\u6d3d\u6d3e\u6d3f\u6d44\u6d45\u6d46\u6d47\u6d48\u6d49\u6d4a\u6d4b\u6d4c\u6d4d\u6d4e\u6d4f\u6d50\u6d51\u6d52\u6d53\u6d54\u6d55\u6d56\u6d57\u6d58\u6d59\u6d62\u6d63\u6d64\u6d65\u6d66\u6d67\u6d68\u6d69\u6d6a\u6d6b\u6d6c\u6d6d\u6d6e\u6d6f\u6d70\u6d71\u6d72\u6d73\u6d74\u6d75\u6d76\u6d77\u6d78\u6d79\u6d7a\u6d7b\u6d7c\u6d7d\u6d7e\u6d7f\u6d80\u6d81\u6d8a\u6d8b\u6d8c\u6d8d\u6d8e\u6d8f\u6d90\u6d91\u6d92\u6d93\u6d94\u6d95\u6d96\u6d97\u6d98\u6d99\u6d9a\u6d9b\u6d9c\u6d9d\u6d9e\u6d9f\u6da0\u6da1\u6da2\u6da3\u6da4\u6da5\u6da6\u6da7\u6da8\u6da9\u6daa\u6db6\u6db7\u6db8\u6db9\u6ddd\u6dba\u6dbb\u6dbc\u6dbd\u6dbe\u6dbf\u6dc0\u6dc1' + '\u6dc2\u6dc3\u6dc4\u6dc5\u6dc6\u6dc7\u6dc8\u6dc9\u6dca\u6dab\u6dcb\u6dcc\u6dcd\u6dce\u6dcf\u6dd0\u6dd1\u6dd2\u6dd3\u6dd4\u6dde\u6ddf\u6de0\u6de1\u6de2\u6de3\u6de4\u6de5\u6de6\u6de7\u6e12\u6de8\u6de9\u6dea\u6deb\u6dec\u6ded\u6dee\u6def\u6df0\u6df1\u6df2\u6df3\u6df4\u6df5\u6df6\u6df7\u6df8\u6df9\u6dfa\u6dfb\u6dfc\u6dfd\u6dfe\u6dff\u6e00\u6e01\u6e02\u6e03\u6e04\u6e05\u6e13\u6e14\u6e15\u6e16\u6e17\u6e18\u6e19\u6e1a\u6e1b\u6e1c\u6e1d\u6e1e\u6e1f\u6e20\u6e21\u6e22\u6e23\u6e24\u6e25\u6e26\u6e27\u6e28\u6e29\u6e2a\u6e2b\u6e2c\u6e2d\u6e2e\u6e2f\u6e30\u6e31\u6e32\u6e33\u6e34\u6e35\u6e40\u6e41\u6e42\u6e43\u6e44\u6e45\u6e46\u6e47\u6e48\u6e49\u6e4a\u6e4b\u6e4c\u6e4d\u6e4e\u6e4f\u6e50\u6e51\u6e52\u6e53\u6e54\u6e55\u6e56\u6e57\u6e58\u6e5d\u6e5e\u6e5f\u6e60\u6e61\u6e62\u6e63' + '\u6e64\u6e65\u6e66\u6e67\u6e68\u6e69\u6e6a\u6e6b\u6e6c\u6e6d\u6e6e\u6e6f\u6e70\u6e7d\u6e71\u6e7e\u6e7f\u6e80\u6e81\u6e72\u6e82\u6e83\u6e84\u6e85\u6e86\u6e87\u6e88\u6e89\u6e8a\u6e8b\u6e8c\u6e8d\u6e8e\u6e8f\u6e9a\u6e90\u6e91\u6e9b\u6e9c\u6e9d\u6e9e\u6e9f\u6ea0\u6ea1\u6ea2\u6ea3\u6ea4\u6ea5\u6ea6\u6ea7\u6ea8\u6ea9\u6eaa\u6eb1\u6eb2\u6eb3\u6eb4\u6eb5\u6eb6\u6eb7\u6eb8\u6eb9\u6eba\u6ebb\u6ebc\u6ebd\u6ebe\u6ebf\u6ec0\u6ec1\u6ec2\u6ec3\u6ec4\u6ec5\u6ec6\u6ec7\u6ecc\u6ecd\u6ece\u6ecf\u6ed0\u6ed1\u6ed2\u6ed4\u6ed5\u6ed6\u6ed7\u6ed8\u6ed9\u6eda\u6edb\u6edd\u6ede\u6edf\u6ee2\u6ee3\u6ee6\u6eea\u6ef0\u6ef2\u6ef3\u6ef4\u6ef6\u6ef7\u6ef9\u6efa\u6efd\u6efe\u6eff\u6f00\u6f01\u6f02\u6f03\u6f04\u6f05\u6f08\u6f09\u6f0a\u6f10\u6f11\u6f12\u6f19\u6f1a\u6f1b\u6f1c\u6f21\u6f22\u6f23' + '\u6f24\u6f25\u6f2d\u6f2e\u6f2f\u6f30\u6f31\u6f38\u6f39\u6f3a\u6f3e\u6f3f\u6f40\u6f41\u6f42\u6f47\u6f48\u6f49\u6f4a\u6f52\u6f53\u6f54\u6f57\u6f5a\u6f5e\u6f5f\u6f60\u6f62\u6f63\u6f64\u6f65\u6f6a\u6f6b\u6f6c\u6f6d\u6f6e\u6f6f\u6f70\u6f71\u6f73\u6f76\u6f77\u6f78\u6f79\u6f7a\u6f7b\u6f7c\u6f7d\u6f7e\u6f7f\u6f80\u6f81\u6f82\u6f83\u6f84\u6f85\u6f86\u6f87\u6f88\u6f8d\u6f89\u6f8e\u6f8f\u6f90\u6f91\u6f92\u6f93\u6f94\u6f95\u6f96\u6f97\u6f98\u6f99\u6f9a\u6f9b\u6f9c\u6f9d\u6f9e\u6f9f\u6fa0\u6fa1\u6fa2\u6fa3\u6fa4\u6fa8\u6fa9\u6faa\u6fab\u6fac\u6fad\u6fae\u6faf\u6fb0\u6fb1\u6fb2\u6fb3\u715f\u6fb6\u6fb7\u6fb8\u6fb9\u6fba\u6fbb\u6fbc\u7a11\u6fbd\u6fc1\u6fc2\u6fc3\u6fc4\u6fc5\u6fc6\u6fbe\u6fd6\u6fc7\u6fc8\u6fc9\u6fcc\u6fcd\u6fce\u6fcf\u6fd0\u6fd1\u6fd7\u6fd8\u6fd9\u6fdb\u6fdc' + '\u6fe0\u6fe1\u6fe2\u6fe4\u6fe5\u6fe7\u6fea\u6feb\u6fed\u6fef\u6ff0\u6ff2\u6ff3\u6ff4\u6ff5\u6ff6\u6ff8\u6ff9\u6ffa\u6ffb\u6ffc\u6ffd\u6ffe\u6fff\u7002\u7003\u7004\u7005\u7006\u7007\u7008\u7009\u700a\u700f\u7010\u7011\u7012\u7013\u7014\u7015\u7016\u7017\u7018\u7019\u701a\u701b\u701c\u701d\u7025\u7026\u7027\u7028\u7029\u702a\u702b\u702c\u702d\u702e\u701e\u702f\u7030\u7031\u7032\u7033\u7034\u7035\u7036\u7037\u7038\u7039\u703a\u703b\u703c\u703d\u704b\u704c\u704d\u704e\u704f\u7050\u7051\u7052\u7053\u7054\u7055\u7056\u7057\u7061\u7062\u7063\u7064\u7065\u7066\u7067\u7068\u7069\u706a\u706b\u706c\u706d\u706e\u706f\u7070\u7071\u7072\u7073\u7074\u7075\u7085\u7086\u7087\u7088\u7089\u708a\u708b\u708c\u708d\u708e\u708f\u7090\u7091\u7092\u7093\u7094\u7095\u7096\u7097\u7098' + '\u7099\u709a\u709b\u709c\u709d\u709e\u709f\u70a8\u70a9\u70aa\u70ab\u70ac\u70ad\u70ae\u70af\u70b0\u70b1\u70b2\u70b3\u70b4\u70b5\u70b6\u70b7\u70b8\u70b9\u70c8\u70c9\u70ca\u70cb\u70cc\u70cd\u70ba\u70ce\u70cf\u70d0\u70d1\u70d2\u70d3\u70d4\u70bb\u70d5\u70d6\u70d7\u70d8\u70e5\u70e6\u70e7\u70e8\u70e9\u70ea\u70eb\u70ec\u70ed\u70ee\u70ef\u70f0\u70f1\u70f2\u70f3\u70f4\u70f5\u70f6\u70f7\u70f8\u7101\u7102\u7103\u7104\u7105\u7106\u7107\u7108\u7109\u710a\u70f9\u710b\u710c\u710d\u710e\u7111\u7112\u7113\u7114\u7115\u7116\u7117\u7118\u7119\u711a\u711b\u711c\u711d\u711e\u711f\u7120\u7127\u7128\u7129\u712e\u712a\u712f\u7130\u7131\u7133\u7134\u7135\u7136\u7137\u713b\u713c\u713d\u713f\u7140\u7142\u7143\u7146\u7147\u7149\u714a\u714b\u714c\u714d\u714e\u714f\u7150\u7151\u7152\u7153' + '\u7154\u7155\u7156\u7157\u7159\u715a\u715b\u715c\u715d\u7160\u7161\u7162\u7163\u7164\u7166\u7167\u7168\u7169\u716c\u716d\u716e\u716f\u7170\u7171\u7172\u7173\u7177\u7178\u7179\u717a\u717b\u717d\u717e\u717f\u7181\u7182\u7183\u7184\u7185\u7186\u7187\u7188\u718a\u718b\u718c\u718e\u7190\u7191\u7195\u7196\u7199\u719c\u719d\u719f\u71a3\u71a4\u71a5\u71a6\u71a9\u71ac\u71ae\u71af\u71b1\u71b3\u71b4\u71b5\u71b6\u71b8\u71b9\u71ba\u71bb\u71bc\u71bd\u71c0\u71c1\u71c2\u71c3\u71c4\u71c5\u71c6\u71c7\u71c8\u71cc\u71cd\u71ce\u71cf\u71d0\u71d1\u71d2\u71d6\u71d7\u71d3\u71d8\u71da\u71db\u71dc\u71df\u71e0\u71e1\u71e2\u71e4\u71e5\u71e6\u71e7\u71ea\u71eb\u71ec\u71ee\u71ef\u71f1\u71f5\u71f6\u71f9\u71fa\u71fb\u71fc\u71fd\u71fe\u7204\u7205\u7206\u7207\u7208\u7209\u720a\u720b\u720c\u720d' + '\u720e\u720f\u7210\u7211\u7212\u7213\u7214\u7215\u7216\u7217\u7218\u7219\u721a\u721b\u7226\u7227\u7228\u7229\u722a\u722b\u722c\u722d\u722e\u722f\u7230\u7231\u7232\u7233\u7234\u7235\u7236\u7237\u7238\u7239\u723a\u723b\u723c\u723d\u723e\u723f\u7240\u7241\u7242\u7243\u7244\u724e\u724f\u7250\u7251\u7252\u7253\u7254\u7255\u7256\u7257\u7258\u7259\u725a\u725b\u725c\u725d\u725e\u725f\u7245\u7269\u7260\u726a\u726b\u726c\u726d\u726e\u726f\u7270\u7271\u7272\u7273\u7274\u7275\u7276\u7277\u7278\u7279\u728d\u728e\u728f\u7290\u7291\u7292\u7293\u7294\u7295\u7296\u7297\u7298\u7299\u729a\u729b\u72b1\u729c\u729d\u729e\u729f\u72a0\u72a1\u72a2\u72a3\u72a4\u72a5\u72a6\u72a7\u72b2\u72b3\u72b4\u72b5\u72b6\u72b7\u72b8\u72b9\u72ba\u72bb\u72bc\u72bd\u72be\u72bf\u72c0\u72c1\u72c2\u72c3' + '\u72c4\u72c5\u72c6\u72c7\u72c8\u72c9\u72ca\u72d8\u72d9\u72da\u72db\u72dc\u72dd\u72de\u72df\u72e0\u72e1\u72e2\u72e3\u72e4\u72f0\u72f1\u72f2\u72f3\u72f4\u72f5\u72f6\u72f7\u72f8\u72f9\u72fa\u72fb\u72fc\u72fd\u72fe\u72ff\u7309\u730a\u730b\u730c\u730d\u730e\u730f\u7310\u7311\u7312\u7313\u7314\u7315\u7316\u7317\u7318\u7319\u731a\u731b\u731c\u7326\u7327\u7328\u7329\u732a\u732b\u732c\u732d\u732e\u732f\u7330\u7338\u7339\u733a\u733b\u733c\u733d\u733e\u733f\u7340\u7341\u7342\u7343\u7344\u7349\u734a\u734b\u734c\u7350\u7353\u7358\u7354\u7355\u7356\u7359\u735b\u735d\u735f\u7361\u7362\u7363\u736a\u736b\u736c\u736d\u736e\u736f\u7370\u7371\u7372\u7375\u7376\u7379\u737a\u737c\u737f\u7380\u7381\u7382\u7383\u7385\u7386\u7387\u7389\u738a\u738b\u738c\u738d\u738e\u738f\u7390\u7391' + '\u7392\u7393\u7397\u7398\u7399\u739a\u739b\u739c\u739d\u739e\u739f\u73a0\u73a1\u73a2\u73a3\u73a4\u73a5\u73a6\u73a7\u73a8\u73a9\u73aa\u73ab\u73ac\u73ad\u73ae\u73af\u73b0\u73b1\u73b7\u73b8\u73b9\u73ba\u73bb\u73bc\u73bd\u73be\u73bf\u73c0\u73c1\u73c2\u73c3\u73c4\u73c5\u73c6\u73c7\u73c8\u73c9\u73ca\u73cb\u73cc\u73cd\u73ce\u73cf\u73d0\u73d1\u73d2\u73d3\u73d4\u73d5\u73d6\u73d7\u73d8\u73d9\u73da\u73db\u73e7\u73e8\u73e9\u73ea\u73eb\u73ec\u73ed\u73ee\u73ef\u73f0\u73f1\u73f2\u73f3\u73f4\u73f5\u73f6\u73f7\u73f8\u73f9\u73fa\u73fb\u73fc\u73fd\u73fe\u73ff\u7400\u7409\u740a\u740b\u740c\u740d\u740e\u740f\u7410\u7411\u7412\u7413\u7414\u7415\u7416\u7417\u7418\u7419\u741a\u741b\u741c\u741d\u741e\u741f\u7420\u7421\u7422\u7423\u7424\u742c\u742d\u742e\u742f\u7430\u7431\u7462\u7432' + '\u7433\u7434\u7435\u7436\u7437\u7438\u7439\u743a\u743b\u743c\u743d\u743e\u743f\u7440\u7441\u7442\u7443\u7444\u7445\u7446\u7447\u7448\u7449\u744a\u744b\u744c\u744d\u744e\u744f\u7463\u7464\u7465\u7466\u7467\u7468\u7469\u746a\u746b\u746c\u746d\u746e\u746f\u7470\u7471\u7472\u7473\u7474\u7475\u7450\u7476\u7477\u7478\u7479\u747a\u747b\u747c\u747d\u747e\u7489\u748a\u748b\u748c\u748d\u748e\u748f\u747f\u7490\u7491\u7492\u7493\u7494\u7495\u7496\u7497\u7498\u7499\u749a\u749b\u749c\u749d\u749e\u749f\u74a0\u74a1\u74a2\u74a3\u74a4\u74a5\u74a6\u74a7\u74b1\u74b2\u74a8\u74b3\u74b4\u74b5\u74b6\u74b7\u74b8\u74b9\u74a9\u74ba\u74bb\u74bc\u74bd\u74be\u74bf\u74c0\u74c1\u74c2\u74c3\u74cc\u74cd\u74ce\u74cf\u74d0\u74d1\u74d2\u74d3\u74d4\u74d5\u74d6\u74d7\u74d8\u74d9\u74da\u74db\u74dc' + '\u74dd\u74de\u74df\u74e0\u74e1\u74e2\u74ec\u74ed\u74ee\u74ef\u74f0\u74f1\u74f2\u74f3\u74f4\u74f5\u74f6\u74f7\u74f8\u74f9\u74fa\u74fb\u74fc\u7504\u7505\u7506\u7507\u7508\u7509\u750a\u750b\u750c\u750d\u750e\u7510\u7511\u7512\u7513\u7514\u7515\u7516\u7517\u7518\u7519\u751a\u751e\u751f\u7520\u7521\u7522\u7523\u7524\u7525\u752a\u752d\u752e\u752f\u7531\u7533\u7534\u7535\u7536\u7538\u7539\u753a\u753b\u753c\u753d\u753e\u753f\u7540\u7541\u7542\u7543\u7544\u7545\u7546\u7547\u7548\u754f\u7550\u7551\u7552\u7553\u7554\u7555\u7556\u7557\u7558\u7559\u755a\u755b\u755c\u755d\u755e\u755f\u7560\u7561\u7562\u7566\u7567\u7568\u7575\u7569\u756a\u756b\u756c\u756d\u756e\u756f\u7570\u7571\u7576\u7577\u7578\u7579\u757a\u757b\u757c\u757d\u757e\u7585\u7586\u7587\u7588\u7589\u758a\u758b' + '\u758c\u758d\u758e\u758f\u757f\u7590\u7591\u759a\u759b\u759c\u759d\u759e\u759f\u75a0\u75a1\u75a2\u75a3\u75a4\u75a5\u75a6\u75a7\u75a8\u75a9\u75aa\u75ab\u75ac\u75b2\u75b3\u75b4\u75b5\u75b6\u75b7\u75b8\u75b9\u75ba\u75bb\u75be\u75bf\u75c0\u75c6\u75c7\u75c8\u75c9\u75ca\u75cc\u75cd\u75ce\u75cf\u75d3\u75d4\u75d6\u75d9\u75da\u75db\u75dc\u75dd\u75df\u75e0\u75e1\u75e2\u75e3\u75e4\u75e6\u75e7\u75e8\u75e9\u75ea\u75eb\u75ee\u75ef\u75f0\u75f1\u75f2\u75f3\u75f4\u75fb\u75fc\u75fd\u75fe\u75ff\u7600\u7601\u7602\u7603\u7604\u7605\u7606\u7607\u7610\u7611\u7612\u7613\u7614\u7615\u7616\u7617\u7618\u7619\u761a\u761b\u761c\u761d\u761e\u761f\u7620\u7621\u7622\u7623\u7624\u7625\u7626\u7627\u7628\u762d\u762e\u762f\u7630\u7631\u7632\u7633\u7634\u7635\u7636\u7637\u7638\u7639\u763a\u764d' + '\u764e\u764f\u7650\u7651\u7652\u7653\u763b\u7654\u7655\u7656\u7657\u7658\u7659\u765a\u765b\u7666\u7667\u7668\u7669\u766a\u766b\u766c\u766d\u766e\u766f\u7670\u7671\u7672\u7673\u7674\u7675\u7676\u7677\u7678\u7679\u767a\u767b\u767c\u7689\u768a\u768b\u768c\u768d\u768e\u768f\u7690\u7691\u7692\u7693\u7694\u7695\u7696\u76a4\u76a5\u76a6\u76a7\u76a8\u76a9\u76aa\u76ab\u76ac\u76ad\u76ae\u76af\u76b0\u76b1\u76b2\u76b3\u76bf\u76c0\u76c1\u76c2\u76c3\u76d3\u7697\u76c4\u76c5\u76c6\u76c7\u76c8\u76c9\u76ca\u76cb\u76d4\u76d5\u76d6\u76d7\u76d8\u76d9\u76da\u76db\u76dc\u76dd\u76de\u76df\u76e3\u76e4\u76e5\u76e6\u76e7\u76ef\u76f0\u76f1\u76f2\u76f3\u76f4\u76f5\u76f6\u76f7\u76f8\u76f9\u76fa\u7702\u7706\u7707\u7703\u7709\u770a\u770b\u770c\u770e\u770f\u7710\u7711\u7713\u7714\u7715\u7716' + '\u7717\u7718\u7719\u771a\u771f\u7720\u7721\u7722\u7723\u7724\u7725\u7726\u7727\u7728\u7729\u772d\u772e\u772f\u7730\u7731\u7732\u7733\u773b\u773c\u773d\u773e\u773f\u7740\u7741\u7742\u7748\u7749\u774a\u774b\u774c\u774d\u774e\u774f\u7750\u7751\u7756\u7757\u7758\u7759\u775a\u775b\u775e\u775f\u7760\u7761\u7762\u7763\u7764\u7768\u7769\u776a\u776b\u776c\u776d\u776e\u776f\u7770\u7777\u7778\u7779\u777a\u777b\u777c\u7784\u7785\u7788\u778b\u778d\u778e\u778f\u7791\u7793\u7794\u7795\u7796\u7799\u779a\u779b\u779c\u779d\u779e\u779f\u77a0\u77a3\u77a4\u77a5\u77a6\u77a7\u77a8\u77a9\u9dc4\u9dc5\u77ae\u77b0\u77b1\u77b2\u77b3\u77b4\u77b5\u77b8\u77b9\u77ba\u77bb\u77c0\u77c1\u77c4\u77c2\u77c3\u77c5\u77c7\u77c8\u77c9\u77cb\u77ce\u77cf\u77d0\u77d1\u77d2\u77d3\u77d4\u77d5\u77d6\u77d7' + '\u77d8\u77d9\u77da\u77db\u7827\u77dd\u77de\u77df\u77e0\u77e1\u77e2\u77e3\u77e4\u77e5\u77e6\u77e7\u77e8\u77e9\u77ea\u77eb\u77ec\u77ed\u77f5\u77f6\u77f7\u77f8\u77f9\u77fa\u77fb\u77fc\u77fd\u77fe\u77ff\u7800\u7801\u7802\u7803\u7804\u7805\u7806\u7807\u7808\u7809\u780a\u780b\u780c\u780d\u780e\u780f\u7810\u7811\u7812\u7813\u7814\u7815\u7816\u7817\u7818\u7819\u781a\u781b\u781c\u781d\u7828\u7829\u782a\u782b\u782c\u782d\u782e\u782f\u7830\u7831\u7832\u7833\u7834\u7835\u7836\u7837\u7838\u7839\u783a\u783b\u783c\u783d\u783e\u783f\u7840\u7841\u7842\u7843\u7844\u7845\u7846\u7855\u7856\u7857\u7858\u7859\u785a\u785b\u785c\u785d\u785e\u785f\u7860\u7861\u7862\u7863\u7864\u7865\u7866\u7867\u7868\u7869\u786a\u786b\u786c\u786d\u786e\u786f\u7870\u7871\u7872\u7873\u7874\u7875\u7876' + '\u7877\u7885\u7886\u7887\u7888\u7889\u788a\u788b\u788c\u788d\u788e\u788f\u7890\u7891\u7892\u7893\u7894\u7895\u7896\u7897\u7898\u7899\u789a\u789b\u789c\u789d\u789e\u789f\u78a0\u78a1\u78a2\u78a3\u78a4\u78a5\u78a6\u78a7\u78a8\u78a9\u78aa\u78ab\u78ac\u78ad\u78ae\u78af\u78bd\u78be\u78bf\u78c0\u78c1\u78c2\u78c3\u78c4\u78c5\u78c6\u78c7\u78c8\u78c9\u78ca\u78cb\u78cc\u78cd\u78ce\u78cf\u78d0\u7878\u78d1\u78d2\u78d3\u78d4\u78d5\u78d6\u78d7\u78d8\u78ff\u78d9\u78da\u78db\u78dc\u78dd\u78de\u78df\u78e0\u78e1\u78e2\u7900\u7901\u7902\u7903\u7904\u7905\u7906\u7907\u7908\u7909\u790a\u790b\u790c\u790d\u790e\u790f\u7910\u7911\u7912\u7913\u7914\u7915\u7916\u7917\u7918\u7919\u791a\u791b\u792c\u792d\u792e\u792f\u7930\u7931\u7932\u7933\u7934\u7935\u7936\u7937\u7938\u7939\u793a\u793b' + '\u793c\u793d\u793e\u793f\u7940\u7941\u7942\u7943\u7944\u7945\u7946\u7947\u7948\u7949\u794a\u794b\u794c\u791c\u794d\u794e\u794f\u791d\u7950\u7951\u7962\u7963\u7964\u7965\u7966\u7967\u7968\u7969\u796a\u796b\u796c\u796d\u796e\u796f\u7970\u7971\u7972\u7973\u7974\u7975\u7976\u7977\u7978\u7979\u797a\u797b\u797c\u7990\u7991\u7992\u7993\u7994\u7995\u7996\u7997\u7998\u7999\u799a\u799b\u799c\u799d\u799e\u799f\u79aa\u79ab\u79ac\u79ad\u79ae\u79af\u79b0\u79b1\u79b2\u79b3\u79b4\u79b5\u79b6\u79ba\u79bb\u79bc\u79bd\u79be\u79b7\u79b8\u79c7\u79c8\u79c9\u79ca\u79cb\u79cc\u79cd\u79ce\u79cf\u79d0\u79d1\u79d6\u79d7\u79d8\u79d9\u79da\u79db\u79dc\u79e2\u79e0\u79e3\u79e4\u79e5\u79e6\u79e7\u79e8\u79eb\u79ed\u79ee\u79ef\u79f0\u79f1\u79f3\u79f4\u79f5\u79f6\u79f7\u79f8\u79f9\u79fa\u79fb' + '\u79fc\u79fd\u79fe\u7a00\u7a01\u7a02\u7a03\u7a04\u7a05\u7a06\u7a07\u7a08\u7a09\u7a0a\u7a0b\u7a0c\u7a0d\u7a0e\u7a12\u7a13\u7a14\u7a15\u7a16\u7a17\u7a18\u7a19\u7a1a\u7a1b\u7a1c\u7a1d\u7a23\u7a24\u7a25\u7a26\u7a27\u7a1e\u7a28\u7a29\u7a2a\u7a2b\u7a2c\u7a2d\u7a2e\u7a2f\u7a30\u7a31\u7a38\u7a39\u7a3a\u7a3b\u7a3c\u7a3d\u7a3e\u7a3f\u7a47\u7a48\u7a49\u7a4a\u7a4b\u7a4c\u7a4d\u7a4e\u7a4f\u7a50\u7a40\u7a51\u7a58\u7a59\u7a5a\u7a5b\u7a5c\u7a5d\u7a5e\u7a5f\u7a60\u7a61\u7a62\u7a63\u7a64\u7a6c\u7a6d\u7a6e\u7a6f\u7a70\u7a71\u7a72\u7a73\u7a74\u7a75\u7a7a\u7a7b\u7a7c\u7a7d\u7a7e\u7a7f\u7a80\u7a81\u7a82\u7a83\u7a8c\u7a8d\u7a8e\u7a8f\u7a90\u7a84\u7a98\u7a99\u7a9a\u7a9b\u7a9c\u7aa0\u7aa1\u7aa2\u7aa7\u7aa5\u7aa8\u7aa9\u7aab\u7aae\u7ab1\u7ab2\u7ab3\u7ab4\u7ab5\u7ab6\u7ab7\u7ab8\u7ab9' + '\u7abb\u7abc\u7abd\u7abe\u7abf\u7ac0\u7ac1\u7ac2\u7ac3\u7ac4\u7ac9\u7aca\u7acb\u7acc\u7acd\u7ace\u7acf\u7ad0\u7ad1\u7ad2\u7ad3\u7ad4\u7ad5\u7ad6\u7ad7\u7ad8\u7ad9\u7ada\u7adb\u7adc\u7add\u7ade\u7adf\u7ae0\u7ae1\u7ae2\u7ae3\u7ae4\u7ae5\u7ae6\u7af5\u7af6\u7af7\u7af8\u7af9\u7afa\u7afb\u7afc\u7afd\u7afe\u7aff\u7b00\u7b01\u7b02\u7b03\u7b04\u7b05\u7b06\u7b07\u7b08\u7b09\u7b0a\u7b0b\u7b0c\u7b0d\u7b0e\u7b0f\u7b10\u7b11\u7b12\u7b13\u7b14\u7b15\u7b16\u7b17\u7b18\u7b19\u7b27\u7b28\u7b29\u7b2a\u7b2b\u7b2c\u7b2d\u7b2e\u7b2f\u7b30\u7b31\u7b32\u7b33\u7b34\u7b5b\u7b35\u7b36\u7b37\u7b38\u7b39\u7b3a\u7b3b\u7b3c\u7b3d\u7b3e\u7b3f\u7b40\u7b41\u7b42\u7b43\u7b44\u7b45\u7b46\u7b47\u7b48\u7b49\u7b4a\u7b4b\u7b4c\u7b4d\u7b4e\u7b4f\u7b50\u7b5c\u7b5d\u7b5e\u7b5f\u7b60\u7b61\u7b62\u7b63' + '\u7b64\u7b65\u7b66\u7b67\u7b68\u7b69\u7b6a\u7b6b\u7b6c\u7b6d\u7b6e\u7b6f\u7b70\u7b71\u7b72\u7b73\u7b74\u7b75\u7b76\u7b77\u7b78\u7b79\u7b7a\u7b7b\u7b7c\u7b7d\u7b7e\u7b7f\u7b89\u7b8a\u7b8b\u7b8c\u7b8d\u7b8e\u7b8f\u7b90\u7b91\u7b92\u7b93\u7b94\u7b95\u7b96\u7b97\u7b98\u7b99\u7b9a\u7b9b\u7b9c\u7b9d\u7b9e\u7b9f\u7ba0\u7ba1\u7ba2\u7ba3\u7ba4\u7ba5\u7ba6\u7ba7\u7ba8\u7ba9\u7baa\u7bab\u7bac\u7bad\u7bae\u7baf\u7bb0\u7bb1\u7bb2\u7bb3\u7bb4\u7bb5\u7bb6\u7bb7\u7bb8\u7bb9\u7bba\u7bbb\u7bbc\u7b80\u7bbd\u7bbe\u7bcd\u7bbf\u7bc0\u7bce\u7bcf\u7bd0\u7bd1\u7bd2\u7bd3\u7bd4\u7bd5\u7bd6\u7bd7\u7bd8\u7bd9\u7bda\u7bdb\u7bdc\u7bdd\u7bde\u7bdf\u7be0\u7be1\u7be2\u7be3\u7be4\u7be5\u7be6\u7be7\u7be8\u7be9\u7bea\u7beb\u7bec\u7bed\u7bee\u7bef\u7bf0\u7bf1\u7bf2\u7bf3\u7bf4\u7bf5\u7bf6\u7bf7' + '\u7bf8\u7bf9\u7bfa\u7bfb\u7bfc\u7bfd\u7bfe\u7bff\u7c12\u7c13\u7c14\u7c15\u7c16\u7c17\u7c18\u7c19\u7c1a\u7c1b\u7c1c\u7c1d\u7c1e\u7c1f\u7c20\u7c21\u7c22\u7c23\u7c24\u7c25\u7c26\u7c27\u7c28\u7c29\u7c2a\u7c2b\u7c2c\u7c2d\u7c2e\u7c2f\u7c30\u7c31\u7c32\u7c3a\u7c3b\u7c3c\u7c3d\u7c3e\u7c3f\u7c40\u7c41\u7c42\u7c43\u7c44\u7c45\u7c46\u7c47\u7c48\u7c49\u7c4a\u7c4b\u7c4c\u7c4d\u7c4e\u7c4f\u7c50\u7c51\u7c52\u7c53\u7c54\u7c55\u7c56\u7c57\u7c58\u7c67\u7c59\u7c5a\u7c88\u7c5b\u7c5c\u7c68\u7c69\u7c6a\u7c6b\u7c6c\u7c6d\u7c6e\u7c6f\u7c70\u7c71\u7c72\u7c73\u7c74\u7c75\u7c76\u7c77\u7c78\u7c79\u7c7a\u7c7b\u7c7c\u7c7d\u7c7e\u7c7f\u7c89\u7c80\u7c8a\u7c8b\u7c8c\u7c8d\u7c8e\u7c8f\u7c90\u7c91\u7c92\u7c81\u7c93\u7c94\u7c95\u7c96\u7c97\u7c98\u7c99\u7c9a\u7c9b\u7ca3\u7ca4\u7ca5\u7ca6\u7ca7' + '\u7ca8\u7ca9\u7caa\u7cab\u7cb1\u7cb2\u7cb3\u7cb4\u7cb5\u7cb6\u7cb7\u7cb8\u7cb9\u7cba\u7cbb\u7cbc\u7cbd\u7cc0\u7cc1\u7cc6\u7cc7\u7cc8\u7cc9\u7ccb\u7cd0\u7cd1\u7cd2\u7cd3\u7cd6\u7cd7\u7cd9\u7cda\u7cdb\u7cdc\u7cdd\u7cde\u7cdf\u7ce0\u7ce1\u7ce2\u7ce3\u7ce4\u7ce5\u7ce6\u7ce9\u7cea\u7ceb\u7cec\u7ced\u7cee\u7cef\u7cf0\u7cf1\u7cf2\u7ce7\u7cf3\u7cf4\u7cf5\u7cf6\u7cf7\u7cf8\u7cf9\u7cfa\u7cfc\u7cfd\u7cfe\u7cff\u7d00\u7d01\u7d02\u7d03\u7d04\u7d05\u7d06\u7d07\u7d08\u7d09\u7d0a\u7d0b\u7d0c\u7d0d\u7d10\u7d11\u7d12\u7d13\u7d14\u7d15\u7d16\u7d17\u7d18\u7d19\u7d1a\u7d1b\u7d1c\u7d1d\u7d1e\u7d21\u7d22\u7d23\u7d24\u7d25\u7d26\u7d27\u7d28\u7d29\u7d2b\u7d2c\u7d2d\u7d2e\u7d2f\u7d30\u7d31\u7d32\u7d33\u7d34\u7d35\u7d36\u7d37\u7d38\u7d39\u7d3a\u7d3b\u7d3c\u7d3d\u7d3e\u7d3f\u7d40\u7d41' + '\u7d42\u7d43\u7d45\u7d46\u7d47\u7d48\u7d49\u7d4a\u7d4b\u7d4c\u7d4d\u7d4e\u7d4f\u7d50\u7d51\u7d52\u7d53\u7d54\u7d55\u7d56\u7d57\u7d58\u7d59\u7d5a\u7d5b\u7d5e\u7d5f\u7d60\u7d61\u7d62\u7d63\u7d64\u7d65\u7d66\u7d67\u7d68\u7d69\u7d6b\u7d6c\u7d6d\u7d6e\u7d6f\u7d70\u7d71\u7d72\u7d73\u7d74\u7d75\u7d76\u7d77\u7d78\u7d79\u7d7a\u7d7c\u7d7d\u7d7e\u7d7f\u7d81\u7d82\u7d85\u7d83\u7d86\u7d8d\u7d8e\u7d8f\u7d91\u7d92\u7d95\u7d97\u7d98\u7d99\u7d9a\u7d9b\u7d9c\u7d9d\u7d9e\u7d9f\u7da0\u7da1\u7da2\u7da4\u7da5\u7da6\u7da7\u7da9\u7daa\u7dab\u7dac\u7daf\u7db0\u7db1\u7db5\u7db6\u7db7\u7db8\u7db9\u7dba\u7dbb\u7dbc\u7dc0\u7dc2\u7dc3\u7dc4\u7dc7\u7dc8\u7dc9\u7dca\u7dcb\u7dcc\u7dcd\u7dce\u7dd4\u7dd5\u7dd6\u7dcf\u7dd7\u7dd8\u7dda\u7ddb\u7ddc\u7ddd\u7dde\u7ddf\u7de0\u7de1\u7de4\u7de5\u7de6' + '\u7de7\u7de8\u7dea\u7dec\u7ded\u7dee\u7def\u7df2\u7df3\u7df4\u7df7\u7df8\u7df9\u7dfa\u7dfb\u7dfc\u7dfd\u7dfe\u7dff\u7e00\u7e01\u7e09\u7e02\u7e03\u7e04\u7e05\u7e0a\u7e0b\u7e0c\u7e0d\u7e0e\u7e0f\u7e14\u7e15\u7e16\u7e1a\u7e1b\u7e1c\u7e1d\u7e1e\u7e1f\u7e20\u7e21\u7e23\u7e29\u7e2a\u7e2b\u7e2c\u7e2d\u7e2f\u7e30\u7e34\u7e35\u7e36\u7e39\u7e3a\u7e3b\u7e3c\u7e3d\u7e3e\u7e3f\u7e41\u7e42\u7e43\u7e44\u7e45\u7e46\u7e47\u7e48\u7e49\u7e4a\u7e4d\u7e4e\u7e4f\u7e50\u7e51\u7e52\u7e53\u7e54\u7e55\u7e56\u7e57\u7e58\u7e60\u7e61\u7e62\u7e63\u7e64\u7e65\u7e66\u7e67\u7e6c\u7e6d\u7e6e\u7e73\u7e74\u7e75\u7e76\u7e77\u7e78\u7e79\u7e7f\u7e80\u7e81\u7e82\u7e83\u7e84\u7e85\u7e86\u7e87\u7e88\u7e89\u7e8a\u7e8b\u7e8e\u7e8f\u7e90\u7e91\u7e92\u7e94\u7e95\u7e96\u7e97\u7e98\u7e93\u7e9c\u7e9d\u7e9e' + '\u7e9f\u7ea2\u7ea3\u7ea4\u7ea6\u7ea7\u7ea8\u7ea9\u7eaa\u7eab\u7eac\u7ead\u7eb1\u7eb2\u7eb3\u7eb4\u7eb5\u7eb6\u7eba\u7ebb\u7ebc\u7ebe\u7ebf\u7ec0\u7ec1\u7ec2\u7ec3\u7ec4\u7ec5\u7ec6\u7ec7\u7ec8\u7eca\u7ecb\u7ecc\u7ecf\u7ed0\u7ed1\u7ed7\u7ed8\u7edb\u7edc\u7edd\u7ede\u7edf\u7ee3\u7ee4\u7ee6\u7ee7\u7ee9\u7eea\u7eeb\u7eec\u7eee\u7eef\u7ef0\u7ef1\u7ef2\u7ef3\u7ef4\u7ef5\u7ef6\u7ef7\u7ef8\u7ef9\u7efa\u7efb\u7efe\u7eff\u7f00\u7f01\u7f02\u7f03\u7f04\u7f05\u7f06\u7f07\u7f08\u7f0b\u7f0c\u7f0d\u7f0e\u7f0f\u7f10\u7f11\u7f14\u7f15\u7f16\u7f17\u7f1a\u7f1b\u7f1c\u7f1d\u7f1e\u7f1f\u7f20\u7f12\u7f21\u7f22\u7f23\u7f2a\u7f2b\u7f2c\u7f2d\u7f2e\u7f2f\u7f30\u7f31\u7f35\u7f36\u7f43\u7f3a\u7f3b\u7f3c\u7f3d\u7f3e\u7f44\u7f45\u7f46\u7f47\u7f4a\u7f4b\u7f4c\u7f4d\u7f4e\u7f50\u7f51\u7f53' + '\u7f54\u7f55\u7f56\u7f57\u7f58\u7f59\u7f5a\u7f5c\u7f5d\u7f5e\u7f5f\u7f60\u7f61\u7f62\u7f63\u7f64\u7f67\u7f68\u7f69\u7f6a\u7f6b\u7f6c\u7f6d\u7f6e\u7f6f\u7f70\u7f71\u7f72\u7f73\u7f74\u7f75\u7f76\u7f77\u7f7a\u7f7b\u7f7c\u7f7d\u7f7e\u7f7f\u7f80\u7f81\u7f82\u7f83\u7f84\u7f85\u7f86\u7f87\u7f88\u7f89\u7f8a\u7f8b\u7f8c\u7f8d\u7f8e\u7f8f\u7f90\u7f91\u7f92\u7f93\u7f94\u7f95\u7f96\u7f97\u7f98\u7f99\u7f9a\u7faa\u7fab\u7fac\u7fad\u7fae\u7faf\u7fb0\u7fb1\u7fb2\u7fb3\u7fb4\u7fb5\u7fb6\u7fb7\u7fb8\u7fb9\u7fba\u7fbb\u7fbc\u7fbd\u7fbe\u7fbf\u7fc0\u7fc1\u7fc2\u7fc3\u7fc4\u7fc5\u7fc6\u7fc7\u7fc8\u7fc9\u7fca\u7fcb\u7fcc\u7fcd\u7fce\u7fcf\u7fd0\u7fd1\u7fd2\u7fd3\u7fd4\u7fde\u7fdf\u7fe0\u7fe1\u7fe2\u7fe3\u7fe4\u7fe5\u7fe6\u7fe7\u7fe8\u7fe9\u7fea\u7feb\u7fec\u7fed\u7fee\u7fef\u7ff0' + '\u7ff1\u7ff2\u7ff3\u7ff4\u7ff5\u7ff6\u7ff7\u7ff8\u7ff9\u7fd5\u7ffa\u7ffb\u7ffc\u7ffd\u7ffe\u7fff\u8000\u8001\u8002\u8003\u8004\u800e\u800f\u8010\u8011\u8012\u8013\u8014\u8015\u8016\u8017\u8018\u8019\u801a\u801b\u801c\u801d\u801e\u801f\u8020\u8021\u8022\u8023\u8024\u8025\u8026\u8027\u8028\u8029\u802a\u802b\u802c\u802d\u802e\u802f\u8030\u8031\u803f\u8040\u8041\u8042\u8043\u8044\u8045\u8046\u8047\u8048\u8049\u804a\u804b\u804c\u804d\u804e\u804f\u8050\u8051\u8052\u8053\u8054\u8055\u8056\u8057\u8058\u8059\u805a\u805b\u805c\u805d\u805e\u805f\u8060\u8075\u8076\u8077\u8078\u8079\u807a\u807b\u807c\u807d\u807e\u807f\u8080\u8081\u8082\u8083\u8084\u8085\u8086\u8087\u8088\u8089\u808a\u808b\u808c\u808d\u808e\u808f\u8090\u8091\u8092\u8093\u8094\u8095\u8096\u8097\u8098\u80a8' + '\u80a9\u80aa\u80ab\u80ac\u80ad\u80ae\u80af\u80b0\u80b1\u80b2\u80b3\u80b4\u80b5\u80b6\u80b7\u80b8\u80b9\u80ba\u80ca\u80cb\u80cc\u80cd\u80ce\u80cf\u80d0\u80d1\u80d2\u80d3\u80d4\u80d5\u80d6\u80d7\u80d8\u80d9\u80da\u80db\u5d48\u80e7\u80e8\u80e9\u80ea\u80eb\u80ec\u80ed\u80ee\u80ef\u80f0\u80f1\u80f2\u80f3\u80f4\u80f5\u80f6\u80f7\u80f8\u8102\u8103\u8104\u8105\u8106\u8107\u8108\u8109\u810a\u810b\u810c\u810d\u810e\u810f\u8110\u8111\u8112\u8113\u8114\u8115\u8116\u8117\u811c\u811d\u811e\u811f\u8120\u8121\u8122\u8127\u8128\u812e\u8129\u812a\u812f\u8130\u8131\u8132\u8135\u813b\u813c\u813e\u813f\u8140\u8141\u8142\u8143\u8145\u8146\u8147\u8148\u814a\u814b\u814c\u814d\u814e\u814f\u8150\u8151\u8152\u8153\u8154\u8156\u8157\u8158\u8159\u815a\u815c\u815d\u815e\u815f\u8160\u8161' + '\u8162\u8163\u8165\u8167\u8168\u8169\u816e\u816a\u816c\u816f\u8171\u8172\u8173\u8174\u8175\u8176\u8177\u817a\u817c\u817d\u817e\u817f\u8180\u8181\u8183\u8184\u8185\u8186\u8187\u8188\u8189\u818d\u818e\u8190\u8191\u8192\u8193\u8197\u8198\u8199\u819a\u819b\u819c\u819d\u819e\u819f\u81a0\u81a1\u81a2\u81a3\u81a6\u81a7\u81a8\u81a9\u81aa\u81ab\u81ac\u81ad\u81ae\u81af\u81b4\u81b5\u81b6\u81b7\u81bd\u81be\u81bf\u81c0\u81c1\u81c2\u81c3\u81c4\u81c5\u81c6\u81c9\u81ca\u81cb\u81cc\u81d5\u81d6\u81d7\u81d8\u81d9\u81da\u81db\u81df\u81e0\u81e1\u81e2\u81e3\u81e7\u81e8\u81e9\u81ea\u81f2\u81f3\u81f4\u81f8\u81f9\u81fa\u81fb\u81fc\u81fd\u81fe\u81ff\u8200\u8202\u8203\u8204\u8205\u8206\u8207\u8208\u8209\u820b\u820d\u820e\u8210\u8215\u8217\u8218\u8219\u821b\u821d\u821e\u821f\u8220\u8221' + '\u8222\u8223\u8224\u8228\u8229\u822a\u822b\u822c\u822d\u822e\u822f\u8230\u8231\u8232\u8233\u8234\u8235\u8236\u8237\u8238\u8239\u823a\u823b\u823c\u8242\u8243\u8244\u8245\u8246\u8247\u8248\u8249\u824a\u824b\u824c\u824d\u824e\u824f\u8250\u8251\u8252\u8253\u8254\u8255\u8256\u8257\u8258\u8259\u825a\u825b\u825c\u825d\u825e\u825f\u8260\u8261\u8262\u8263\u8264\u8265\u8266\u8267\u8268\u8286\u8269\u826a\u826b\u826c\u826d\u826e\u826f\u8270\u8271\u8272\u8273\u8274\u8275\u8276\u8277\u8278\u8287\u8288\u8289\u828a\u828b\u828c\u828d\u828e\u828f\u8290\u8291\u8292\u8293\u8294\u8295\u8296\u8297\u8298\u8299\u829a\u829b\u829c\u829d\u829e\u829f\u82a0\u82a1\u82a2\u82a3\u82a4\u82a5\u82a6\u82a7\u82a8\u82a9\u82aa\u82ab\u82ac\u82ad\u82ae\u82af\u82b0\u82b1\u82b2\u82b3\u82b4\u82b5\u82b6' + '\u82b7\u82b8\u82b9\u82ba\u82bb\u82bc\u82bd\u82be\u82bf\u82c0\u82c1\u82c2\u82c3\u82c4\u82c5\u82c6\u82c7\u82c8\u82d6\u82c9\u82ca\u82cb\u82d7\u82d8\u82d9\u82da\u82db\u82dc\u82dd\u8340\u82de\u82df\u82e0\u82e1\u82e2\u8341\u82e3\u82e4\u82e5\u82e6\u82e7\u82e8\u82e9\u82ea\u82eb\u82ec\u82ed\u82ee\u82ef\u82f0\u82f1\u82f2\u82f3\u82f4\u82f5\u82f6\u82f7\u82f8\u82f9\u82fa\u82fb\u82fc\u8279\u82fd\u82fe\u82ff\u8300\u8301\u8302\u8303\u8304\u8305\u8306\u8307\u8308\u8309\u830a\u830b\u830c\u830d\u830e\u830f\u8310\u83a4\u8311\u8312\u8313\u8314\u8315\u8316\u8317\u8318\u8319\u831a\u831b\u831c\u831d\u831e\u831f\u8320\u8321\u8322\u8323\u8324\u8325\u8326\u8327\u8328\u8329\u832a\u832b\u832c\u8342\u8343\u8344\u8345\u8346\u8347\u8348\u8349\u834a\u834b\u834c\u834d\u834e\u834f\u8350\u8351' + '\u8352\u8353\u8354\u8355\u8356\u8357\u8358\u8359\u835a\u835b\u835c\u835d\u835e\u835f\u8360\u8361\u8362\u8363\u8364\u8365\u8366\u8367\u8368\u8369\u836a\u836b\u836c\u836d\u836e\u836f\u8370\u8371\u8372\u8373\u8374\u8375\u8376\u8377\u8378\u8379\u837a\u837b\u837c\u837d\u837e\u842a\u837f\u8380\u8381\u8382\u8383\u8384\u8385\u8386\u8387\u8388\u8389\u838a\u838b\u838c\u838d\u838e\u83a5\u83a6\u83a7\u83a8\u83a9\u83aa\u83ab\u83ac\u83ad\u83ae\u83af\u83b0\u83b1\u83b2\u83b3\u83b4\u83b5\u83b6\u83b7\u83b8\u83b9\u83ba\u83bb\u83bc\u83bd\u83be\u83bf\u83c0\u83c1\u83c2\u83c3\u83c4\u83c5\u83c6\u83c7\u83c8\u83c9\u83ca\u83cb\u83cc\u83cd\u83ce\u83cf\u83d0\u83d1\u83d2\u83d3\u83d4\u83d5\u83d6\u83d7\u83d8\u83d9\u83da\u83db\u83dc\u83dd\u83de\u83df\u83e0\u83e1\u83e2\u83e3\u83e4\u83e5\u83e6' + '\u83e7\u83e8\u83e9\u83ea\u83eb\u83ec\u83ed\u83ee\u83ef\u83f0\u83f1\u83f2\u83f3\u83f4\u83f5\u83f6\u83f7\u83f8\u83f9\u83fa\u83fb\u83fc\u83fd\u83fe\u83ff\u8400\u8401\u8402\u8403\u8404\u8405\u8406\u8407\u8408\u8409\u840a\u840b\u840c\u840d\u840e\u840f\u842b\u842c\u842d\u75e5\u842e\u842f\u8430\u8431\u8432\u8433\u8434\u8435\u8436\u8437\u8438\u8439\u843a\u843b\u843c\u843d\u843e\u843f\u8440\u8441\u8442\u8443\u8444\u8445\u8446\u8447\u8448\u8449\u844a\u844b\u844c\u844d\u844e\u844f\u8450\u8451\u8452\u8453\u8454\u8455\u8456\u8457\u8410\u8458\u8459\u845a\u845b\u845c\u845d\u845e\u845f\u8460\u8461\u8462\u8463\u8464\u8465\u8466\u8467\u8468\u8469\u846a\u846b\u846c\u846d\u846e\u846f\u8470\u8471\u8472\u8473\u8474\u8475\u8476\u8477\u8478\u8479\u847a\u847b\u847c\u847d\u847e\u847f' + '\u8480\u8481\u8482\u8483\u8484\u8485\u8486\u8487\u8488\u8489\u848a\u848b\u848c\u848d\u848e\u848f\u849f\u84a0\u84a1\u84a2\u84a3\u84a4\u84a5\u84a6\u84a7\u84a8\u84a9\u84aa\u84ab\u84ac\u84ad\u84ae\u84af\u84b0\u84b1\u84b2\u84b3\u84b4\u84b5\u84b6\u84b7\u84b8\u84b9\u84ba\u84bb\u84bc\u84bd\u84be\u84bf\u84c0\u84c1\u84c2\u84c3\u84c4\u84c5\u84c6\u6a6b\u84c7\u84c8\u84c9\u84ca\u84cb\u84cc\u84cd\u84ce\u84cf\u84d0\u84d1\u84d2\u84d3\u84d4\u84d5\u84d6\u84d7\u84d8\u84d9\u84da\u84db\u84dc\u84dd\u84de\u84df\u84e0\u84e1\u84e2\u84e3\u84e4\u84e5\u84e6\u84e7\u84e8\u84e9\u84ea\u84eb\u84ec\u84ed\u84ee\u84ef\u84f0\u84f1\u84f2\u8506\u84f3\u8507\u8508\u8509\u850a\u850b\u850c\u850d\u850e\u850f\u8510\u8511\u8512\u8513\u8514\u8515\u8516\u8517\u8518\u8519\u851a\u851b\u851c\u851d\u851e\u851f' + '\u8520\u8521\u8522\u8523\u8524\u8525\u8526\u8527\u8528\u8529\u852a\u852b\u852c\u852d\u852e\u852f\u8530\u8531\u8532\u8533\u8534\u8535\u8536\u8537\u8538\u8539\u853a\u853b\u853c\u853d\u853e\u853f\u8540\u8541\u8542\u8543\u8544\u8545\u8546\u8547\u8548\u8549\u854a\u854b\u854c\u854d\u854e\u854f\u8550\u8551\u8552\u8553\u8554\u8555\u8556\u8557\u8558\u8559\u855a\u855b\u855c\u857c\u857d\u857e\u857f\u8580\u8581\u8582\u8583\u8584\u8585\u8586\u8587\u8588\u8589\u858a\u858b\u858c\u858d\u858e\u858f\u8590\u8591\u8592\u8593\u8594\u8595\u8596\u8597\u8598\u8599\u859a\u859b\u859c\u859d\u859e\u859f\u85a0\u85a1\u85a2\u85a3\u85a4\u85a5\u85a6\u85a7\u85a8\u85a9\u85aa\u85ab\u85ac\u85ad\u85ae\u85af\u85b0\u85b1\u85b2\u85b3\u85b4\u85cd\u85ce\u85cf\u85d0\u85d1\u85d2\u85d3\u85d4\u85d5\u85d6' + '\u85d7\u85d8\u85d9\u85da\u85db\u85dc\u85dd\u85de\u85df\u85e0\u85e1\u85e2\u85e3\u85e4\u85e5\u85e6\u85e7\u85e8\u85e9\u85ea\u85eb\u85ec\u85ed\u85ee\u85ef\u85f0\u85f1\u85f2\u85f3\u85f4\u85f5\u85f6\u85f7\u85f8\u85f9\u85fa\u85fb\u85fc\u85fd\u85fe\u85ff\u8613\u8600\u8601\u8602\u8614\u8603\u8615\u8616\u8617\u8618\u8619\u861a\u861b\u861c\u861d\u861e\u861f\u8620\u8621\u8622\u8623\u8624\u8625\u8626\u8627\u8628\u8629\u862a\u862b\u862c\u862d\u862e\u862f\u8630\u8631\u8632\u8633\u8634\u8635\u8636\u8637\u8638\u8639\u8647\u8648\u8649\u864a\u864b\u864c\u864d\u864e\u864f\u8650\u8651\u8652\u8653\u8654\u8655\u8656\u8657\u8658\u8659\u865a\u865b\u865c\u865d\u865e\u865f\u8660\u8670\u8661\u8662\u8663\u8664\u8665\u8666\u8667\u8671\u8672\u8673\u8674\u8675\u8676\u8677\u8678\u8679\u867a' + '\u867b\u867c\u867d\u867e\u867f\u8680\u8681\u8682\u8683\u8684\u8685\u8686\u8687\u8688\u8689\u868a\u868b\u868c\u869d\u868d\u868e\u869e\u869f\u86a0\u86a1\u86a2\u86a3\u86a4\u86a5\u86a6\u86a7\u86a8\u86a9\u86aa\u868f\u86ab\u863a\u86ac\u86ad\u86ae\u86af\u86b0\u86b1\u86b2\u86b3\u86b4\u86b5\u86b6\u86b7\u86c8\u86bd\u86be\u86bf\u86c0\u86c1\u86c2\u86c9\u86ca\u86cb\u86cc\u86cd\u86ce\u86cf\u86d0\u86d1\u86d2\u86d6\u86d7\u86d8\u86d9\u86db\u86dc\u86dd\u86de\u86e2\u86e8\u86e9\u86eb\u86ec\u86ed\u86f0\u86f2\u43c5\u86f3\u86f4\u86f9\u86fa\u86fb\u86fc\u86fd\u86fe\u8701\u8702\u8703\u8706\u8707\u870d\u870e\u870f\u8710\u8712\u8713\u8714\u8716\u8717\u8719\u871d\u871e\u871f\u8721\u8722\u8723\u8724\u8725\u8726\u8727\u8728\u8729\u872a\u872b\u872c\u872d\u872e\u872f\u8730\u8731\u8732\u8733' + '\u8734\u8735\u8736\u8737\u873b\u873c\u873d\u873e\u8793\u873f\u8740\u8741\u8742\u8743\u8744\u8745\u8746\u8747\u8748\u8749\u874a\u874b\u874c\u874d\u874e\u874f\u8750\u8751\u8752\u8753\u8754\u8755\u8756\u8757\u8758\u8759\u875a\u875b\u875c\u875d\u875e\u875f\u8760\u8765\u8761\u8766\u8767\u8768\u8769\u876a\u876b\u876c\u876d\u876e\u876f\u8770\u8771\u8772\u8773\u8774\u8775\u8776\u8777\u8778\u8779\u877a\u877b\u877c\u877d\u877e\u877f\u8780\u8781\u8782\u8783\u8784\u8785\u8786\u8787\u8788\u8794\u8795\u8796\u8797\u8798\u8799\u87c1\u879a\u879b\u879c\u879d\u879e\u879f\u87a0\u87a1\u87a2\u87a3\u87a4\u87a5\u87a6\u87a7\u87a8\u87a9\u87aa\u87ab\u87ac\u87ad\u87ae\u87af\u87b0\u87b1\u87b2\u87b3\u87b4\u87b5\u87b6\u87b7\u87c2\u87c3\u87c4\u87c5\u87c6\u87c7\u87c8\u87c9\u87ca\u87cb\u87cc' + '\u87cd\u87ce\u87cf\u87d0\u87d1\u87d2\u87d3\u87d4\u87d5\u87d6\u87d7\u87d8\u87d9\u87da\u87db\u87dc\u87dd\u87de\u87df\u87e0\u87e1\u87e2\u87e3\u87e4\u87f0\u87f1\u87f2\u87f3\u87f4\u87f5\u87f6\u87f7\u87f8\u87f9\u87fa\u87fb\u87fc\u87fd\u87fe\u87ff\u8800\u8801\u8802\u87e5\u8803\u8804\u8805\u8806\u8807\u8808\u8809\u880a\u880b\u880c\u880d\u880e\u880f\u8810\u8811\u8812\u8813\u8814\u8815\u8816\u8817\u8818\u8819\u881a\u881b\u881c\u87e6\u881d\u881e\u881f\u8820\u8821\u882f\u8830\u8831\u8832\u8833\u8834\u8835\u8836\u8837\u8838\u8839\u883a\u883b\u883c\u883d\u883e\u883f\u8840\u8841\u8842\u8843\u8844\u8845\u8846\u8847\u8848\u8849\u884a\u884b\u884c\u884d\u884e\u884f\u8850\u8851\u8852\u8853\u8854\u8855\u8856\u8857\u8858\u8859\u885a\u885b\u886f\u885c\u885d\u885e\u885f\u8860\u8861' + '\u8862\u8870\u8871\u8872\u8873\u8874\u8875\u8876\u8877\u8878\u8879\u887a\u887b\u887c\u887d\u887e\u887f\u8880\u8881\u8882\u8883\u8884\u8885\u8886\u8887\u8888\u8889\u888a\u888b\u888c\u888d\u888e\u888f\u8890\u8891\u8892\u8893\u8894\u8895\u8896\u8897\u8898\u88a2\u88a3\u88a4\u88a5\u88a6\u88a7\u88a8\u88a9\u88aa\u88ab\u88ac\u88ad\u88ae\u88af\u88b0\u88b1\u88b2\u88b3\u88b4\u88b5\u88b6\u88b7\u88b8\u88b9\u88ba\u88bb\u88bc\u88bd\u88be\u88bf\u88c0\u88c1\u88c2\u88c3\u88c4\u88c5\u88c6\u88c7\u88c8\u88c9\u88ca\u88dc\u88dd\u88de\u88df\u8899\u88e0\u88e1\u88e2\u88e3\u88e4\u88e5\u88e6\u88e7\u88e8\u8863\u88e9\u88ea\u88eb\u88ec\u88ed\u88ee\u88ef\u88f0\u88f1\u88f2\u88f3\u88f4\u88f5\u88f6\u88f7\u88f8\u88f9\u88fa\u88fb\u88fc\u8903\u8904\u8905\u8906\u8907\u8908\u8909\u890a\u890b\u890c' + '\u890d\u88cb\u890e\u890f\u8910\u8911\u8912\u8913\u8914\u8915\u8916\u8917\u8918\u8919\u88fd\u891a\u8923\u8924\u8925\u8926\u8927\u8928\u8929\u892a\u892b\u892c\u8933\u8934\u8935\u8936\u8937\u8938\u8939\u893a\u893b\u893c\u893d\u8942\u8943\u8944\u8945\u8946\u8947\u8948\u8949\u894f\u8950\u8951\u8952\u8953\u8954\u8955\u893e\u8959\u895a\u895b\u895c\u895d\u895e\u895f\u8961\u8962\u8963\u8964\u8966\u8967\u8968\u896a\u896b\u896d\u896f\u8970\u8971\u8972\u8976\u8977\u8978\u8979\u897a\u897b\u897d\u897e\u897f\u8980\u8981\u8982\u8983\u8984\u8985\u8987\u8988\u8989\u898a\u898b\u898c\u898d\u898e\u898f\u8993\u8994\u8995\u8996\u8999\u899a\u899b\u899c\u899d\u899e\u899f\u89a3\u89a4\u89a5\u89a6\u89a7\u89a8\u89a9\u89aa\u89ab\u89ac\u89ad\u89ae\u89af\u89b0\u89b1\u89b2\u89b3\u89b4\u89b5' + '\u89b6\u89b7\u89b8\u89b9\u89ba\u89bb\u89bc\u89bd\u89c1\u89c2\u89c3\u89c4\u89c5\u89c6\u89c7\u89c8\u89c9\u89ca\u89cb\u89cc\u89cd\u89ce\u89cf\u89d0\u89d1\u89d2\u89d3\u89d4\u89d5\u89d6\u89d7\u89d8\u89d9\u89da\u89db\u89dc\u89dd\u89de\u89df\u89e0\u89e1\u89e2\u89e3\u89e4\u89e5\u89e6\u89e7\u89ef\u89e8\u89f0\u89f1\u89f2\u89f3\u89f4\u89f5\u89f6\u89f7\u89f8\u89f9\u89fa\u89fb\u89fc\u89fd\u89fe\u89ff\u8a00\u8a01\u8a02\u8a03\u8a04\u8a05\u8a06\u8a07\u8a08\u8a10\u8a11\u8a12\u8a13\u8a14\u8a15\u8a16\u8a17\u8a18\u8a19\u8a1a\u8a1b\u8a1c\u8a1d\u8a1e\u8a1f\u8a20\u8a21\u8a22\u8a23\u8a24\u8a25\u8a26\u8a27\u8a28\u8a29\u8a2a\u8a2b\u8a5e\u8a30\u8a31\u8a32\u8a33\u8a34\u8a35\u8a36\u8a37\u8a38\u8a39\u8a3a\u8a3b\u8a3c\u8a3d\u8a3e\u8a3f\u8a40\u8a41\u8a42\u8a43\u8a44\u8a45\u8a46\u8a47\u8a48' + '\u8a49\u8a4a\u8a4b\u8a4c\u8a4d\u8a5f\u8a60\u8a61\u8a62\u8a63\u8a64\u8a65\u8a66\u8a67\u8a68\u8a69\u8a6a\u8a6b\u8a6c\u8a6d\u8a6e\u8a6f\u8a70\u8a71\u8a72\u8a73\u8a74\u8a75\u8a76\u8a77\u8a83\u8a84\u8a85\u8a86\u8a87\u8a88\u8a89\u8a8a\u8a8b\u8a8c\u8a8d\u8a8e\u8a8f\u8a90\u8a91\u8a92\u8a93\u8a94\u8a95\u8a96\u8a97\u8a9f\u8a98\u8aa0\u8aa1\u8aa2\u8aa3\u8aa4\u8aa5\u8aa6\u8aa7\u8aa8\u8aa9\u8aaa\u8aab\u8aac\u8aad\u8aae\u8aaf\u8ab0\u8abc\u8abd\u8abe\u8abf\u8ac0\u8ac1\u8ac2\u8ac3\u8ac4\u8ac5\u8ac6\u8ac7\u8ac8\u8ac9\u8ab1\u8aca\u8ad2\u8ad3\u8ad4\u8ad5\u8ad6\u8ad7\u8ad8\u8ad9\u8ada\u8adb\u8adc\u8add\u8ade\u8ae1\u8ae2\u8ae3\u8ae4\u8ae5\u8ae6\u8aeb\u8aec\u8aed\u8aee\u8aef\u8af0\u8af2\u8af3\u8af4\u8af5\u8af7\u8af8\u8afc\u8afd\u8afe\u8b01\u8b02\u8b03\u8b04\u8b05\u8af9\u8b08\u8b09' + '\u8b0a\u8b0b\u8b0e\u8b10\u8b11\u8b14\u8b15\u8b16\u8b17\u8b18\u8b19\u8b1a\u8b1b\u8b1d\u8b1e\u8b21\u8b22\u8b23\u8b24\u8b25\u8b26\u8b2a\u7580\u8b2b\u8b2c\u8b1c\u8b2d\u8b32\u8b33\u8b36\u8b37\u8b38\u8b39\u8b3a\u8b3d\u8b3e\u8b3f\u8b40\u8b43\u8b44\u8b45\u8b46\u8b47\u8b4c\u8b4d\u8b4e\u8b4f\u8b50\u8b54\u8b55\u8b56\u8b57\u8b5a\u8b5b\u8b5c\u8b5d\u8b5e\u8b64\u8b65\u8b66\u8b67\u8b68\u8b6a\u8b6b\u8b6c\u8b6f\u8b70\u8b71\u8b72\u8b73\u7549\u8b74\u8b75\u8b76\u8b77\u8b78\u8b79\u8b7a\u8b7b\u8b7c\u8b7d\u8b7e\u8b7f\u8b80\u8b81\u8b82\u8b83\u8b84\u8b85\u8b86\u8b88\u8b89\u8b8c\u8b8a\u8b8b\u8b8d\u8b8e\u8b8f\u8b90\u8b91\u8b92\u8b93\u8b94\u8b95\u8b99\u8b9a\u8b9b\u8b9c\u8b9e\u8b9f\u8ba0\u8ba1\u8ba7\u8ba8\u8bad\u8bae\u8bb1\u8bb4\u8bb5\u8bb9\u8bba\u8bbb\u8bbe\u8bbf\u8bc0\u8bc1\u8bc2\u8bc3' + '\u8bc5\u8bc6\u8bc7\u8bc8\u8bc9\u8bca\u8bcb\u8bcc\u8bcd\u8bd1\u8bd2\u8bd3\u8bd4\u8bd5\u8bd6\u8bd7\u8bd8\u8bd9\u8bda\u8bdb\u8bdc\u8bdd\u8bde\u8bdf\u8be0\u8be1\u8be2\u8be5\u8be6\u8be7\u8be8\u8be9\u8bea\u8beb\u8bec\u8bed\u8bee\u8bef\u8bf0\u8bf1\u8bf2\u8bf3\u8bf4\u8bf5\u8bf6\u8bf7\u8bf8\u8bf9\u8bfa\u8bfb\u8bfc\u8bfd\u8c0f\u8c10\u8c11\u8c12\u8c13\u8c14\u8c15\u8c16\u8c17\u8c18\u8c19\u8c1a\u8c1b\u8c1c\u8c1d\u8c1e\u8c1f\u8c20\u8c21\u8c22\u8c23\u8c24\u8c25\u8c26\u8c27\u8c28\u8c29\u8c2a\u8c2b\u8c2c\u8c2d\u8c2e\u8c2f\u8c30\u8c31\u8c32\u8c33\u8c34\u8c35\u8c36\u8c37\u8c38\u8c39\u8c3a\u8c3b\u8c4c\u8c4d\u8c4e\u8c4f\u8c50\u8c51\u8c52\u8c53\u8c54\u8c55\u8c56\u8c57\u8c58\u8c59\u8c5a\u8c5b\u8c5c\u8c5d\u8c5e\u8c5f\u8c60\u8c61\u8c62\u8c63\u8c64\u8c65\u8c66\u8c67\u8c68\u8c69\u8c6a' + '\u8c6b\u8c6c\u8c6d\u8c6e\u8c6f\u8c70\u8c71\u8c72\u8c73\u8c74\u8c75\u8c81\u8c82\u8c83\u8c84\u8c85\u8c86\u8c87\u8c88\u8c89\u8c8a\u8c8b\u8c8c\u8c8d\u8c8e\u8c8f\u8c90\u8c91\u8c92\u8c93\u8c94\u8c95\u8c76\u8c96\u8c97\u8c98\u8c99\u8c9a\u8c9b\u8c9c\u8c9d\u8c9e\u8c9f\u8ca0\u8ca1\u8ca2\u8ca3\u8cb0\u8cb1\u8cb2\u8cb3\u8cb4\u8cb5\u8cb6\u8cb7\u8cb8\u8cb9\u8cba\u8cbb\u8cbc\u8cbd\u8cbe\u8cbf\u8cc0\u8cc1\u8cc2\u8cc3\u8cc4\u8cc5\u8cc6\u8cc7\u8cc8\u8cc9\u8cca\u8ccb\u8ccc\u8ccd\u8cce\u8ccf\u8cd0\u8cd1\u8cd2\u8cd3\u8cd4\u8cd5\u8cd6\u8cd7\u8cd8\u8cd9\u8cda\u8cdb\u8ced\u8cee\u8cef\u8cf0\u8cf1\u8cf2\u8cf3\u8cf4\u8cf5\u8cf6\u8cf7\u8cf8\u8cf9\u8cfa\u8cdc\u8cfb\u8cfc\u8cfd\u8cfe\u8cff\u8d00\u8d01\u8d02\u8d03\u8d04\u8d05\u8d06\u8d07\u8d08\u8d09\u8d0a\u8d0b\u8d0c\u8d0d\u8d0e\u8d0f\u8d10' + '\u8d11\u8d12\u8d13\u8d14\u8d1a\u8d1b\u8d1c\u8d1d\u8d1e\u8d1f\u8d20\u8d21\u8d22\u8d23\u8d24\u8d25\u8d26\u8d27\u8d28\u8d29\u8d2a\u8d2b\u8d2c\u8d2d\u8d2e\u8d2f\u8d30\u8d31\u8d32\u8d33\u8d34\u8d35\u8d36\u8d37\u8d38\u8d3e\u8d3f\u8d40\u8d41\u8d42\u8d43\u8d44\u8d45\u8d46\u8d47\u8d48\u8d49\u8d4a\u8d4b\u8d4c\u8d4d\u8d4e\u8d4f\u8d50\u8d51\u8d52\u8d53\u8d54\u8d55\u8d56\u8d57\u8d58\u8d59\u8d62\u8d63\u8d64\u8d65\u8d66\u8d67\u8d68\u8d69\u8d6a\u8d6b\u8d6c\u8d6d\u8d6e\u8d6f\u8d87\u8d70\u8d71\u8d72\u8d73\u8d74\u8d75\u8d76\u8d77\u8d78\u8d79\u8d7a\u8d7b\u8d7c\u8d7d\u8d7e\u8d88\u8d89\u8d8a\u8d8b\u8d8c\u8d8d\u8d8e\u8d8f\u8d90\u8d91\u8d92\u8d93\u8d94\u8d95\u8d96\u8d97\u8d98\u8d99\u8d9a\u8d9b\u8d9c\u8d9d\u8da5\u8da6\u8da7\u8da8\u8da9\u8daa\u8dab\u8dac\u8dad\u8dae\u8daf\u8db6\u8db7' + '\u8db8\u8db9\u8dba\u8dbb\u8dbc\u8dbd\u8dc1\u8dc2\u8dc3\u8dc4\u8dc5\u8dc6\u8dc7\u8dc8\u8dc9\u8dca\u8dcb\u8dcd\u8dce\u8dcf\u8dd0\u8dd1\u8dd2\u8dd3\u8dd4\u8dd5\u8dd6\u8dd7\u8dd8\u8dd9\u8dda\u8ddb\u8ddc\u8ddd\u8dde\u8ddf\u8de0\u8de1\u8de2\u8de3\u8de4\u8de5\u8de6\u8de7\u8de8\u8de9\u8dea\u8deb\u8dec\u8ded\u8dee\u8def\u8df0\u8df1\u8df2\u8df3\u8df4\u8df5\u8df6\u8df7\u8df8\u8df9\u8dfa\u8dfb\u8dfc\u8dfe\u8dff\u8e00\u8e01\u8e02\u8e03\u8e04\u8e05\u8e06\u8e07\u8e08\u8e09\u8e0a\u8e0b\u8e0c\u8e0d\u8e0e\u8e0f\u8e10\u8e11\u8e12\u8e13\u8e14\u8e15\u8e16\u8e17\u8e18\u8e19\u8e1a\u8e1b\u8e1c\u8e1d\u8e1e\u8e1f\u8e20\u8e21\u8e22\u8e23\u8e24\u8e25\u8e26\u8e28\u8e29\u8e2a\u8e2b\u8e2c\u8e2d\u8e2e\u8e2f\u8e30\u8e31\u8e32\u8e33\u8e34\u8e35\u8e36\u8e37\u8e38\u8e39\u8e3a\u8e3b\u8e3c\u8e3d' + '\u8e3e\u8e3f\u8e40\u8e41\u8e42\u8e43\u8e44\u8e45\u8e46\u8e47\u8e48\u8e4a\u8e4b\u8e4c\u8e4d\u8e4e\u8e4f\u8e50\u8e51\u8e52\u8e53\u8e54\u8e55\u8e56\u8e57\u8e58\u8e59\u8e5a\u8e5b\u8e5c\u8e5d\u8e5e\u8e5f\u8e60\u8e61\u8e62\u8e63\u8e64\u8e65\u8e66\u8e69\u8e6a\u8e6b\u8e6c\u8e6d\u8e6f\u8e70\u8e71\u8e72\u8e73\u8e74\u8e75\u8e76\u8e77\u8e79\u8e7a\u8e7c\u8e7e\u8e7f\u8e80\u8e83\u8e84\u8e86\u8e88\u8e89\u8e8a\u8e8c\u8e8e\u8e8f\u8e91\u8e92\u8e94\u8e95\u8e96\u8e99\u8e9a\u8ea0\u8ea1\u8ea2\u8ea9\u8ead\u8eae\u8eb0\u8eb2\u8eb3\u8eb4\u8eb5\u8eb6\u8eb8\u8eb9\u8eba\u8ebb\u8ebc\u8ebd\u8ec0\u8ec1\u8ec2\u8ec3\u8ec5\u8ec6\u8ec7\u8ec8\u8ec9\u8ecb\u8ecc\u8ecd\u8ece\u8ed2\u8ed3\u8ed4\u8ed5\u8ed8\u8ed9\u8eda\u8edb\u8edc\u8edd\u8ede\u8ee3\u8ee0\u8ee8\u8ee9\u8eea\u8eeb\u8eec\u8eed\u8ef2\u8ef3' + '\u8ef4\u8ef5\u8ef6\u8ef7\u8efa\u8efb\u8efc\u8efd\u8efe\u8eff\u8f00\u8f01\u8f02\u8f03\u8f05\u8f06\u8f09\u8f0a\u8f0b\u8f0c\u8f0e\u8f0f\u8f10\u8f12\u8f13\u8f14\u8f17\u8f1c\u8f1d\u8f1e\u8f1f\u8f20\u8f21\u8f22\u8f23\u8f24\u8f25\u8f26\u8f27\u8f28\u8f29\u8f2a\u8f2b\u8f2c\u8f2d\u8f2e\u8f2f\u8f32\u8f33\u8f34\u8f35\u8f36\u8f37\u8f38\u8f39\u8f3a\u8f3b\u8f3c\u8f3d\u8f3e\u8f3f\u8f40\u8f41\u8f42\u8f43\u8f44\u8f4c\u8f4d\u8f4e\u8f4f\u8f50\u8f51\u8f52\u8f53\u8f54\u8f55\u8f56\u8f57\u8f58\u8f5c\u8f5d\u8f5e\u8f5f\u8f60\u8f61\u8f62\u8f63\u8f64\u8f65\u8f6a\u8f6b\u8f6c\u8f6d\u8f6e\u8f6f\u8f70\u8f71\u8f72\u8f73\u8f74\u8f75\u8f76\u8f77\u8f78\u8f79\u8f7a\u8f7b\u8f7c\u8f7d\u8f88\u8f89\u8f8a\u8f8b\u8f8c\u8f8d\u8f8e\u8f8f\u8f90\u8f96\u8f97\u8f98\u8f99\u8f9a\u8f9b\u8f9c\u8f9d\u8fa0\u8fa1' + '\u8fa2\u5b15\u8fa3\u8fa4\u8fa5\u8fa6\u8fa8\u8fa9\u8faa\u8fab\u8fac\u8fad\u8fae\u8fb0\u8fb1\u8fb2\u8fb4\u8fb5\u8fb6\u8fb7\u8fb8\u8fb9\u8fba\u8fbb\u8fbc\u8fbe\u8fbf\u8fc1\u8fc2\u8fc3\u8fc4\u8fc5\u8fc6\u8fc7\u8fc8\u8fc9\u8fca\u8fcb\u8fcc\u8fcd\u8fce\u8fcf\u8fd0\u8fd1\u8fd2\u8fd3\u8fd4\u8fd5\u8fd6\u8fd7\u8fd8\u8fd9\u8fda\u8fdb\u8fdc\u8fdd\u8fde\u8fdf\u8fe0\u8fe1\u8fe2\u8fe3\u8fe4\u8fe5\u8fe6\u8fe7\u8fe8\u8fe9\u8fea\u8feb\u8fec\u8fee\u8fef\u8ff0\u8ff1\u8ff2\u8ff3\u8ff4\u8ff5\u8ff6\u8ff7\u8ff8\u8ff9\u8ffa\u8ffb\u8ffc\u8fff\u9000\u9003\u9004\u9005\u9006\u9007\u9008\u9009\u900a\u900b\u900c\u900d\u900e\u900f\u9011\u9012\u9013\u9014\u9015\u9016\u9018\u901b\u901c\u901d\u901e\u9020\u9021\u9022\u9023\u9024\u9025\u9027\u9028\u9029\u902d\u902e\u902f\u9030\u9031\u9032\u9033' + '\u9039\u903a\u903b\u903c\u903d\u903e\u903f\u9040\u9041\u9042\u9043\u9044\u904d\u904e\u904f\u9050\u9051\u9052\u9053\u9054\u9055\u905c\u905d\u905e\u905f\u9060\u9061\u906a\u906b\u906c\u906d\u906e\u906f\u9070\u9071\u9072\u9073\u907a\u907b\u907c\u9081\u908c\u908d\u908e\u908f\u9090\u9092\u9097\u9098\u909d\u90a1\u90a2\u90a3\u90a6\u90a7\u90a8\u90a9\u90ac\u90ad\u90ae\u90e9\u90af\u90b0\u90b1\u90b2\u90b3\u90b4\u90b5\u90b6\u90bf\u90c0\u90c1\u90c2\u90c3\u90c4\u90c5\u90c6\u90c7\u90c8\u90c9\u90ca\u90cb\u90cc\u90cd\u90ce\u90cf\u90d0\u90d1\u90d2\u90d3\u90d4\u90d5\u90d6\u90d7\u90d8\u90ea\u90eb\u90ec\u90ed\u90ee\u90ef\u90f0\u90f1\u90f2\u90f3\u90f4\u90f5\u90f6\u90f7\u90f8\u90f9\u90fa\u90fb\u90fc\u90fd\u90fe\u90ff\u90d9\u9100\u9101\u9102\u9103\u9104\u9105\u910c\u910d\u910e\u910f' + '\u9110\u9111\u9112\u9113\u9114\u9115\u9116\u9117\u9118\u9119\u911a\u911b\u911c\u911d\u911e\u9128\u9129\u912a\u912b\u912c\u912d\u912e\u912f\u9130\u9131\u9132\u9133\u9134\u9135\u9136\u9137\u9138\u9139\u913a\u913b\u913c\u913d\u913e\u913f\u9140\u9141\u9142\u9143\u9159\u9144\u9145\u9146\u9147\u915a\u915b\u915c\u915d\u915e\u915f\u9160\u9161\u9162\u9163\u9148\u9164\u9165\u9166\u9167\u9168\u9169\u916a\u916b\u916c\u916d\u916e\u9179\u917a\u917b\u917c\u917d\u917e\u917f\u9180\u9181\u9182\u9183\u9184\u9185\u9186\u918e\u918f\u9190\u9191\u9192\u9193\u9194\u9195\u9196\u9197\u9198\u9199\u919a\u919b\u919c\u919d\u919e\u919f\u91a0\u91a1\u91a9\u91aa\u91ab\u91ac\u91ad\u91ae\u91a2\u91af\u91b0\u91b1\u91b2\u91b3\u91b4\u91b5\u91b6\u91b7\u91b8\u91b9\u91ba\u91bb\u91bc\u91bd\u91be\u91bf' + '\u91a3\u91c5\u91c6\u91c7\u91c8\u91c9\u91ca\u91cb\u91cc\u91cd\u91d0\u91d1\u91d2\u91d3\u91d4\u91d5\u91d8\u91d9\u91da\u91db\u91dc\u91dd\u91de\u91e0\u91e1\u91e2\u91e3\u91e4\u91e5\u91e8\u91e9\u91ea\u91eb\u91ed\u91ee\u91ef\u91f0\u91f1\u91f3\u91f4\u91f7\u91f5\u91f6\u91f9\u91fa\u91fb\u91fc\u91fd\u91fe\u9204\u9205\u920a\u920b\u920c\u920d\u920e\u920f\u9210\u9211\u9212\u9213\u9215\u9216\u9218\u921b\u921c\u921d\u921e\u921f\u9220\u9222\u9224\u9226\u9227\u9228\u9229\u922a\u922b\u922f\u9230\u9231\u9232\u9233\u9234\u9235\u9236\u9238\u9239\u923a\u923b\u923c\u923d\u923e\u923f\u9240\u9241\u9242\u9243\u9244\u9245\u924f\u9250\u9251\u9252\u9253\u9254\u9255\u9256\u9257\u926d\u9258\u9259\u925a\u925b\u925c\u925d\u925e\u925f\u9260\u9261\u9262\u9263\u9264\u9265\u9266\u9267\u926e\u926f' + '\u9270\u9271\u9272\u9273\u9274\u9275\u9276\u9277\u9278\u9279\u927a\u927b\u927c\u9282\u9283\u9284\u9285\u9286\u9287\u9288\u9289\u928a\u928f\u9290\u9291\u9292\u9293\u9294\u9295\u9296\u9297\u9298\u9299\u929a\u929b\u929c\u929d\u929e\u929f\u92a0\u92a1\u92a2\u92a3\u92a4\u92a5\u92aa\u92ab\u92ac\u92ad\u92ae\u92af\u92b0\u92b1\u92b2\u92b3\u92b4\u92b5\u92b6\u92b7\u92b8\u92b9\u92bf\u92c0\u92c1\u92c2\u92c3\u92c4\u92c5\u92c6\u92c7\u92ce\u92cf\u92d0\u92d1\u92d2\u92d3\u92d4\u92dd\u92de\u92df\u92e0\u92e1\u92e2\u92e3\u92e4\u92e9\u92ea\u92eb\u92ec\u92ed\u92ee\u92f0\u92f1\u92f2\u92f3\u92f4\u92f8\u92f9\u92fa\u92fd\u92fe\u9301\u9304\u9305\u9306\u9307\u9308\u9309\u930b\u930c\u930d\u930e\u930f\u9311\u9312\u9313\u9314\u9315\u9316\u9317\u9318\u9319\u931a\u931b\u931d\u931e\u931f\u9320' + '\u9321\u9322\u9323\u9324\u9325\u9326\u9327\u9328\u9329\u932a\u932b\u932c\u932d\u932e\u932f\u9330\u9331\u9332\u9333\u9334\u9335\u9336\u9337\u9338\u9339\u933a\u933b\u933c\u933d\u933e\u933f\u9340\u9341\u9342\u9343\u9344\u9345\u9347\u9348\u9349\u934a\u934b\u934c\u934d\u934e\u934f\u9350\u9352\u9353\u9354\u9355\u9358\u935a\u935d\u935e\u935f\u9360\u9361\u9362\u9363\u9364\u9365\u9367\u9368\u9369\u936a\u936b\u936c\u936d\u936e\u936f\u9370\u9371\u9372\u9375\u9376\u9377\u9378\u9379\u937a\u937b\u937c\u937d\u937e\u937f\u9380\u9381\u9382\u9383\u9384\u9385\u9386\u9387\u9388\u9389\u938a\u9391\u9392\u9393\u9394\u9395\u9396\u9397\u9398\u9399\u939a\u939b\u939c\u938b\u939d\u939e\u939f\u93a0\u93a1\u93a2\u93a3\u93a6\u93a7\u93a8\u93a9\u93aa\u93ab\u93ac\u93ad\u93ae\u93af\u93b0\u93b1' + '\u93b2\u93b3\u93b4\u93b5\u93b6\u93b7\u93b8\u93b9\u93ba\u93bb\u93bc\u93c4\u93c5\u93c6\u93c7\u93c8\u93c9\u93ca\u93cb\u93cc\u93cd\u93ce\u93cf\u93d0\u93d1\u93d2\u93d3\u93d4\u93d5\u93d6\u93d7\u93d8\u93d9\u93da\u93db\u93dc\u93dd\u93de\u93df\u93e0\u93e4\u93e5\u93e6\u93e7\u93e8\u93e9\u93ea\u93eb\u93ec\u93ed\u93ee\u93ef\u93f0\u93f1\u93f2\u93f3\u93f4\u93f5\u93f6\u93f7\u93fd\u93fe\u93ff\u9400\u9401\u9402\u9403\u9404\u9405\u9406\u9407\u9408\u9409\u940a\u940b\u940c\u940d\u940e\u940f\u9410\u9411\u9412\u9413\u9414\u9415\u9416\u9417\u9418\u941a\u941b\u941c\u941d\u941e\u941f\u9420\u9421\u9422\u9423\u9424\u9425\u9426\u9427\u942c\u942d\u942e\u942f\u9430\u9431\u9432\u9433\u9434\u9435\u9436\u9437\u943d\u9438\u943e\u943f\u9440\u9441\u9442\u9443\u9444\u9445\u9446\u944c\u944d\u944e' + '\u944f\u9450\u9451\u9452\u9453\u9454\u9447\u9457\u9458\u9455\u945b\u945c\u945d\u945f\u9460\u9461\u9462\u9464\u9465\u9466\u9468\u9469\u946a\u946b\u946c\u946d\u946e\u946f\u9470\u9471\u9473\u9474\u9475\u9476\u9477\u9478\u9479\u947a\u947b\u947c\u947d\u947e\u947f\u9480\u9481\u9485\u9486\u9487\u9488\u9489\u948a\u948b\u948c\u948d\u948e\u948f\u9490\u9491\u9492\u9493\u9497\u9498\u9499\u949a\u949b\u949c\u949d\u949e\u949f\u94a0\u94a1\u94a2\u94a3\u94a4\u94a5\u94a6\u94a7\u94a8\u94a9\u94aa\u94ab\u94ac\u94b6\u94ad\u94b7\u94b8\u94b9\u94ba\u94bb\u94bc\u94bd\u94be\u94bf\u94c0\u94c1\u94c2\u94c3\u94c4\u94c5\u94c6\u94c7\u94c8\u94c9\u94ca\u94d2\u94d3\u94d4\u94d5\u94d6\u94d7\u94d8\u94d9\u94da\u94db\u94dc\u94dd\u94de\u94df\u94e0\u94e1\u94e2\u94e9\u94ea\u94eb\u94ec\u94ed\u94ee\u94ef' + '\u94f0\u94f1\u94f2\u94f3\u94f4\u94f5\u94f6\u94f7\u94f8\u94f9\u94fa\u94fe\u94ff\u9500\u9501\u9502\u9503\u9504\u9505\u9506\u9507\u9508\u9509\u950a\u9510\u9511\u9512\u9513\u9514\u9515\u9516\u9517\u9518\u9519\u951a\u951b\u951c\u951d\u9523\u9524\u9525\u9526\u9527\u9528\u9529\u952a\u952b\u952c\u952d\u952e\u952f\u9534\u9535\u9536\u9537\u9538\u953a\u953b\u953d\u953e\u953f\u9540\u9541\u9542\u9543\u9545\u9544\u9546\u9547\u9548\u9549\u954a\u954b\u954e\u954f\u9550\u9553\u9554\u9555\u9556\u9557\u9558\u9559\u955c\u955d\u955e\u955f\u9560\u9561\u9562\u9563\u9564\u9565\u9566\u9567\u956d\u956e\u956f\u9570\u9571\u9572\u9573\u957a\u957b\u957c\u957d\u957e\u957f\u9580\u9581\u9582\u9583\u9584\u9585\u958c\u958d\u958e\u958f\u9590\u9591\u9592\u9593\u9594\u9595\u9596\u9597\u9598\u9599' + '\u959c\u959d\u959e\u959f\u95a0\u95a1\u95a2\u95a3\u95a4\u95a5\u95a6\u95a7\u95a8\u95ad\u95ae\u95af\u95b0\u95b1\u95b2\u95b3\u95b4\u95b5\u95b6\u95b7\u95c1\u95c2\u95c3\u95c4\u95c5\u95c6\u95c7\u95c8\u95c9\u95ca\u95cb\u95cc\u95cd\u95d2\u95d3\u95d4\u95d5\u95d6\u95d7\u95d8\u95d9\u95db\u95dc\u95dd\u95de\u95df\u95e5\u95e6\u95e7\u95e8\u95e9\u95ea\u95eb\u95ec\u95ed\u95ee\u95f3\u95f4\u95f5\u95f6\u95f7\u95fa\u95fb\u95fc\u95fd\u95fe\u9600\u9601\u9602\u9603\u9604\u9605\u9606\u9607\u9608\u960a\u960b\u960c\u960d\u960e\u960f\u9610\u9611\u9612\u9613\u9614\u9615\u9616\u9617\u9618\u9619\u961a\u961b\u961c\u961d\u961f\u9620\u9621\u9622\u9623\u9624\u9625\u9626\u9627\u9628\u9629\u962a\u962b\u962c\u962d\u962e\u962f\u9630\u9631\u9632\u9633\u9634\u9635\u9636\u9637\u9638\u963a\u963b\u963c' + '\u963d\u963e\u963f\u9640\u9641\u9642\u9643\u9644\u9645\u9646\u9647\u9648\u9649\u964a\u964b\u964c\u964d\u964e\u964f\u9650\u9651\u9652\u9653\u9654\u9655\u9656\u9657\u9658\u9659\u965a\u965b\u965c\u965d\u965e\u965f\u9660\u9661\u9662\u9663\u9664\u9665\u9666\u9667\u9668\u9669\u966e\u966f\u9670\u9671\u9672\u9673\u9674\u9675\u9676\u9677\u9678\u9679\u967a\u967b\u967c\u967d\u967e\u967f\u9680\u9681\u9682\u9683\u9684\u9685\u9686\u9687\u9688\u9689\u968a\u968b\u968c\u968d\u968e\u968f\u9690\u9691\u9692\u9693\u9694\u9695\u9696\u9697\u9698\u9699\u969a\u969b\u969c\u969d\u969e\u969f\u96a0\u96a1\u96a2\u96a3\u96a4\u96a5\u96a6\u96a7\u96a8\u96a9\u96aa\u96ab\u96ac\u96ad\u96ae\u96af\u96b0\u96b1\u96b2\u96b3\u96b4\u96b5\u96bb\u96bc\u96bd\u96be\u96bf\u96c0\u96c1\u974b\u96c2\u96c3\u96c4' + '\u96c5\u96c6\u96c7\u96c8\u96c9\u96ca\u96cb\u96cc\u96cd\u96ce\u96cf\u96d0\u96d1\u96d2\u96d3\u96b6\u96d4\u96d5\u96d6\u96d7\u96d8\u96d9\u96da\u96db\u96dc\u96dd\u96de\u96df\u96e0\u96e1\u96e2\u96e3\u96e4\u96e5\u96e6\u96e7\u96e8\u96e9\u96ea\u96eb\u96ec\u96ed\u96ee\u96ef\u96f0\u96f1\u96f2\u96f3\u96f4\u96f5\u96fd\u96fe\u96ff\u9700\u9701\u9702\u9703\u9704\u9705\u9706\u9707\u9708\u9709\u970a\u970b\u970c\u970d\u970e\u970f\u9710\u9711\u9712\u9713\u9714\u9715\u9716\u9717\u9718\u9719\u971a\u971b\u971c\u971d\u971e\u971f\u9720\u9721\u9722\u9723\u9724\u9725\u9726\u9727\u9728\u9729\u972a\u972b\u972c\u972d\u972e\u972f\u9730\u9731\u9732\u9733\u9734\u9735\u9736\u9737\u9738\u9739\u973a\u973b\u973c\u973d\u973e\u973f\u9740\u9741\u974c\u974d\u974e\u974f\u9750\u9751\u9752\u9753\u9754' + '\u9755\u9756\u9757\u9758\u9759\u975a\u975b\u975c\u975d\u975e\u975f\u9760\u9761\u9762\u9763\u9764\u9765\u9766\u9767\u9768\u9769\u976a\u976b\u976c\u976d\u976e\u976f\u9770\u9771\u9772\u9773\u9774\u9775\u9776\u9777\u9778\u9779\u977a\u977b\u977c\u97a2\u977d\u977e\u977f\u9780\u9781\u9782\u9783\u9784\u9785\u9786\u9787\u9788\u9789\u978a\u978b\u978c\u978d\u978e\u978f\u9790\u9791\u9792\u9793\u9794\u9795\u9796\u9797\u9798\u9799\u979a\u97a3\u979b\u97a4\u97a5\u97a6\u97a7\u97a8\u97a9\u97aa\u97ab\u97ac\u97ad\u97ae\u97af\u97b0\u97b1\u97b2\u97b3\u97b4\u97b5\u97b6\u97b7\u97b8\u97b9\u97ba\u97bb\u97bc\u97bd\u97be\u97bf\u97c0\u97c1\u97c2\u97c3\u97c4\u97c5\u97c6\u97c7\u97c8\u97c9\u97ca\u97cb\u97cc\u97cd\u97ce\u97cf\u97d0\u97d1\u97d2\u97d3\u97d4\u97d5\u97d6\u97d7\u97d8\u97d9\u97da' + '\u97db\u97dc\u97dd\u97de\u97df\u97e0\u97e1\u97e2\u97e9\u97ea\u97eb\u97ec\u97ed\u97ee\u97ef\u97f0\u97f1\u97f2\u97f3\u97f4\u97f5\u97f6\u97f7\u97f8\u97f9\u97fa\u97fb\u97fc\u97fd\u97fe\u97ff\u9800\u9801\u9802\u9803\u9804\u9805\u9806\u9807\u9808\u9809\u982c\u980a\u980b\u980c\u980d\u980e\u980f\u9810\u9811\u9812\u9813\u9814\u9815\u9816\u9817\u9818\u9819\u981a\u981b\u981c\u981d\u981e\u981f\u982d\u982e\u982f\u9830\u9831\u9832\u9833\u9834\u9835\u9836\u9837\u9838\u9839\u983a\u983b\u983c\u983d\u983e\u983f\u9840\u9841\u9842\u9843\u9844\u9845\u9846\u9847\u9848\u9849\u984a\u984b\u984c\u984d\u984e\u984f\u9850\u9851\u9852\u9853\u9854\u9855\u9856\u9857\u9858\u9859\u985a\u985b\u985c\u985d\u985e\u985f\u986d\u98aa\u986e\u986f\u9870\u9871\u9860\u9872\u9873\u9874\u9875\u9876\u9877' + '\u9878\u9879\u987a\u987b\u987c\u987d\u987e\u987f\u9880\u9881\u9882\u9883\u9884\u9885\u9886\u9887\u9888\u9889\u988a\u988b\u988c\u988d\u988e\u988f\u9890\u9891\u9892\u9893\u9894\u9895\u9896\u9897\u9898\u9899\u989a\u989b\u989c\u989d\u989e\u989f\u98a0\u98ab\u98ac\u98ad\u98ae\u98af\u98b0\u98b1\u98b2\u98b3\u98b4\u98b5\u98b6\u98b7\u98b8\u98b9\u98ba\u98bb\u98bc\u98bd\u98be\u98bf\u98c0\u98c1\u98c2\u98c3\u98ce\u98cf\u98d0\u98d1\u98d2\u98d3\u98d4\u98d5\u98d6\u98d7\u98d8\u98d9\u98da\u98db\u98dc\u98dd\u98de\u98df\u98e0\u98ea\u98eb\u98ec\u98ed\u98ee\u98ef\u98f0\u98f1\u98f2\u98f3\u98f4\u98f5\u98f6\u98f7\u98f8\u98f9\u98fa\u98fb\u98e1\u9902\u9903\u9904\u9905\u9906\u990a\u990b\u990c\u990d\u990e\u990f\u9910\u9912\u9913\u9914\u9915\u9916\u9917\u9918\u9919\u991a\u991b\u991c\u991d' + '\u991e\u991f\u9920\u9921\u9922\u9924\u9925\u9926\u9927\u9928\u9929\u992a\u992b\u992c\u992d\u992e\u992f\u9930\u9931\u9932\u9933\u9934\u9935\u9936\u9937\u9938\u9939\u993a\u993b\u993c\u993d\u993e\u993f\u9940\u9941\u9942\u9943\u9944\u9945\u9946\u9947\u9948\u9949\u994a\u994b\u994c\u994d\u994e\u9952\u9953\u9954\u9955\u9956\u9957\u9958\u9959\u995a\u995b\u995c\u995d\u995e\u995f\u9960\u9961\u9962\u9963\u9964\u9965\u9966\u9967\u9968\u9969\u996a\u996b\u996c\u996d\u996e\u996f\u9970\u9975\u9976\u9977\u9978\u9979\u997a\u997b\u997c\u997d\u997e\u997f\u9980\u9981\u9982\u9983\u9984\u9985\u9986\u9987\u9988\u9989\u998a\u998b\u998c\u998d\u998e\u998f\u9990\u9991\u9992\u9993\u9994\u9995\u9996\u9997\u9998\u9999\u999a\u999b\u999c\u999d\u999f\u99a0\u99a1\u99a2\u99a3\u99a4\u99a5\u99a6' + '\u99a7\u99a8\u99a9\u99aa\u99ab\u99ac\u99ad\u99ae\u99af\u99b0\u99b1\u99b2\u99b3\u99b4\u99b5\u99b6\u99b7\u99b8\u99b9\u99ba\u99bb\u99bc\u99bf\u99c0\u99c1\u99c2\u99c3\u99c4\u99c5\u99c6\u99c7\u99c8\u99c9\u99ca\u99cb\u99cc\u99cd\u99ce\u99cf\u99d0\u99d1\u99d2\u99d3\u99d4\u99d5\u99d6\u99d7\u99d8\u99d9\u99da\u99db\u99dc\u99dd\u99de\u99df\u99e0\u99e1\u99e2\u99e3\u99e4\u99e5\u99e6\u99e7\u99e8\u99e9\u99ea\u99eb\u99ec\u99ed\u99ee\u99ef\u99f0\u99f1\u99f2\u99f3\u99f4\u99f5\u99f6\u99f7\u99f8\u99f9\u99fa\u99fb\u99fc\u99fd\u99fe\u9a01\u9a02\u9a03\u9a04\u9a05\u9a06\u9a07\u9a08\u9a09\u9a0a\u9a0b\u9a0c\u9a0d\u9a0e\u9a0f\u9a10\u9a11\u9a12\u9a13\u9a14\u9a15\u9a16\u9a1a\u9a1b\u9a1c\u9a1d\u9a1e\u9a1f\u9a20\u9a21\u9a22\u9a23\u9a24\u9a26\u9a27\u9a28\u9a2a\u9a2b\u9a2f\u9a31\u9a32\u9a33' + '\u9a34\u9a35\u9a36\u9a37\u9a38\u9a39\u9a3a\u9a3b\u9a3c\u9a3d\u9a3e\u9a41\u9a42\u9a43\u9a44\u9a45\u9a46\u9a47\u9a48\u9a49\u9a4a\u9a4b\u9a4c\u9a4d\u9a52\u9a53\u9a54\u9a55\u9a56\u9a57\u9a58\u9a59\u9a5a\u9a5e\u9a5f\u9a60\u9a61\u9a62\u9a63\u9a64\u9a65\u9a66\u9a67\u9a6f\u9a70\u9a71\u9a72\u9a73\u9a74\u9a75\u9a76\u9a77\u9a78\u9a7d\u9a7e\u9a93\u9a7f\u9a80\u9a81\u9a82\u9a83\u9a84\u9a85\u9a86\u9a87\u9a88\u9a89\u9a94\u9a95\u9a96\u9a97\u9a98\u9a99\u9a9a\u9a9b\u9a9c\u9a9d\u9a9e\u9a9f\u9aa0\u9aa8\u9aa9\u9aaa\u9aab\u9aac\u9aad\u9aae\u9aaf\u9ab0\u9ab1\u9ab2\u9ab3\u9ab4\u9ab5\u9ab7\u9ab8\u9ab9\u9aba\u9abc\u9abd\u9abe\u9abf\u9ac0\u9ac4\u9ac6\u9ac7\u9ac8\u9ac9\u9aca\u9acb\u9acc\u9acd\u9ace\u9acf\u9ad0\u9ad1\u9ad2\u9ad3\u9ad4\u9ad5\u9ad6\u9ad7\u9ad8\u9ad9\u9ada\u9adb\u9adc\u9add' + '\u9ade\u9adf\u9ae0\u9ae2\u9ae3\u9ae4\u9ae5\u9ae7\u9ae8\u9ae9\u9aea\u9aeb\u9aec\u9aed\u9aee\u9aef\u9af0\u9af1\u9af2\u9af3\u9af4\u9af5\u9af7\u9af8\u9af9\u9afa\u9afb\u9afc\u9afd\u9afe\u9aff\u9b00\u9b03\u9b04\u9b05\u9b06\u9b07\u9b09\u9b0a\u9b0b\u9b0c\u9b0d\u9b0e\u9b0f\u9b10\u9b11\u9b12\u9b13\u9b14\u9b15\u9b16\u9b17\u9b18\u9b19\u9b1a\u9b20\u9b21\u9b22\u9b23\u9b24\u9b25\u9b26\u9b27\u9b28\u9b29\u9b2a\u9b2b\u9b2c\u9b2d\u9b2e\u9b2f\u9b30\u9b31\u9b32\u9b36\u9b37\u9b38\u9b39\u9b3a\u9b3b\u9b3c\u9b3d\u9b3e\u9b3f\u9b40\u9b41\u9b47\u9b48\u9b49\u9b4a\u9b61\u9b4b\u9b4c\u9b4d\u9b4e\u9b4f\u9b50\u9b51\u9b52\u9b53\u9b54\u9b55\u9b56\u9b57\u9b58\u9b59\u9b62\u9b63\u9b64\u9b65\u9b66\u9b67\u9b68\u9b69\u9b6a\u9b6b\u9b6c\u9b6d\u9b6e\u9b6f\u9b70\u9b71\u9b72\u9b7a\u9b7b\u9b7c\u9b7d\u9b7e' + '\u9b7f\u9b80\u9b81\u9b82\u9b83\u9b84\u9b85\u9b86\u9b87\u9b88\u9b89\u9b8a\u9b8b\u9b8c\u9b8d\u9b8e\u9b8f\u9b93\u9b94\u9b95\u9b96\u9b97\u9b98\u9b99\u9b9a\u9b9b\u9ba3\u9ba4\u9ba5\u9ba6\u9ba7\u9ba8\u9ba9\u9baa\u9baf\u9bb0\u9bb1\u9bb2\u9bb7\u9bb8\u9bb9\u9bba\u9bbb\u9bbc\u9bc0\u9bc1\u9bc2\u9bc3\u9bc4\u9bc5\u9bc6\u9bca\u9bcb\u9bcd\u9bd1\u9bd2\u9bd3\u9bd4\u9bd5\u9bd6\u9bd7\u9bd8\u9bd9\u9bdb\u9bdc\u9bdf\u9be0\u9be1\u9be2\u9be3\u9be4\u9be5\u9be6\u9bea\u9beb\u9bec\u9bed\u9bee\u9bef\u9bf0\u9bf5\u9bf6\u9bf7\u9bf9\u9bfc\u9bfd\u9c00\u9c03\u9c04\u9c05\u9c06\u9c07\u9c08\u9c09\u9c0a\u9c0b\u9c0c\u9c0e\u9c0f\u9c10\u9c15\u9c17\u9c18\u9c1a\u9c1c\u9c1d\u9c1e\u9c1f\u9c21\u9c22\u9c23\u9c24\u9c25\u9c26\u9c27\u9c28\u9c2b\u9c2c\u9c2d\u9c2e\u9c2f\u9c30\u9c31\u9c32\u9c33\u9c34\u9c35\u9c37' + '\u9c38\u9c39\u9c42\u9c43\u9c44\u9c45\u9c46\u9c47\u9c48\u9c49\u9c4a\u9c4e\u9c4f\u9c50\u9c51\u9c52\u9c53\u9c54\u9c55\u9c56\u9c57\u9c58\u9c59\u9c5a\u9c5f\u9c60\u9c61\u9c62\u9c63\u9c64\u9c65\u9c66\u9c67\u9c6f\u9c70\u9c71\u9c72\u9c73\u9c7a\u9c7b\u9c7c\u9c7d\u9c7e\u9c7f\u9c80\u9c81\u9c87\u9c88\u9c89\u9c8a\u9c8b\u9c8c\u9c8d\u9c90\u9c91\u9c92\u9c93\u9c94\u9c95\u9c96\u9c97\u9c98\u9c99\u9c9a\u9c9b\u9c9f\u9ca2\u9ca3\u9ca4\u9ca5\u9ca6\u9ca7\u9ca8\u9caa\u9cac\u9cad\u9cae\u9caf\u9cb0\u9cb3\u9cb5\u9cb7\u9cb8\u9cb9\u9cba\u9cbb\u9cbc\u9cbd\u9cbe\u9cbf\u9cc0\u9cc1\u9cc2\u9cc3\u9cc5\u9cc8\u9cca\u9ccc\u9cce\u9ccf\u9cd3\u9cd7\u9cd8\u9cdd\u9ce1\u9ce3\u9ce4\u9ce5\u9ce6\u9ce7\u9ce8\u9ce9\u9cea\u9ceb\u9cef\u9cf0\u9cf1\u9cf2\u9cf3\u9cf4\u9cf5\u9cf6\u9cfd\u9cfe\u9cff\u9d00\u9d01\u9d02' + '\u9d03\u9d04\u9d05\u9d06\u9d07\u9d08\u9d09\u9d10\u9d11\u9d12\u9d13\u9d14\u9d15\u9d16\u9d17\u9d18\u9d19\u9d1a\u9d1b\u9d1d\u9d1e\u9d1f\u9d20\u9d21\u9d22\u9d23\u9d2c\u9d2d\u9d2e\u9d2f\u9d30\u9d31\u9d32\u9d33\u9d3b\u9d3c\u9d3d\u9d3e\u9d3f\u9d40\u9d41\u9d42\u9d43\u9d44\u9d45\u9d46\u9d47\u9d48\u9d49\u9d51\u9d52\u9d53\u9d54\u9d55\u9d56\u9d57\u9d5e\u9d5f\u9d60\u9d61\u9d65\u9d66\u9d67\u9d68\u9d6f\u9d70\u9d71\u9d72\u9d75\u9d76\u9d79\u9d7a\u9d7b\u9d7f\u9d83\u9d84\u9d85\u9d87\u9d88\u9d8d\u9d8e\u9d8f\u9d90\u9d92\u9d93\u9d94\u9d96\u9d97\u9d98\u9d99\u9d9a\u9da4\u9d9e\u9d9f\u9da0\u9da1\u9da5\u9da6\u9da7\u9da9\u9dac\u9dad\u9dae\u9daf\u9db0\u9db1\u9db2\u9db3\u9db4\u9db5\u9db6\u9db8\u9dba\u9dbb\u9dbd\u9dc3\u9dc9\u9dca\u9dcc\u9dcd\u9dcf\u9dd2\u9dd3\u9dd5\u9dd6\u9dd9\u9dda\u9ddb' + '\u9ddd\u9dde\u9ddf\u9de0\u9de1\u9de2\u9de3\u9de4\u9de5\u9de6\u9de9\u9dea\u9deb\u9dec\u9ded\u9dee\u9def\u9df0\u9df1\u9df2\u9df7\u9df8\u9df9\u9dfa\u9dfb\u9df3\u9dfc\u9e06\u9e07\u9e08\u9e09\u9e0a\u9e0b\u9e0c\u9e0d\u9e0e\u9e1a\u9e1b\u9e0f\u9e10\u9e11\u9e12\u9e13\u9e14\u9e15\u9e1c\u9e1d\u9e1e\u9e1f\u9e20\u9e21\u9e22\u9e23\u9e24\u9e25\u9e26\u9e27\u9e28\u9e29\u9e2a\u9e2b\u9e2c\u9e4a\u9e35\u9e36\u9e37\u9e38\u9e39\u9e3a\u9e3b\u9e3c\u9e3d\u9e3e\u9e3f\u9e40\u9e4b\u9e4c\u9e4d\u9e4e\u9e4f\u9e50\u9e51\u9e52\u9e53\u9e54\u9e55\u9e60\u9e61\u9e62\u9e63\u9e64\u9e65\u9e66\u9e67\u9e68\u9e73\u9e74\u9e75\u9e76\u9e77\u9e7c\u9e7d\u9e7e\u9e7f\u9e80\u9e87\u9e88\u9e89\u9e8d\u9e8e\u9e8f\u9e90\u9e91\u9e94\u9e95\u9e97\u9e98\u9e99\u9e9a\u9e9b\u9e9c\u9e9d\u9e9e\u9e9f\u9ea0\u9ea1\u9ea2\u9ea3' + '\u9ea4\u9ea5\u9ea6\u9ea7\u9ea8\u9ea9\u9eaa\u9eab\u9eac\u9ead\u9eae\u9eaf\u9eb0\u9eb1\u9eb2\u9eb3\u9eb4\u9eb5\u9eb6\u9eb7\u9eb8\u9eb9\u9eba\u9ebb\u9ebc\u9ebd\u9ebe\u9ebf\u9ec0\u9ec1\u9ec2\u9ec3\u9ec4\u9ec5\u9ec6\u9ec7\u9ec8\u9ec9\u9eca\u9ecb\u9ecc\u9ece\u9ecf\u9ed1\u9ed2\u9ed7\u9ed8\u9ed9\u9eda\u9edb\u9ee2\u9ee3\u9ee6\u9ee7\u9eed\u9eee\u9ef5\u9ef6\u9ef7\u9eff\u9f00\u9f01\u9f02\u9f03\u9f04\u9f08\u9f09\u9f0a\u9f0b\u9f0e\u9f0f\u9f10\u9f11\u9f12\u9f13\u9f15\u9f18\u9f19\u9f1a\u9f1b\u9f1c\u9f1d\u9f1e\u9f1f\u9f20\u9f22\u9f21\u9f23\u9f24\u9f25\u9f26\u9f27\u9f2a\u9f2b\u9f2c\u9f2d\u9f2e\u9f2f\u9f30\u9f31\u9f32\u9f35\u9f36\u9f37\u9f38\u9f3c\u9f3d\u9f3e\u9f3f\u9f40\u9f41\u9f42\u9f43\u9f44\u9f45\u9f4b\u9f4c\u9f4d\u9f4e\u9f4f\u9f50\u9f51\u9f61\u9f52\u9f53\u9f54\u9f55\u9f56' + '\u9f62\u9f63\u9f64\u9f65\u9f66\u9f67\u9f68\u9f69\u9f6a\u9f6b\u9f6c\u9f6d\u9f6e\u9f6f\u9f70\u9f71\u9f79\u9f7a\u9f7b\u9f7c\u9f7d\u9f7e\u9f7f\u9f80\u9f81\u9f82\u9f8b\u9f8c\u9f8d\u9f83\u9f8e\u9f8f\u9f90\u9f91\u9f92\u9f93\u9f94\u9f95\u9f96\u9f97\u9f98\u9f99\u9f9f\u9fa0\u9fa1\u9fa2\u9fa3\u9fa4\u9fa5\u9fa6\u9fa7\u9fa8\u9fa9\u9faa\u9fb4\u9fab\u9fb5\u9fb6\u9fb7\u9fb8\u9fb9\u9fba\u9fbb\u9fbc\u9fbd\u9fbe\u9fbf\u9fc0\u9fc6\u9fc7\u9fc8\u9fc9\u9fca\u9fcb\u9fcf\u9fd0\u9fd1\u9fd2\u9fd3\u9fd4\u9fd5\u9fd6\u9fd7\u9fd8\u9fde\u9fdf\u9fe0\u9fe1\u9fe2\u9fe3\u9fe8\u9fe9\u9fea\u9ff1\u9ff2\u9ff3\u9ff7\u9ff8\u9ffa\u9ffb\u9ffc\u9ffd\u9ffe\u9fff\ua000\ua001\ua002\ua003\ua004\ua005\ua006\ua007\ua008\ua009\ua00a\ua00b\ua00c\ua00d\ua00e\ua00f\ua010\ua011\ua012\ua013\ua014\ua015\ua016\ua017' + '\ua018\ua019\ua01a\ua01b\ua01c\ua01d\ua01e\ua01f\ua020\ua023\ua021\ua022\ua024\ua025\ua026\ua027\ua028\ua029\ua02a\ua02b\ua02c\ua02e\ua02f\ua030\ua033\ua036\ua037\ua038\ua039\ua03a\ua03c\ua03d\ua03e\ua03f\ua040\ua041\ua045\ua046\ua047\ua048\ua049\ua04a\ua04b\ua04c\ua04e\ua04f\ua050\ua052\ua053\ua054\ua055\ua056\ua057\ua058\ua05b\ua05c\ua05d\ua05e\ua05f\ua060\ua061\ua062\ua063\ua064\ua065\ua066\ua067\ua068\ua069\ua06a\ua06b\ua06c\ua075\ua076\ua077\ua078\ua079\ua07a\ua07b\ua07c\ua07d\ua07e\ua07f\ua080\ua081\ua082\ua083\ua084\ua085\ua086\ua087\ua088\ua089\ua08a\ua08b\ua08c\ua08d\ua095\ua096\ua097\ua098\ua099\ua09a\ua09b\ua09c\ua09d\ua09e\ua09f\ua0a0\ua0a1\ua0a2\ua0a3\ua0a4\ua0a5\ua0a6\ua0c4\ua0af\ua0b0\ua0b1\ua0b2\ua0b3\ua0b4\ua0b5\ua0b6\ua0b7\ua0b8\ua0b9\ua0ba' + '\ua0bb\ua0bc\ua0bd\ua0be\ua0c5\ua0c6\ua0c7\ua0c8\ua0c9\ua0ca\ua0cb\ua0cc\ua0cd\ua0ce\ua0cf\ua0d0\ua0d1\ua0d2\ua0d3\ua0d4\ua0de\ua0df\ua0e0\ua0e1\ua0e2\ua0e3\ua0e4\ua0e5\ua0e6\ua0e7\ua0e8\ua0e9\ua0ea\ua0eb\ua0ec\ua0ed\ua0ee\ua0ef\ua0f0\ua0f1\ua0f2\ua0fa\ua0fb\ua0fc\ua0fd\ua0fe\ua0ff\ua100\ua101\ua102\ua103\ua104\ua105\ua106\ua107\ua108\ua109\ua111\ua112\ua113\ua114\ua115\ua116\ua117\ua118\ua119\ua11a\ua11b\ua11c\ua11d\ua11e\ua11f\ua129\ua12a\ua12b\ua12c\ua12d\ua12e\ua12f\ua130\ua131\ua132\ua133\ua134\ua135\ua136\ua13c\ua13d\ua13e\ua13f\ua140\ua141\ua142\ua146\ua147\ua148\ua14d\ua14e\ua14f\ua150\ua152\ua153\ua154\ua155\ua158\ua159\ua15b\ua15d\ua15f\ua160\ua161\ua162\ua163\ua164\ua165\ua166\ua167\ua168\ua169\ua16a\ua16b\ua16c\ua16d\ua16e\ua16f\ua170\ua171\ua172' + '\ua173\ua174\ua175\ua176\ua177\ua178\ua179\ua17a\ua17b\ua17c\ua17e\ua17f\ua180\ua181\ua182\ua183\ua185\ua186\ua187\ua188\ua189\ua18a\ua18b\ua18c\ua18d\ua18e\ua18f\ua190\ua191\ua192\ua193\ua194\ua196\ua197\ua198\ua199\ua19a\ua19b\ua19c\ua19d\ua19e\ua1a0\ua1a2\ua1a3\ua1a4\ua1a5\ua1a6\ua1a7\ua1a8\ua1a9\ua1ac\ua1ad\ua1ae\ua1af\ua1b0\ua1b1\ua1b7\ua1b8\ua1b9\ua1ba\ua1bb\ua1c2\ua1c3\ua1ca\ua1cb\ua1cc\ua1cf\ua1d0\ua1d1\ua1d2\ua1d7\ua1d8\ua1d9\ua1da\ua1db\ua1dc\ua1dd\ua1df\ua1e0\ua1e1\ua1e3\ua1e5\ua1e6\ua1e7\ua1e8\ua1ea\ua1ec\ua1ee\ua1ef\ua1f0\ua1f3\ua1f5\ua1f6\ua1f8\ua1fa\ua1fc\ua1fd\ua200\ua201\ua202\ua203\ua204\ua205\ua206\ua207\ua208\ua209\ua210\ua211\ua212\ua213\ua214\ua215\ua216\ua217\ua218\ua219\ua21c\ua21d\ua21e\ua21f\ua220\ua221\ua222\ua226\ua227\ua228\ua229' + '\ua22a\ua22b\ua22c\ua231\ua232\ua233\ua234\ua223\ua235\ua23d\ua23e\ua23f\ua240\ua241\ua242\ua243\ua248\ua249\ua24a\ua24b\ua250\ua251\ua252\ua253\ua254\ua258\ua259\ua25a\ua25b\ua255\ua25d\ua25e\ua25f\ua260\ua261\ua266\ua26a\ua26c\ua26d\ua26e\ua26f\ua270\ua271\ua274\ua275\ua276\ua277\ua278\ua279\ua27a\ua27b\ua27f\ua281\ua282\ua283\ua284\ua285\ua286\ua287\ua288\ua28b\ua28c\ua28d\ua28e\ua28f\ua290\ua291\ua293\ua294\ua295\ua296\ua299\ua29c\ua29d\ua29e\ua29f\ua2a0\ua2a1\ua2a2\ua2a3\ua2aa\ua2ac\ua2ad\ua2ae\ua2af\ua2b0\ua2b1\ua2b6\ua2b7\ua2b8\ua2bc\ua2be\ua2bf\ua2c0\ua2c1\ua2c3\ua2c4\ua2c5\ua2c6\ua2ca\ua2cb\ua2cc\ua2cd\ua2ce\ua2cf\ua2d0\ua2d1\ua2d2\ua2d3\ua2d4\ua2d5\ua2d6\ua2d7\ua2d8\ua2d9\ua2da\ua2db\ua2dc\ua2dd\ua2de\ua2df\ua2e0\ua2e9\ua2ea\ua2eb\ua2ec\ua2ed\ua2ee' + '\ua2ef\ua2f0\ua2f1\ua2f2\ua2f3\ua2f4\ua2f5\ua2f6\ua2f7\ua2f8\ua2f9\ua2fa\ua2fb\ua2fc\ua2fd\ua2fe\ua2ff\ua300\ua301\ua302\ua303\ua304\ua305\ua306\ua307\ua314\ua315\ua316\ua317\ua318\ua319\ua31a\ua31b\ua31c\ua31d\ua308\ua31e\ua31f\ua320\ua321\ua322\ua323\ua324\ua325\ua326\ua327\ua328\ua329\ua32a\ua32b\ua32c\ua32d\ua32e\ua335\ua336\ua337\ua338\ua339\ua32f\ua33a\ua33b\ua33c\ua33d\ua33e\ua33f\ua340\ua341\ua342\ua343\ua344\ua345\ua346\ua347\ua348\ua349\ua34a\ua34b\ua34c\ua34d\ua34e\ua34f\ua350\ua351\ua352\ua35e\ua35f\ua360\ua361\ua362\ua363\ua364\ua365\ua366\ua367\ua368\ua369\ua36a\ua36b\ua36c\ua36d\ua36e\ua36f\ua370\ua371\ua372\ua373\ua374\ua375\ua376\ua377\ua378\ua379\ua37a\ua37b\ua37c\ua37d\ua37e\ua37f\ua393\ua394\ua395\ua396\ua397\ua380\ua398\ua353\ua399\ua39a' + '\ua39b\ua39c\ua39d\ua39e\ua39f\ua3a0\ua3a1\ua3a2\ua3a3\ua3a4\ua3a5\ua3a6\ua3a7\ua3a8\ua3a9\ua3aa\ua3ab\ua3ac\ua3ad\ua3ae\ua3af\ua3b0\ua3b1\ua3b2\ua3b3\ua3b4\ua3b5\ua3b6\ua3c2\ua3c3\ua3c4\ua3c5\ua3b7\ua3c6\ua3c7\ua3c8\ua3c9\ua3ca\ua3cb\ua3cc\ua3cd\ua3ce\ua3cf\ua3d0\ua3d1\ua3d2\ua3d3\ua3d4\ua3d5\ua3e0\ua3e1\ua3e2\ua3e3\ua3e4\ua3e5\ua3e6\ua3e7\ua3e8\ua3e9\ua3ea\ua3eb\ua3ec\ua3ed\ua3ee\ua3ef\ua3f0\ua3f1\ua3f2\ua3f3\ua3f4\ua3f5\ua3f6\ua3f7\ua405\ua406\ua407\ua408\ua409\ua40a\ua40b\ua40c\ua40d\ua40e\ua40f\ua410\ua411\ua412\ua413\ua414\ua415\ua416\ua417\ua41f\ua420\ua421\ua422\ua423\ua424\ua425\ua426\ua427\ua428\ua429\ua42a\ua434\ua42b\ua42c\ua42d\ua435\ua436\ua437\ua438\ua439\ua43d\ua43e\ua43f\ua440\ua441\ua442\ua444\ua445\ua448\ua44a\ua44b\ua44d\ua44e\ua44f\ua450' + '\ua451\ua452\ua453\ua454\ua457\ua458\ua459\ua45a\ua45b\ua45c\ua45d\ua45e\ua45f\ua460\ua461\ua462\ua463\ua467\ua468\ua469\ua46a\ua46b\ua46c\ua46d\ua46e\ua46f\ua470\ua471\ua472\ua473\ua474\ua475\ua476\ua477\ua478\ua479\ua47a\ua47b\ua47c\ua47d\ua47e\ua47f\ua480\ua481\ua482\ua485\ua486\ua487\ua488\ua489\ua48a\ua48b\ua48c\ua48d\ua48e\ua48f\ua490\ua491\ua492\ua493\ua494\ua495\ua4a6\ua496\ua497\ua498\ua499\ua49a\ua49b\ua49c\ua49d\ua49e\ua49f\ua4a0\ua4a1\ua4a2\ua4a7\ua4a8\ua4a9\ua4aa\ua4ab\ua4ac\ua4ad\ua4b1\ua4b2\ua4b3\ua4b4\ua4b5\ua4b6\ua4b7\ua4b8\ua4b9\ua4bc\ua4bd\ua4be\ua4bf\ua4c0\ua4c1\ua4c2\ua4c3\ua4c4\ua4c5\ua4c6\ua4c7\ua4c8\ua4c9\ua4ca\ua4cb\ua4cc\ua4cd\ua4ce\ua4cf\ua4d0\ua4d2\ua4d3\ua4d4\ua4d5\ua4d6\ua4d7\ua4de\ua4df\ua4e0\ua4e1\ua4e2\ua4e3\ua4e4\ua4e5\ua4e6' + '\ua4e7\ua4e8\ua4e9\ua4ea\ua4eb\ua4ec\ua4ed\ua4ee\ua4ef\ua4f0\ua502\ua4f1\ua4f2\ua4f3\ua4f4\ua503\ua504\ua505\ua506\ua507\ua508\ua509\ua50a\ua50b\ua50c\ua50d\ua50e\ua50f\ua510\ua511\ua512\ua513\ua514\ua515\ua516\ua517\ua518\ua519\ua51a\ua51b\ua51c\ua51d\ua51e\ua51f\ua520\ua533\ua534\ua535\ua536\ua537\ua538\ua539\ua53a\ua53b\ua53c\ua53d\ua53e\ua53f\ua540\ua541\ua542\ua543\ua544\ua545\ua546\ua547\ua548\ua549\ua54a\ua54b\ua54c\ua54d\ua54e\ua54f\ua55a\ua55b\ua55c\ua55d\ua55e\ua55f\ua560\ua561\ua562\ua563\ua564\ua565\ua566\ua567\ua568\ua569\ua56a\ua56b\ua56c\ua56d\ua56e\ua56f\ua570\ua571\ua572\ua573\u8bb2\ua574\ua57d\ua57e\ua57f\ua580\ua581\ua582\ua583\ua584\ua585\ua586\ua587\ua588\ua589\ua58a\ua58b\ua58c\ua58d\ua58e\ua58f\ua590\ua591\ua592\ua593\ua594\ua595\ua596' + '\ua597\ua598\ua599\ua59a\ua59b\ua59c\ua59d\ua59e\ua59f\ua5a0\ua5a1\ua5a2\ua5a3\ua5a4\ua5a5\ua5a6\ua5b1\ua5a7\ua5b2\ua5b3\ua5b4\ua5b5\ua5b6\ua5b7\ua5b8\ua5b9\ua5ba\ua5bb\ua5bc\ua5bd\ua5be\ua5bf\ua5c0\ua5c1\ua5c2\ua5c3\ua5c4\ua5c5\ua5c6\ua5c7\ua5c8\ua5c9\ua5ca\ua5cb\ua5d7\ua5d8\ua5d9\ua5da\ua5db\ua5dc\ua5dd\ua5de\ua5df\ua5e0\ua5e1\ua5e2\ua5e3\ua5e4\ua5e5\ua5e6\ua5e7\ua5e8\ua5e9\ua5ea\ua5eb\ua5ec\ua5ed\ua5ee\ua5ef\ua5f0\ua5f1\ua5f2\ua5f3\ua5f4\ua5f5\ua600\ua5f6\ua5f7\ua5f8\ua5f9\ua601\ua602\ua603\ua604\ua605\ua606\ua607\ua608\ua609\ua60a\ua60b\ua60c\ua60d\ua60e\ua60f\ua610\ua3f8\ua618\ua619\ua61a\ua61b\ua61c\ua61d\ua61e\ua61f\ua620\ua621\ua622\ua623\ua624\ua625\ua626\ua627\ua628\ua629\ua62a\ua62b\ua62c\ua62d\ua62e\ua62f\ua63c\ua630\ua631\ua632\ua63d\ua63e\ua63f' + '\ua640\ua641\ua642\ua643\ua644\ua645\ua646\ua647\ua648\ua649\ua64a\ua650\ua651\ua652\ua653\ua654\ua655\ua656\ua657\ua65a\ua65b\ua65d\ua65e\ua65f\ua661\ua662\ua663\ua664\ua665\ua666\ua667\ua66b\ua66c\ua66d\ua66e\ua66f\ua670\ua671\ua672\ua673\ua674\ua675\ua677\ua678\ua679\ua67a\ua67b\ua67c\ua67d\ua67e\ua67f\ua680\ua681\ua682\ua683\ua684\ua685\ua686\ua687\ua688\ua689\ua68a\ua68b\ua68c\ua68f\ua690\ua691\ua692\ua693\ua694\ua695\ua696\ua697\ua699\ua69a\ua69b\ua69c\ua69d\ua69e\ua69f\ua6a0\ua6a1\ua6a2\ua6a3\ua6a4\ua6a6\ua6a7\ua6a8\ua6ae\ua6a9\ua6aa\ua6ab\ua6ac\ua6af\ua6b0\ua6b1\ua6b2\ua6b3\ua6b4\ua6b5\ua6b6\ua6b8\ua6b9\ua6ba\ua6bb\ua6bc\ua6bd\ua6be\ua6bf\ua6c0\ua6c1\ua6c2\ua6c3\ua6c5\ua6c6\ua6c7\ua6c8\ua6c9\ua6ca\ua6cc\ua6ce\ua6cf\ua6d0\ua6d1\ua6d4\ua6d5\ua6d7\ua6d8' + '\ua6d9\ua6da\ua6db\ua6dd\ua6de\ua6e2\ua6e3\ua6e4\ua6e5\ua6e9\ua6ea\ua6eb\ua6ec\ua6ed\ua6ee\ua6ef\ua6f0\ua6f2\ua6f3\ua6f4\ua6f5\ua6f6\ua6f7\ua6f8\ua6fa\ua6fb\ua6fc\ua6fd\ua6fe\ua6ff\ua701\ua703\ua704\ua707\ua708\ua709\ua70a\ua70b\ua738\ua70c\ua710\ua711\ua712\ua713\ua716\ua717\ua718\ua71d\ua71e\ua720\ua721\ua725\ua726\ua72c\ua730\ua737\ua739\ua73a\ua73b\ua73c\ua73d\ua73e\ua73f\ua741\ua742\ua743\ua746\ua747\ua748\ua749\ua74a\ua74c\ua74d\ua74e\ua74f\ua750\ua757\ua758\ua759\ua75d\ua764\ua768\ua769\ua76d\ua770\ua771\ua772\ua773\ua774\u6a93\ua776\ua777\ua778\ua779\ua77a\ua77d\ua77e\ua77f\ua784\ua785\ua788\ua789\ua78a\ua78b\ua78c\ua78d\ua78e\ua792\ua793\ua794\ua795\ua796\ua79a\ua79b\ua79c\ua79d\ua79e\ua7a1\ua7a5\ua7a7\ua7a8\ua7a9\ua7aa\ua7ab\ua7ac\ua7ad\ua7be\ua7ae' + '\ua7af\ua7b0\ua7b1\ua7b3\ua7b4\ua7b6\ua7b8\ua7b9\ua7bb\ua7bc\ua7bd\ua7bf\ua7c0\ua7c1\ua7c2\ua7c3\ua7c4\ua7c5\ua7c6\ua7ca\ua7cb\ua7cc\ua7cd\ua7d1\ua7d3\ua7d4\ua7d5\ua7d6\ua7d8\ua7d9\ua7da\ua7db\ua7dc\ua7dd\ua7df\ua7e0\ua7e1\ua7e3\ua7e4\ua7e5\ua7e6\ua7e7\ua7e8\ua7e9\ua7ea\ua7ee\ua7f0\ua7f1\ua7f2\ua7f3\ua7f5\ua7f6\ua7f7\ua7f8\ua7fa\ua7fb\ua7fc\ua7fd\ua800\ua801\ua802\ua803\ua804\ua805\ua808\ua809\ua80e\ua80f\ua810\ua811\ua812\ua813\ua814\ua817\ua818\ua819\ua81c\ua81d\ua81e\ua81f\ua820\ua821\ua822\ua823\ua824\ua825\ua827\ua828\ua829\ua82c\ua82d\ua82e\ua82f\ua830\ua831\ua832\ua833\ua834\ua835\ua836\ua83a\ua83b\ua83c\ua83d\ua83e\ua83f\ua842\ua843\ua844\ua847\ua848\ua849\ua84a\ua84b\ua84e\ua84f\ua850\ua851\ua852\ua853\ua857\ua858\ua859\ua85a\ua860\ua861\ua865\ua867' + '\ua868\ua869\ua86a\ua86b\ua86c\ua86d\ua86e\ua86f\ua870\ua871\ua872\ua873\ua874\ua875\ua876\ua877\u526c\ua878\ua879\ua87a\ua87b\ua87c\ua87e\ua87f\ua880\ua881\ua884\ua885\ua886\ua889\ua88c\ua88d\ua88e\ua88f\ua891\ua892\ua894\ua895\u6a94\u5cce\u4336\u8604\u9026\u9268\u979c\ua120\ua156\u96b7\u629e\u47d6\u9820\u4a1b\u3d38\u56f4\u4824\u821c\u4e6c\u4100\u436c\u8d7f\u4a2e\u6a95\u71a0\u8411\u86c3\u8c77\u92d5\u727a\u5eac\u75d0\u754a\u3ec8\u5333\u5334\u838f\u8acb\u668c\u48ca\u7401\u99bd\ua3d6\u415c\u832d\u8490\u99ff\ua455\u3cfa\u3cfd\u3fef\u401c\u408d\u4191\u5a80\u6226\u6319\u765c\u76fd\u7df6\u7f24\u7f32\u7f48\u820a\u86c4\u9bd0\u9cb1\ua449\u6aeb\u63db\u73dc\u9971\u523d\u7a76\u67d6\u6d5a\u8e9c\ua3f9\ua464\ua483\ua484\ua4a3\ua4ba\ua4bb\ua698\ua7eb\ua7ec\u0000\u0000\u0000' + '\u3813\u3814\u3815\u3816\u3817\u3818\u3819\u381a\u381b\u381c\u381d\u381e\u381f\u3820\u3821\u3822\u3823\u3824\u3825\u3826\u3827\u3828\u3829\u382a\u382b\u382c\u382d\u382e\u382f\u3830\u3831\u3832\u3833\u3834\u3835\u3836\u3837\u3838\u3839\u383a\u383b\u383c\u383d\u383e\u383f\u3840\u3841\u3842\u3843\u3844\u3845\u3846\u3847\u3848\u3849\u384a\u384b\u384c\u384d\u384e\u384f\u3850\u3851\u3852\u3853\u3854\u3855\u3856\u3857\u3858\u3859\u385a\u385b\u385c\u385d\u385e\u385f\u3860\u3861\u3862\u3863\u3864\u3865\u3866\u3867\u3868\u3869\u386a\u386b\u386c\u386d\u386e\u386f\u3870\u3871\u3872\u3873\u3874\u3875\u3876\u3877\u3878\u3879\u387a\u387b\u387c\u387d\u387e\u387f\u3880\u3881\u3882\u3883\u3884\u3885\u3886\u3887\u3888\u3889\u388a\u388b\u388c\u388d\u388e\u388f\u3890\u3891\u3892' + '\u3893\u3894\u3895\u3896\u3897\u3898\u3899\u389a\u389b\u389c\u389d\u389e\u389f\u38a0\u38a1\u38a2\u38a3\u38a4\u38a5\u38a6\u38a7\u38a8\u38a9\u38aa\u38ab\u38ac\u38ad\u38ae\u38af\u38b0\u38b1\u38b2\u38b3\u38b4\u38b5\u38b6\u38b7\u38b8\u38b9\u38ba\u38bb\u38bc\u38bd\u38be\u38bf\u38c0\u38c1\u38c2\u38c3\u38c4\u38c5\u38c6\u38c7\u38c8\u38c9\u38ca\u38cb\u38cc\u38cd\u38ce\u38cf\u38d0\u38d1\u38d2\u38d3\u38d4\u38d5\u38d6\u38d7\u38d8\u38d9\u38da\u38db\u38dc\u38dd\u38de\u38df\u38e0\u38e1\u38e2\u38e3\u38e4\u38e5\u38e6\u38e7\u38e8\u38e9\u38ea\u38eb\u38ec\u38ed\u38ee\u38ef\u38f0\u38f1\u38f2\u38f3\u38f4\u38f5\u38f6\u38f7\u38f8\u38f9\u38fa\u38fb\u38fc\u38fd\u38fe\u38ff\u3900\u3901\u3902\u3903\u3904\u3905\u3906\u3907\u3908\u3909\u390a\u390b\u390c\u390d\u390e\u390f\u3910\u3911\u3912' + '\u3913\u3914\u3915\u3916\u3917\u3918\u3919\u391a\u391b\u391c\u391d\u391e\u391f\u3920\u3921\u3922\u3923\u3924\u3925\u3926\u3927\u3928\u3929\u392a\u392b\u392c\u392d\u392e\u392f\u3930\u3931\u3932\u3933\u3934\u3935\u3936\u3937\u3938\u3939\u393a\u393b\u393c\u393d\u393e\u393f\u3940\u3941\u3942\u3943\u3944\u3945\u3946\u3947\u3948\u3949\u394a\u394b\u394c\u394d\u394e\u394f\u3950\u3951\u3952\u3953\u3954\u3955\u3956\u3957\u3958\u3959\u395a\u395b\u395c\u395d\u395e\u395f\u3960\u3961\u3962\u3963\u3964\u3965\u3966\u3967\u3968\u3969\u396a\u396b\u396c\u396d\u396e\u396f\u3970\u3971\u3972\u3973\u3974\u3975\u3976\u3977\u3978\u3979\u397a\u397b\u397c\u397d\u397e\u397f\u3980\u3981\u3982\u3983\u3984\u3985\u3986\u3987\u3988\u3989\u398a\u398b\u398c\u398d\u398e\u398f\u3990\u3991\u3992' + '\u3993\u3994\u3995\u3996\u3997\u3998\u3999\u399a\u399b\u399c\u399d\u399e\u399f\u39a0\u39a1\u39a2\u39a3\u39a4\u39a5\u39a6\u39a7\u39a8\u39a9\u39aa\u39ab\u39ac\u39ad\u39ae\u39af\u39b0\u39b1\u39b2\u39b3\u39b4\u39b5\u39b6\u39b7\u39b8\u39b9\u39ba\u39bb\u39bc\u39bd\u39be\u39bf\u39c0\u39c1\u39c2\u39c3\u39c4\u39c5\u39c6\u39c7\u39c8\u39c9\u39ca\u39cb\u39cc\u39cd\u39ce\u39cf\u39d0\u39d1\u39d2\u39d3\u39d4\u39d5\u39d6\u39d7\u39d8\u39d9\u39da\u39db\u39dc\u39dd\u39de\u39df\u39e0\u39e1\u39e2\u39e3\u39e4\u39e5\u39e6\u39e7\u39e8\u39e9\u39ea\u39eb\u39ec\u39ed\u39ee\u39ef\u39f0\u39f1\u39f2\u39f3\u39f4\u39f5\u39f6\u39f7\u39f8\u39f9\u39fa\u39fb\u39fc\u39fd\u39fe\u39ff\u3a00\u3a01\u3a02\u3a03\u3a04\u3a05\u3a06\u3a07\u3a08\u3a09\u3a0a\u3a0b\u3a0c\u3a0d\u3a0e\u3a0f\u3a10\u3a11\u3a12' + '\u3a13\u3a14\u3a15\u3a16\u3a17\u3a18\u3a19\u3a1a\u3a1b\u3a1c\u3a1d\u3a1e\u3a1f\u3a20\u3a21\u3a22\u3a23\u3a24\u3a25\u3a26\u3a27\u3a28\u3a29\u3a2a\u3a2b\u3a2c\u3a2d\u3a2e\u3a2f\u3a30\u3a31\u3a32\u3a33\u3a34\u3a35\u3a36\u3a37\u3a38\u3a39\u3a3a\u3a3b\u3a3c\u3a3d\u3a3e\u3a3f\u3a40\u3a41\u3a42\u3a43\u3a44\u3a45\u3a46\u3a47\u3a48\u3a49\u3a4a\u3a4b\u3a4c\u3a4d\u3a4e\u3a4f\u3a50\u3a51\u3a52\u3a53\u3a54\u3a55\u3a56\u3a57\u3a58\u3a59\u3a5a\u3a5b\u3a5c\u3a5d\u3a5e\u3a5f\u3a60\u3a61\u3a62\u3a63\u3a64\u3a65\u3a66\u3a67\u3a68\u3a69\u3a6a\u3a6b\u3a6c\u3a6d\u3a6e\u3a6f\u3a70\u3a71\u3a72\u3a73\u3a74\u3a75\u3a76\u3a77\u3a78\u3a79\u3a7a\u3a7b\u3a7c\u3a7d\u3a7e\u3a7f\u3a80\u3a81\u3a82\u3a83\u3a84\u3a85\u3a86\u3a87\u3a88\u3a89\u3a8a\u3a8b\u3a8c\u3a8d\u3a8e\u3a8f\u3a90\u3a91\u3a92' + '\u3a93\u3a94\u3a95\u3a96\u3a97\u3a98\u3a99\u3a9a\u3a9b\u3a9c\u3a9d\u3a9e\u3a9f\u3aa0\u3aa1\u3aa2\u3aa3\u3aa4\u3aa5\u3aa6\u3aa7\u3aa8\u3aa9\u3aaa\u3aab\u3aac\u3aad\u3aae\u3aaf\u3ab0\u3ab1\u3ab2\u3ab3\u3ab4\u3ab5\u3ab6\u3ab7\u3ab8\u3ab9\u3aba\u3abb\u3abc\u3abd\u3abe\u3abf\u3ac0\u3ac1\u3ac2\u3ac3\u3ac4\u3ac5\u3ac6\u3ac7\u3ac8\u3ac9\u3aca\u3acb\u3acc\u3acd\u3ace\u3acf\u3ad0\u3ad1\u3ad2\u3ad3\u3ad4\u3ad5\u3ad6\u3ad7\u3ad8\u3ad9\u3ada\u3adb\u3adc\u3add\u3ade\u3adf\u3ae0\u3ae1\u3ae2\u3ae3\u3ae4\u3ae5\u3ae6\u3ae7\u3ae8\u3ae9\u3aea\u3aeb\u3aec\u3aed\u3aee\u3aef\u3af0\u3af1\u3af2\u3af3\u3af4\u3af5\u3af6\u3af7\u3af8\u3af9\u3afa\u3afb\u3afc\u3afd\u3afe\u3aff\u3b00\u3b01\u3b02\u3b03\u3b04\u3b05\u3b06\u3b07\u3b08\u3b09\u3b0a\u3b0b\u3b0c\u3b0d\u3b0e\u3b0f\u3b10\u3b11\u3b12' + '\u3b13\u3b14\u3b15\u3b16\u3b17\u3b18\u3b19\u3b1a\u3b1b\u3b1c\u3b1d\u3b1e\u3b1f\u3b20\u3b21\u3b22\u3b23\u3b24\u3b25\u3b26\u3b27\u3b28\u3b29\u3b2a\u3b2b\u3b2c\u3b2d\u3b2e\u3b2f\u3b30\u3b31\u3b32\u3b33\u3b34\u3b35\u3b36\u3b37\u3b38\u3b39\u3b3a\u3b3b\u3b3c\u3b3d\u3b3e\u3b3f\u3b40\u3b41\u3b42\u3b43\u3b44\u3b45\u3b46\u3b47\u3b48\u3b49\u3b4a\u3b4b\u3b4c\u3b4d\u3b4e\u3b4f\u3b50\u3b51\u3b52\u3b53\u3b54\u3b55\u3b56\u3b57\u3b58\u3b59\u3b5a\u3b5b\u3b5c\u3b5d\u3b5e\u3b5f\u3b60\u3b61\u3b62\u3b63\u3b64\u3b65\u3b66\u3b67\u3b68\u3b69\u3b6a\u3b6b\u3b6c\u3b6d\u3b6e\u3b6f\u3b70\u3b71\u3b72\u3b73\u3b74\u3b75\u3b76\u3b77\u3b78\u3b79\u3b7a\u3b7b\u3b7c\u3b7d\u3b7e\u3b7f\u3b80\u3b81\u3b82\u3b83\u3b84\u3b85\u3b86\u3b87\u3b88\u3b89\u3b8a\u3b8b\u3b8c\u3b8d\u3b8e\u3b8f\u3b90\u3b91\u3b92' + '\u3b93\u3b94\u3b95\u3b96\u3b97\u3b98\u3b99\u3b9a\u3b9b\u3b9c\u3b9d\u3b9e\u3b9f\u3ba0\u3ba1\u3ba2\u3ba3\u3ba4\u3ba5\u3ba6\u3ba7\u3ba8\u3ba9\u3baa\u3bab\u3bac\u3bad\u3bae\u3baf\u3bb0\u3bb1\u3bb2\u3bb3\u3bb4\u3bb5\u3bb6\u3bb7\u3bb8\u3bb9\u3bba\u3bbb\u3bbc\u3bbd\u3bbe\u3bbf\u3bc0\u3bc1\u3bc2\u3bc3\u3bc4\u3bc5\u3bc6\u3bc7\u3bc8\u3bc9\u3bca\u3bcb\u3bcc\u3bcd\u3bce\u3bcf\u3bd0\u3bd1\u3bd2\u3bd3\u3bd4\u3bd5\u3bd6\u3bd7\u3bd8\u3bd9\u3bda\u3bdb\u3bdc\u3bdd\u3bde\u3bdf\u3be0\u3be1\u3be2\u3be3\u3be4\u3be5\u3be6\u3be7\u3be8\u3be9\u3bea\u3beb\u3bec\u3bed\u3bee\u3bef\u3bf0\u3bf1\u3bf2\u3bf3\u3bf4\u3bf5\u3bf6\u3bf7\u3bf8\u3bf9\u3bfa\u3bfb\u3bfc\u3bfd\u3bfe\u3bff\u3c00\u3c01\u3c02\u3c03\u3c04\u3c05\u3c06\u3c07\u3c08\u3c09\u3c0a\u3c0b\u3c0c\u3c0d\u3c0e\u3c0f\u3c10\u3c11\u3c12' + '\u3c13\u3c14\u3c15\u3c16\u3c17\u3c18\u3c19\u3c1a\u3c1b\u3c1c\u3c1d\u3c1e\u3c1f\u3c20\u3c21\u3c22\u3c23\u3c24\u3c25\u3c26\u3c27\u3c28\u3c29\u3c2a\u3c2b\u3c2c\u3c2d\u3c2e\u3c2f\u3c30\u3c31\u3c32\u3c33\u3c34\u3c35\u3c36\u3c37\u3c38\u3c39\u3c3a\u3c3b\u3c3c\u3c3d\u3c3e\u3c3f\u3c40\u3c41\u3c42\u3c43\u3c44\u3c45\u3c46\u3c47\u3c48\u3c49\u3c4a\u3c4b\u3c4c\u3c4d\u3c4e\u3c4f\u3c50\u3c51\u3c52\u3c53\u3c54\u3c55\u3c56\u3c57\u3c58\u3c59\u3c5a\u3c5b\u3c5c\u3c5d\u3c5e\u3c5f\u3c60\u3c61\u3c62\u3c63\u3c64\u3c65\u3c66\u3c67\u3c68\u3c69\u3c6a\u3c6b\u3c6c\u3c6d\u3c6e\u3c6f\u3c70\u3c71\u3c72\u3c73\u3c74\u3c75\u3c76\u3c77\u3c78\u3c79\u3c7a\u3c7b\u3c7c\u3c7d\u3c7e\u3c7f\u3c80\u3c81\u3c82\u3c83\u3c84\u3c85\u3c86\u3c87\u3c88\u3c89\u3c8a\u3c8b\u3c8c\u3c8d\u3c8e\u3c8f\u3c90\u3c91\u3c92' + '\u3c93\u3c94\u3c95\u3c96\u3c97\u3c98\u3c99\u3c9a\u3c9b\u3c9c\u3c9d\u3c9e\u3c9f\u0000\u0000\u0000\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fbd\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc6\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcd\u0fce\u0fcf\u0fd0\u0fd1\u0fd2\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u3ca6\u3ca7\u3ca8\u3ca9\u3caa\u3cab\u3cac\u3cad\u3cae\u3caf\u3cb0\u3cb1\u3cb2\u3cb3\u3cb4\u3cb5\u3cb6\u3cb7\u3cb8\u3cb9\u3cba\u3cbb\u3cbc\u3cbd\u3cbe\u3cbf\u3cc2\u3cc0\u3cc3\u3cc1\u3cc4\u3cc5\u3cc6\u3cc7\u3cc8\u3cc9\u3cca\u3ccb\u3ccc\u3ccd\u3ca0\u3ca1\u3ca2\u3ca3\u3ca5\u3ca4\u0228\u0299' + '\u32c1\u32c2\u32c3\u32c4\u32c5\u32c6\u32c7\u32c8\u32c9\u32ca\u32cb\u32cc\u32cd\u32cf\u32d0\u32d1\u32d2\u32d3\u32d4\u32d5\u32d6\u32d7\u32d8\u32d9\u32da\u32db\u32dc\u32dd\u32de\u32df\u32e0\u32e2\u32e3\u32e4\u32e5\u32e6\u32e7\u32e8\u32e9\u32ea\u32eb\u32ec\u32ed\u32ee\u32ef\u32f0\u32f1\u32f2\u32f3\u32f4\u32f5\u32f6\u32f8\u32f9\u32fa\u32fb\u32fc\u32fd\u32fe\u32ff\u3300\u3301\u3302\u3303\u3304\u3305\u3306\u3307\u3308\u3309\u330a\u330b\u330d\u330e\u330f\u3310\u3311\u3312\u3313\u3314\u3315\u3316\u3317\u3319\u331a\u331b\u331c\u331d\u331e\u3321\u3322\u3324\u3325\u3326\u3327\u3328\u3329\u332b\u332c\u332d\u332e\u332f\u3330\u3331\u3332\u3333\u3334\u3336\u3337\u3338\u3339\u333b\u333c\u333d\u333e\u333f\u3340\u3341\u3342\u3343\u3344\u3345\u3346\u3347\u3348\u3349\u334a\u334b' + '\u334c\u334d\u334e\u334f\u3350\u3351\u3353\u3354\u3356\u3357\u3358\u3359\u335a\u335b\u335c\u335d\u335e\u335f\u3360\u3361\u3362\u3363\u3364\u3365\u3366\u3367\u3368\u3369\u336a\u336b\u336c\u336d\u336e\u336f\u3370\u3371\u3372\u3373\u3374\u3375\u3376\u3377\u3378\u3379\u337a\u337b\u337c\u337d\u337e\u337f\u3380\u3381\u3382\u3384\u3385\u3386\u3387\u3388\u3389\u338a\u338b\u338c\u338d\u338e\u338f\u3390\u3391\u3392\u3393\u3394\u3395\u3396\u3397\u3398\u3399\u339a\u339c\u339d\u339e\u339f\u33a0\u33a1\u33a4\u33a5\u33a6\u33a7\u33a8\u33a9\u33aa\u33ac\u33ad\u33ae\u33af\u33b0\u33b1\u33b2\u33b3\u33b4\u33b5\u33b6\u33b7\u33b8\u33b9\u33ba\u33bb\u33bc\u33bd\u33be\u33bf\u33c0\u33c1\u33c2\u33c3\u33c4\u33c5\u33c6\u33c7\u33c8\u33c9\u33ca\u33cb\u33cc\u33cd\u33ce\u33cf\u33d0\u33d1\u33d2' + '\u33d3\u33d4\u33d5\u33d6\u33d7\u33d8\u33d9\u33da\u33db\u33dc\u33dd\u33de\u33df\u0229\u029a\u027f\u331f\u3335\u3355\u32ce\u32e1\u32f7\u330c\u3318\u3320\u3323\u332a\u3352\u3383\u339b\u33a3\u33ab\u10c0\u10f2\u116b\u11bf\u1203\u1243\u127c\u12b1\u12e7\u131c\u333a\u33a2\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u1bc0\u1bbf\u1bc6\u1bc5\u1bca\u1bc9\u1be4\u1be3\u1beb\u1be9\u1c7d\u1c7a\u1c98\u1c97\u1cc8\u1cc7\u1cd1\u1cd0\u1ce2\u1ce1\u1ceb\u1cea\u1cee\u1cec\u1cfa\u1cf9\u1cff\u1cfe\u1d03\u1d02\u1d13\u1d12\u1ca1\u1ca0\u1b9c\u1b9b\u1c0e\u1c0d\u1c19\u1c18\u1c38\u1c31\u1c39\u1c32\u1c3a\u1c33\u1c34\u00c4\u0000\u0000\u0000\u0417\u1bab\u1bd4\u1be1\u1c6c\u1ccd\u1cd3\u1cd8\u1c93\u0059\u005a\u0463\u1cca' + '\u1b98\u1b97\u1bd2\u1bd1\u1bb7\u1bb6\u1cb8\u1cb7\u1bce\u1bcd\u1c68\u1c67\u1c62\u1c61\u1ca3\u1ca2\u1ca7\u1ca6\u1cb0\u1caf\u1c91\u1c90\u1cc3\u1cc2\u1c3b\u1c35\u1c3c\u1c36\u1ccf\u1cda\u1c81\u1cf4\u33e0\u33e1\u33e2\u33e3\u33e4\u33e5\u33e6\u33e7\u33e8\u33e9\u33ea\u33eb\u33ec\u33ed\u33ee\u33ef\u33f0\u33f1\u33f2\u33f3\u33f4\u33f5\u33f6\u33f7\u33f8\u33f9\u33fa\u33fb\u33fc\u33fd\u33fe\u33ff\u3400\u3401\u3402\u3403\u3404\u3405\u3406\u3407\u3408\u3409\u340a\u340b\u340c\u340d\u340e\u340f\u3410\u3411\u3412\u3413\u3414\u3415\u3416\u3417\u3418\u3419\u341a\u341b\u341c\u341d\u341e\u341f\u3420\u3421\u3422\u3423\u3424\u3425\u3426\u3427\u3428\u3429\u342a\u342b\u342c\u342d\u342e\u342f\u0145\u0146\u02d4\u029b\u0265\u022a\u0239\u0280\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u1972\u1971\u197e\u197d\u1532\u1531\u180b\u180a\u195e\u195d\u1960\u195f\u1962\u1961\u14be\u17d0\u138a\u1389\u138d\u138c\u138f\u138e\u1393\u1392\u1383\u1382\u1395\u1394\u1401\u1400\u15cb\u15ca\u15cd\u15cc\u15cf\u15ce\u1602\u1601\u1605\u1604\u1711\u1710\u1707\u1706\u16f0\u16ef\u1738\u1737\u173e\u173d\u1740\u173f\u174f\u174e\u1751\u1750\u1785\u1783\u17a4\u17a3\u18a6\u18a5\u18a3\u18a2\u193a\u1939\u1949\u1948\u194b\u194a\u194f\u194e\u1954\u1953\u1956\u1955\u1959\u1957\u195a\u1443\u161a\u1653\u168f\u17a1\u17a2\u1819\u195b\u1423\u1422\u14b5\u14b4\u14e4\u14f8\u14f7' + '\u161d\u161c\u1777\u1776\u17c1\u17c0\u1800\u17ff\u0544\u0545\u0546\u1974\u1973\u187c\u1618\u1975\u1689\u1688\u13f7\u13f6\u13f8\u1527\u13c7\u13c6\u14c1\u14c0\u134c\u134a\u16ae\u16ac\u1836\u1834\u14e2\u14e1\u15af\u15ae\u166c\u166b\u176d\u176c\u17b9\u17b8\u1529\u14a0\u14eb\u1611\u1574\u174d\u15d1\u181b\u1598\u18eb\u13cf\u13cd\u1714\u1713\u1874\u1873\u139c\u139b\u1581\u1580\u187a\u1879\u0000\u0000\u18cc\u18cb\u13f9\u17d6\u192e\u1438\u1437\u17d2\u17d1\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u1530\u152f\u1577\u1519\u16ee\u1883\u14c8\u1741\u1651\u1576\u1652' + '\u2866\u2867\u2868\u2869\u286a\u286b\u286c\u286e\u286f\u2870\u2871\u0176\u2872\u2873\u2874\u2875\u2876\u2877\u2878\u2879\u287a\u287b\u287c\u287d\u287e\u287f\u2880\u2881\u2882\u2883\u2884\u2885\u2886\u2887\u2888\u2889\u288a\u288b\u288c\u288d\u0571\u0572\u0573\u0574\u286d\u0000\u0000\u0000\u1025\u1026\u1027\u1028\u1029\u102a\u0575\u0576\u107c\u0577\u0000\u0000\u0000\u0000\u0000\u0000\u2a73\u2a74\u2a75\u2a76\u2a77\u2a78\u2a79\u2a7a\u2a7f\u2a80\u2a81\u2a82\u2a83\u2a84\u2a85\u2a86\u2a87\u2a88\u2a89\u2a8a\u2a8c\u2a8d\u2a8e\u2a8f\u2a92\u2a95\u2a96\u2a98\u2a99\u2a9c\u2aa1\u2aa2\u2aa3\u2aa4\u2a9d\u2a9e\u2a9f\u2aa0\u2aa5\u2a8b\u2a90\u2a7b\u2a7c\u2a7d\u2a7e\u2a91\u2a97\u2a9a\u2a9b\u2a93\u2a94\u2aa6\u04b6\u04b7\u02a9\u02aa\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0177\u0189\u288e\u288f\u2890\u2891\u2892\u2893\u2894\u2895\u2896\u2897\u2898\u2899\u289a\u289b\u289c\u289d\u289e\u289f\u28a0\u28a1\u28a2\u28a3\u28a4\u28a5\u28a6\u28a7\u28a8\u28a9\u28aa\u28ab\u28ac\u28ad\u28ae\u28af\u28b0\u28b1\u28b2\u28b3\u28b4\u28b5\u28b6\u28b7\u28b8\u28b9\u28ba\u28bb\u28bc\u28bd\u28be\u28bf\u28c0\u28c1\u28c2\u28c3\u28c4\u28c5\u28c6\u28c7\u28c8\u28c9\u28ca\u28cb\u28cc\u28cd\u28ce\u28cf\u28d0\u0161\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u02a5\u02a6\u10c1\u10f3\u116c\u11c0\u1204\u1244\u127d\u12b2\u12e8\u131d\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u258e\u258f\u2590\u2591\u2592\u2593\u0474\u0475\u0476\u2594\u0477\u2532\u2548\u25a9' + '\u10c2\u10f4\u116d\u11c1\u1205\u1245\u127e\u12b3\u12e9\u131e\u2b9d\u2b9e\u2b9f\u2ba0\u2ba1\u2ba2\u2ba3\u2ba4\u2ba5\u2ba6\u2ba7\u2ba8\u2ba9\u2baa\u2bab\u2bac\u2bad\u2bae\u2baf\u2bb0\u2bb1\u2bb2\u2bb3\u2bb4\u2bb5\u2bb6\u2bb7\u2bb8\u2bb9\u2bba\u2bbb\u2bbc\u2bbd\u01a3\u01a4\u01a5\u04bc\u02ad\u2b79\u2b7a\u2b7b\u2b7c\u2b7d\u2b7e\u2b7f\u2b80\u2b81\u2b82\u2b83\u2b84\u2b85\u2b86\u2b87\u2b88\u2b89\u2b8a\u2b8b\u2b8c\u2b8d\u2b8e\u2b8f\u2b90\u2b91\u2b92\u2b93\u2b94\u2b95\u2b96\u2b97\u2b98\u2b99\u2b9a\u2b9b\u2b9c\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u02d5\u34ef\u34f0\u34f1\u34f2\u34f3\u34f4\u34f5\u34f6\u34f7\u34f8\u34f9\u34fa\u34fb\u34fc\u34fd\u34fe\u34ff\u3500\u3501\u3502\u3503\u3504\u3505\u3506\u3507\u3508\u3509\u350a\u350b\u0000\u0000\u0000' + '\u0162\u0178\u018f\u018a\u2dfe\u2dff\u2e00\u2e01\u2e02\u2e03\u2e04\u2e05\u2e06\u2e07\u2e08\u2e09\u2e0a\u2e0b\u2e0c\u2e0d\u2e0e\u2e0f\u2e10\u2e11\u2e12\u2e13\u2e14\u2e15\u2e16\u2e17\u2e18\u2e19\u2e1a\u2e1b\u2e1c\u2e1d\u2e1e\u2e1f\u2e20\u2e21\u2e22\u2e23\u2e24\u2e26\u2e27\u2e29\u2e2a\u2e2b\u2e2c\u2e2d\u2e2e\u0153\u2e2f\u2e30\u2e32\u2e33\u2e34\u2e35\u2e37\u2e38\u2e31\u2e36\u2e25\u2e28\u2e39\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u0261\u02b8\u02b9\u02d0\u02d1\u02d2\u02d3\u0000\u1057\u10c3\u10f5\u116e\u11c2\u1206\u1246\u127f\u12b4\u12ea\u131f\u0000\u0000\u0000\u0000\u04bd\u04be\u2bc7\u2bcf\u2bd9\u2bef\u2c08\u2c54\u1058\u2be1\u2c03\u2bc5\u2bc8\u2bd4\u2bdb\u2be9\u2bec\u2bf0\u10c4\u10f6\u116f\u11c3\u1207\u1247\u1280\u12b5\u12eb\u1320\u2c25\u2bf5\u2bf8\u2c06\u2c09\u0000' + '\u2d73\u2d74\u2d75\u2d76\u2d77\u2d78\u2d79\u2d7a\u2d7b\u2d7c\u2d7d\u2d7e\u2d7f\u2d80\u2d81\u2d82\u2d83\u2d84\u2d85\u2d86\u2d87\u2d88\u2d89\u2d8a\u2d8b\u2d8c\u2d8d\u2d8e\u2d8f\u2d90\u2d91\u2d92\u2d93\u2d94\u2d95\u2d96\u2d97\u2d98\u2d99\u2d9a\u2d9b\u2da0\u2da1\u2da2\u2da3\u2da4\u2da5\u2da6\u2da7\u2da8\u2da9\u2d9c\u2d9d\u2d9e\u2d9f\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2daa\u2dab\u2dac\u2dad\u2dae\u2daf\u2db0\u2db1\u2db2\u2db3\u2db4\u2db5\u2db6\u2db7\u0000\u0000\u10c5\u10f7\u1170\u11c4\u1208\u1248\u1281\u12b6\u12ec\u1321\u0000\u0000\u04bf\u02ba\u02bb\u02bc\u2bc4\u2bcd\u2bd0\u2bd3\u2bda\u2be0\u2be4\u2be6\u2be8\u2beb\u2bf7\u2bfb\u2c1d\u2c20\u2c22\u2c01\u1059\u2c23\u2bd6\u2c0f\u2c6c\u2c6d\u2c6e\u05a0\u05a1\u05a2\u2c10\u2c69\u2c6a\u2c6b\u2bd1\u2bdd' + '\u2985\u2986\u2987\u2988\u2989\u298a\u298b\u298c\u298d\u298e\u298f\u2990\u2991\u2992\u2993\u2994\u2995\u2996\u2997\u2998\u2999\u299a\u299b\u299c\u299d\u299e\u299f\u29a0\u29a1\u29a2\u29a3\u29a4\u29a5\u29a6\u29a7\u29a8\u29a9\u29aa\u29ab\u29ac\u29ad\u29ae\u29af\u29b0\u29b1\u29b2\u29b3\u29b4\u29b5\u29b6\u29b7\u29b8\u29b9\u29ba\u29bb\u29bc\u29bd\u29be\u29bf\u29c0\u29c1\u29c2\u29c3\u01a0\u29c4\u01a1\u29c5\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u29c6\u29c7\u105a\u0481\u0482\u2843\u2844\u2845\u2846\u2847\u2848\u2849\u284a\u284b\u284c\u284d\u2856\u2857\u2858\u2859\u285a\u02bd\u0281\u2827\u105b\u105c\u285b\u2865\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u23ac\u23ad\u23ae\u23af\u23b0\u23b1\u0000\u0000\u2473\u2474\u2475\u2476\u2477\u2478\u0000\u0000\u2454\u2455\u2456\u2457\u2458\u2459\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u0000\u24ce\u24cf\u24d0\u24d1\u24d2\u24d3\u24d4\u0000\u13a8\u13a0\u1487\u1488\u148c\u14bf\u14ed\u1612\u160d\u160f\u1650\u168e\u1693\u16f4\u16fd\u1703\u16f9\u16f7\u16f8\u16fa\u16fb\u177e\u1782\u1799\u179a\u1795\u179b\u179c\u179d\u17e0\u1868\u1872\u186d\u186e\u1877\u18ea\u18ec\u18ed\u18e6\u18e7\u18e8\u18e9\u190f\u0547\u1533\u1613\u160c\u1878\u1950\u1951\u1705\u1952\u13ad\u1aee\u1432\u1808\u17a0\u18d1\u04f8\u04f9\u0000\u0000\u0000\u0000\u2ee2\u2ee4\u2ee6\u2ee8\u2eea\u2eec\u2eee\u2ef0\u2ef2\u2ef4\u2ef6\u2ef8\u2efa\u2efc\u2efe\u2f00' + '\u2f02\u2f04\u2f06\u2f08\u2f0a\u2f0c\u2f0e\u2f10\u2f12\u2f14\u2f16\u2f18\u2f1a\u2f1c\u2f1e\u2f20\u2f22\u2f24\u2f26\u2f28\u2f2a\u2f2c\u2f2e\u2f30\u2f32\u2f34\u2f36\u2f38\u2f3a\u2f3c\u2f3e\u2f40\u2f42\u2f44\u2f46\u2f48\u2f4a\u2f4c\u2f4e\u2f50\u2f52\u2f54\u2f56\u2f58\u2f5a\u2f5c\u2f5e\u2f60\u2f62\u2f64\u2f66\u2f68\u2f6a\u2f6c\u2f6e\u2f70\u2f72\u2f74\u2f76\u2f78\u2f7a\u2f7c\u2f7e\u2f80\u2828\u2829\u282a\u282b\u282c\u282d\u282e\u282f\u2830\u2831\u2832\u2833\u2834\u2835\u2836\u2837\u2838\u2839\u283a\u283b\u283c\u283d\u283e\u283f\u2840\u2841\u2842\u285c\u285d\u285e\u285f\u2860\u2861\u2862\u2863\u284e\u284f\u2850\u2851\u2852\u2853\u2854\u2855\u02be\u0191\u2864\u0000\u0000\u10c6\u10f8\u1171\u11c5\u1209\u1249\u1282\u12b7\u12ed\u1322\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u3589\u358a\u358b\u358c\u358d\u358e\u358f\u3590\u3591\u3592\u3593\u3594\u3595\u3596\u3597\u3598\u3599\u359a\u359b\u359c\u359d\u359e\u359f\u0000\u0000\u0000\u0000\u3615\u3616\u3617\u3618\u3619\u361a\u361b\u361c\u361d\u361e\u361f\u3620\u3621\u3622\u3623\u3624\u3625\u3626\u3627\u3628\u3629\u362a\u362b\u362c\u362d\u362e\u362f\u3630\u3631\u3632\u3633\u3634\u3635\u3636\u3637\u3638\u3639\u363a\u363b\u363c\u363d\u363e\u363f\u3640\u3641\u3642\u3643\u3644\u3645\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\uab2c\ua9c8\uab3e\uab33\uaa0a\ua8a9\ua915\uabe6\uabe7\ua93f\uab62\ua91e\ua93e\ua998\uaa58\uaab8\uaaf6\uaafc\uab06\uab54\ua9dd\ua9fd\uaa18\uaa3c\uaaec\uab5b\uabaf\ua8b4\ua90e\ua9e2\uaa1f\uaaf5\uabc3\ua960\uaa10\uaaf1\uab09\ua9a0\uaad1\uaafd\ua971\ua9cd\uaa01\uaa32\uab57\ua8c6\ua8e0\ua8f7\ua9aa\ua9e1\uaa1e\uaa61\uaac0\uaaf3\uaaf9\uab3c\uab87\uabbd\uabc2\uaa6d\uaa81\uaaa5\uaae9\uab67\uabc7\uab1c\ua934\ua978\uaa98\uaaca\uaa2c\uaa6f\uab31\uab86\ua933\ua959\ua9e0\uaa04\uaa0c\uaaa3\uaaab\uab75\ua8f6\uaad0\ua8e3\ua8e2\uaa8a\uaaa6\uaaeb\uab77\uab27\ua9a1\ua9de\uab20\ua8ac\ua94b\ua988\uaa37\uaa50\ua8fd\uaa71\ua8c9\ua982\ua8a2\ua9fa\ua9b0\uaaa2\ua912\ua930\uaa65\uaaed\uab17\ua9ec\uab47\ua9f9\ua9a3\uaae6\ua9a5\uaa4f\ua8bc\ua8d4\ua8e1\ua9da\uaa9e\uaade\uab1b\uab61\ua8f9' + '\ua91a\ua941\ua975\ua9b7\uaa11\uaa72\uab6f\uabb0\uabc9\uabd1\ua8f0\ua9c6\ua9e6\uab44\ua96a\ua995\ua999\ua9a9\uaa0f\uaa19\uaa42\uaa88\uaaa7\uaac9\uab40\uaaef\uab4e\uab68\ua8ec\ua8f2\ua91b\uaa17\uab03\uab18\ua970\ua987\ua9a4\ua9ea\uaa97\uaa35\ua8c1\ua929\ua94c\ua961\ua989\uaa3b\uaa40\uaabd\uaac8\uab65\uab85\uab88\uab9a\ua8c7\uaa85\uab5d\uab7e\ua98d\ua8b6\ua8cb\ua94d\ua958\ua9b4\ua9df\uaa1c\uaa57\uaaf0\uab53\uabe1\ua9c3\uab74\ua8ef\ua9d4\ua9d6\ua9ff\uaa07\uaa3e\uaa4e\uaa6c\uaaa1\uab9d\ua8d7\ua99b\uab78\ua8ca\ua95f\uaa05\uab41\ua981\ua98f\ua9d7\uaa38\uab7a\ua8ed\ua918\ua95b\ua9bf\ua9d2\ua9dc\ua9fb\uaa3d\uaa54\uaab7\uab04\uab05\uab5f\uab80\ua901\uaa08\ua919\uaa1d\uaa43\uaaf2\uab7b\uabbe\uabca\ua9d5\uaa03\uaad3\uaa8f\uaa93\uaa9b\uaa31\uaa16\uab26\ua8c0\uaae7\ua8ee' + '\ua8eb\ua96f\ua9a2\uaa9d\ua949\ua9fe\ua9c5\uab43\uaaff\uab76\uab0e\ua973\ua8cf\ua922\u441d\u48f9\ua92e\u5023\ua9c1\u6003\u6072\ua8e4\uaa33\uaa5d\uaa76\uaa7f\uaa80\uaa84\uab8b\uaa9c\uaabe\u8605\uaaf4\u8789\uab1e\u9034\u938c\uab4f\uab59\u9742\u979d\u9b9c\uaba7\uaba8\uaba9\uabc1\uab58\uab7d\ua8c8\ua8cc\ua8d1\ua8f5\ua8f8\ua907\ua920\ua924\ua925\ua92d\ua931\ua95a\ua95c\ua98b\ua992\ua993\ua996\ua9ae\ua9ba\ua9c4\ua9db\uaa02\uaa06\uaa0d\uaa1a\uaa21\uaa3f\uaa6e\uaa77\uaa7a\uaa79\uaa7b\uaa7c\uaa7d\uaa82\uaa83\uaa8b\uaa8d\uaa95\uaaa8\uaaaa\uaaac\uaab6\uaac3\uaad5\uaae1\uaae2\uaaee\uab07\uab10\uab22\uab24\uab34\uab35\uab4a\uab50\uab81\uab95\uab9b\ua98a\uabf4\uaada\u0000\u0000\ua8a4\ua8df\ua8d3\ua8c5\ua8d0\ua8d9\ua8f4\ua8fb\ua921\ua91d\ua91f\ua923\ua92f\ua932\ua93d\ua940' + '\ua944\ua945\ua972\ua974\ua97f\ua983\ua98c\ua990\ua98e\ua994\ua991\ua997\ua99c\ua9a6\ua9a7\ua9a8\ua9af\ua9c2\ua9ce\ua9cf\ua9d3\ua9e7\ua9ed\uaa00\uaa0b\uaa09\uaa0e\uaa12\uaa1b\uaa68\uaa24\uaa30\uaa34\uaa41\uaa46\uaa4d\uaa55\uaa56\uaa5e\uaa5f\uaa64\uaa67\uaa66\uaa70\uaa8e\uaa96\uaa9a\uaaa4\uaaa9\uaaaf\uaac4\uaae8\uaaea\uaafb\uab08\uab0d\uab11\uab19\uab1f\uab1a\uab23\uab21\uab1d\uab25\uab28\uab36\uab42\uab52\uab5c\uab66\uab79\uab82\uab8c\uab91\uab96\uab99\uab9c\uabb6\uabe8\uabf2\uabf1\uabf3\uabee\uabef\uabf0\uabf5\uabf6\uabf7\uabda\uabe4\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' + '\u14b7\u14ba\u14bb\u14b8\u14b9\u17cb\u17ce\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u1e52\u1e4f\u1e50\u1e68\u1e51\u0000\u0000\u0000\u0000\u0000\u1e9a\u00d6\u1e9e\u1eb2\u1e7c\u1e8a\u1e8d\u1ea0\u1ea6\u1ea9\u1ebe\u1ec7\u0678\u1ec2\u1ec1\u1ec5\u1ec4\u1e7d\u1e7e\u1e7f\u1e83\u1e87\u1e8b\u1e8e\u1e91\u1e95\u0000\u1e98\u1e9b\u1ea3\u1ea2\u1ea7\u0000\u1eab\u0000\u1eae\u1eb0\u0000\u1eb6\u1eb5\u0000\u1eba\u1ebc\u1ebf\u1ec3\u1ec8\u1e90\u1e84\u1ea4\u1eb7\u1e80\u1ef0\u1eef\u1f4e\u1f4d\u1f4b\u1f4c\u1f53\u1f52\u1f50\u1f51\u1f58\u1f57\u1f55\u1f56\u1fa9\u1fa8\u1fa6\u1fa7\u1fb0\u1faf\u1fad\u1fae\u1fa4\u1fa3\u1fa1\u1fa2\u212a\u2129\u2127\u2128\u2131\u2130\u212e\u212f\u1fd1\u1fd0\u1fce\u1fcf\u1fcc\u1fcb\u1fc9\u1fca\u1fd6\u1fd5\u1fd3\u1fd4\u1fdc\u1fdb' + '\u1fd9\u1fda\u201a\u2019\u2017\u2016\u201e\u201d\u2011\u2010\u2038\u2037\u202f\u202e\u216c\u216b\u2169\u216a\u217c\u217b\u2179\u217a\u2189\u2188\u2186\u2187\u2183\u2182\u2180\u2181\u2208\u2207\u220e\u220d\u220b\u220c\u2233\u2232\u222c\u222b\u2229\u222a\u2227\u2226\u2224\u2225\u2292\u2291\u2295\u2294\u0554\u0555\u0556\u0557\u0558\u0559\u055a\u055b\u055c\u055d\u055e\u055f\u0560\u0561\u0562\u0563\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2175\u2174\u2172\u2173\u2243\u2242\u2240\u223f\u2248\u2247\u2245\u224f\u224e\u223d\u223c\u224b\u224a\u2286\u2285\u2283\u2284\u2256\u2257\u1f00\u1eff\u1f10\u1f0f\u1f12\u1f11\u1f16\u1f15\u1f14\u1f13\u1f18\u1f17\u1f22\u1f21\u1f20\u1f1c\u1f1a\u1f19\u227f\u227e\u227c\u227d' + '\u1f02\u1f04\u1f0b\u1f1d\u1f1f\u1f36\u1f38\u1f3b\u1f42\u1f47\u1f49\u1f6e\u1f73\u1f78\u1f81\u1f8b\u1f8d\u1f93\u1f99\u1f9d\u1f9f\u1fba\u1fbf\u1fe7\u1fea\u1ff7\u1ff8\u1ffa\u2049\u204e\u2052\u2059\u2099\u20a5\u20b2\u20b4\u20b8\u20be\u20cd\u20d0\u20e0\u20e9\u20ee\u20fd\u20ff\u2114\u2116\u2118\u211c\u211f\u2121\u213b\u213e\u2144\u2146\u2152\u2154\u2156\u2158\u215c\u2160\u2165\u2167\u219f\u21a6\u21ac\u21b2\u21b8\u21ba\u21ca\u21d0\u21d6\u21dc\u21de\u21df\u21e9\u21f1\u21f6\u21fc\u2203\u2205\u221c\u221e\u2221\u2222\u2264\u2267\u226a\u2270\u2278\u227a\u200e\u2027\u225b\u01cb\u01cc\u01ce\u01d0\u01d2\u01d3\u1f06\u1f07\u1f0a\u1f0c\u1f1b\u1f1e\u1f3d\u1f3e\u1f41\u1f43\u1f46\u1f48\u1f7c\u1f7d\u1f80\u1f87\u1f8a\u1f8c\u1f94\u1f95\u1f98\u1f9a\u1f9c\u1f9e\u211e\u2120\u2143\u2145' + '\u2151\u215b\u215f\u2164\u2166\u21b1\u21b7\u21b9\u21c8\u21db\u21f7\u21f8\u21fb\u21ff\u2202\u2204\u225a\u226b\u226c\u226f\u2274\u2277\u2279\u1f01\u1f03\u1f05\u1f08\u1f0d\u1f35\u1f37\u1f3a\u1f3f\u1f44\u1f6d\u1f72\u1f77\u1f7e\u1f88\u1f96\u1fb9\u1fbe\u1fe6\u1fe9\u1ff6\u1ff9\u2047\u204c\u2050\u2057\u2098\u209d\u20a4\u20b1\u20b3\u20b7\u20bd\u20cc\u20de\u20e8\u20ed\u20fc\u20fe\u2113\u2115\u2117\u211b\u213a\u213d\u2153\u2155\u2157\u2159\u215d\u219e\u21a5\u21ab\u21af\u21b6\u21c9\u21cf\u21d5\u21da\u21e8\u21f0\u21f5\u21f9\u2200\u221b\u221d\u221a\u2263\u2266\u2269\u226d\u2275\u1f09\u1f0e\u1f40\u1f45\u1f7f\u1f89\u1f97\u1f9b\u2058\u2060\u207d\u2085\u215a\u215e\u21b0\u21fa\u2201\u226e\u2276\u01cd\u01cf\u01d1\u20d6\u20d8\u20f4\u20f6\u2104\u2106\u2062\u2064\u2087\u2089\u1fee' + '\u1ff0\u1fc5\u1fc7\u1ffc\u1ffe\u20a9\u20ab\u20c0\u20c2\u206d\u2072\u2079\u207f\u207b\u2056\u209f\u20bc\u20d5\u20d7\u20f3\u20f5\u2103\u2105\u2061\u2063\u2086\u2088\u1fed\u1fef\u1fc4\u1fc6\u1ffb\u1ffd\u20a8\u20aa\u20bf\u20c1\u206c\u2071\u2078\u207e\u207a\u2055\u209e\u20bb\u206a\u206f\u2076\u207c\u205f\u2084\u20ce\u2048\u204d\u2051\u206b\u2070\u2077\u20cf\u20df\u1f2a\u1f2b\u0400\u0401\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u1f6f\u1f75\u1f74\u1f76\u1f79\u1f82\u1f83\u1f84\u1fc1\u1fc0\u1fec\u1feb\u204f\u204a\u204b\u205c\u205b\u205a\u205e\u205d\u209b\u209a\u20a7\u2074\u2073\u206e\u2081\u2080\u2083\u2082\u20b5\u20ba\u20b9\u20d2\u20d1\u20d3\u20d4\u20eb\u20f0\u20ef\u20f1\u2100\u2102\u2101\u211a\u2119\u2140\u2141' + '\u21a8\u21aa\u21a9\u21a0\u21a1\u21ae\u21ad\u21b4\u21b3\u21d1\u21d2\u21d4\u21cb\u21cd\u21d7\u21d8\u0000\u0000\u21cc\u221f\u2220\u21f2\u21f3\u21ed\u21ec\u21ee\u21fe\u21fd\u2272\u2271\u1f3c\u1f71\u1f70\u1f7b\u1f7a\u1f86\u1f85\u1fc3\u1fbb\u1fc2\u2053\u209c\u2075\u20b6\u21a4\u21b5\u2268\u2265\u2273\u21dd\u2142\u21f4\u213f\u21a7\u20f2\u2163\u21ea\u21d9\u21a2\u2162\u21a3\u21eb\u1fbc\u1fe8\u21ce\u211d\u1f39\u2161\u20ea\u20a6\u2054\u21ef\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u20a3\u213c\u1f2e\u1f2d\u21d3\u20a0\u2028\u20ec\u2239\u20a1\u20a2\u1fbd\u2029\u0553\u0000\u0000' + '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0219\u022e\u029e\u023e\u0235\u0269\u0273\u03f9\u03fb\u028b\u0000\u0000\u0000\u0000\u0000\u0000\u00c1\u0000\u00be\u0000\u0000\u0000\u0000\u0093\u0000\u00bf\u0000\u0000\u0000\u0000\u00c3\u0000\u0289\u0209\u0206\u01f7\u01f8\u0300\u0377\u0381\u0385\u03f4\u03f7\u03ef\u03f1\u03e1\u03e3\u03dc\u03df\u03e6\u03e9\u03eb\u03ed\u022f\u0230\u037a\u037d\u01ee\u01ef\u01f0\u01f1\u01f4\u01f5\u01f6\u0218\u022c\u0287\u0000\u0234\u023d\u0272\u0268\u0208\u02fd\u0374\u0380\u0384\u03f3\u03f6\u0425\u0420\u0413\u0679\u01fc\u0681\u068d\u0685\u0000\u041c\u106f\u0428\u0410\u0000\u0000\u0000\u0000\u00ed\u00ec\u00f1\u0000\u00f5\u0000\u00fa\u00f9\u0100\u00ff\u0105\u0104\u010b\u010a\u010f\u010e' + '\u1ee5\u1ee9\u1ee8\u1eec\u1eeb\u1ef3\u1ef2\u1ef6\u1ef5\u1efe\u1efd\u1efb\u1efc\u1f29\u1f28\u1f34\u1f33\u1f31\u1f32\u1f67\u1f66\u1f6c\u1f6b\u1f69\u1f6a\u1f92\u1f91\u1f8f\u1f90\u1fb8\u1fb7\u1fb5\u1fb6\u1fe5\u1fe4\u1fe2\u1fe3\u1ff5\u1ff4\u1ff2\u1ff3\u200a\u2009\u200d\u200c\u2026\u2025\u202c\u202b\u2046\u2045\u2043\u2044\u2069\u2068\u2066\u2067\u2097\u2096\u2094\u2095\u20b0\u20af\u20ad\u20ae\u20cb\u20ca\u20c8\u20c9\u20dd\u20dc\u20da\u20db\u20e7\u20e6\u20e4\u20e5\u20fb\u20fa\u20f8\u20f9\u2112\u2111\u210f\u2110\u2139\u2138\u2136\u2137\u2150\u214f\u214d\u214e\u2195\u2194\u2192\u2193\u21c6\u21c5\u21c3\u21c4\u21e7\u21e6\u21e4\u21e5\u2219\u2218\u2216\u2217\u2237\u2236\u2259\u2258\u2261\u2260\u225e\u225f\u2197\u2196\u2199\u2198\u219b\u219a\u219d\u219c\u0000\u0000\u0000' + '\u0000\u0267\u02f0\u0424\u106e\u0427\u041f\u02e8\u02fc\u0373\u0412\u0677\u0217\u01fb\u0285\u0419\u10c7\u10f9\u1172\u11c6\u120a\u124a\u1283\u12b8\u12ee\u1323\u023c\u0233\u0680\u0684\u068c\u0271\u040f\u1335\u13b3\u13d6\u1409\u144c\u14ae\u14cf\u1504\u153f\u158b\u15a3\u15d9\u1626\u1659\u169a\u171e\u1749\u175c\u17aa\u17eb\u1821\u1891\u18b8\u18d8\u18f2\u1917\u0379\u041b\u037c\u04c8\u01f3\u04c2\u1331\u13af\u13d1\u1403\u1446\u14aa\u14ca\u14ff\u1538\u1587\u159e\u15d3\u1621\u1655\u1695\u171a\u1746\u1757\u17a6\u17e7\u181d\u188c\u18b3\u18d3\u18ef\u1913\u037f\u0692\u0383\u0696\u0397\u0399\u029d\u03e5\u03e8\u022d\u0215\u37c1\u3647\u3652\u365b\u3665\u366e\u3779\u3782\u378a\u36e2\u1067\u3649\u3654\u365d\u3667\u3670\u3678\u3684\u3693\u369e\u36a6\u36af\u36b8\u36c1\u36c7\u36cf' + '\u36d5\u36dc\u36e4\u36eb\u36f3\u36fb\u3700\u3705\u3709\u370d\u3713\u3720\u372d\u373a\u374a\u3758\u3762\u376a\u376e\u3775\u377b\u3784\u378c\u3793\u3798\u379f\u37a6\u37ad\u37b2\u37c6\u00a5\u00a7\u350f\u3433\u3437\u35a4\u343b\u35a8\u35ab\u3440\u3444\u3448\u35b0\u35b3\u35b6\u35b9\u35bc\u35bf\u3493\u344d\u3452\u3456\u34a0\u345a\u345e\u3462\u3468\u346d\u3471\u3477\u347c\u3481\u3486\u0000\u0000\u0000\u3512\u3515\u3518\u351b\u351e\u3521\u0000\u0000\u3524\u3527\u352a\u352d\u3530\u3533\u0000\u0000\u3536\u3539\u353c\u353f\u3542\u3545\u0000\u0000\u3548\u354b\u354e\u0000\u0000\u0000\u106c\u1071\u0690\u04ca\u0694\u1073\u1089\u0000\u08b9\u05ef\u05f5\u05f2\u05f7\u0958\u0984\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u1018\uabf8\u0000\u0000'; /// Kangxi Radicals (U+2F00-U+2FD5) โ†’ CJK Unified equivalents. /// ICU decomposes these via NFD before collation. @@ -225,12 +737,6 @@ const Map _kangxiDecomp = { 0x2FD5: 0x9FA0, }; -final Map _ranks = _buildRanks(); - -Map _buildRanks() { - return {for (var i = 0; i < _ducetOrder.length; i++) _ducetOrder.codeUnitAt(i): i}; -} - /// Compare two characters using DUCET + CLDR ordering. /// Decomposes Kangxi radicals to CJK equivalents before lookup. /// Falls back to codepoint order for characters not in the table. @@ -239,7 +745,9 @@ int ducetCompare(String a, String b) { var cpB = b.runes.first; cpA = _kangxiDecomp[cpA] ?? cpA; cpB = _kangxiDecomp[cpB] ?? cpB; - final rankA = _ranks[cpA] ?? (0x100000 + cpA); - final rankB = _ranks[cpB] ?? (0x100000 + cpB); + final storedRankA = cpA <= 0xFFFF ? _ducetRanks.codeUnitAt(cpA) : 0; + final storedRankB = cpB <= 0xFFFF ? _ducetRanks.codeUnitAt(cpB) : 0; + final rankA = storedRankA == 0 ? 0x100000 + cpA : storedRankA - 1; + final rankB = storedRankB == 0 ? 0x100000 + cpB : storedRankB - 1; return rankA.compareTo(rankB); } diff --git a/lib/data/hid_key_labels.dart b/lib/data/hid_key_labels.dart index ca900630..85920641 100644 --- a/lib/data/hid_key_labels.dart +++ b/lib/data/hid_key_labels.dart @@ -1,4 +1,4 @@ -// Generated from USB HID Usage Tables โ€” do not edit by hand. +// Generated by dart run scripts/generate_hid_key_labels.dart from scripts/data/hid_key_labels.json; do not edit by hand. /// Human-readable labels for physical keyboard keys, keyed by USB HID usage code. const hidKeyLabels = { diff --git a/lib/data/iso_639_data.dart b/lib/data/iso_639_data.dart index ef7018f4..ec7babf4 100644 --- a/lib/data/iso_639_data.dart +++ b/lib/data/iso_639_data.dart @@ -1,4 +1,4 @@ -// Generated from iso_639_codes.json โ€” do not edit by hand. +// Generated by dart run scripts/generate_iso_639_data.dart from scripts/data/iso_639_codes.json; do not edit by hand. class LanguageEntry { final String code1; diff --git a/lib/focus/focusable_action_bar.dart b/lib/focus/focusable_action_bar.dart index 6ffb1a97..aa0276ce 100644 --- a/lib/focus/focusable_action_bar.dart +++ b/lib/focus/focusable_action_bar.dart @@ -106,7 +106,22 @@ class FocusableActionBarState extends State { FocusNode? getFocusNode(int index) => index >= 0 && index < _focusNodes.length ? _focusNodes[index] : null; void requestFocusOnFirst() { - if (_focusNodes.isNotEmpty) _focusNodes.first.requestFocus(); + final index = _nextEnabledIndex(-1); + if (index != null) _focusNodes[index].requestFocus(); + } + + int? _previousEnabledIndex(int index) { + for (var candidate = index - 1; candidate >= 0; candidate--) { + if (widget.actions[candidate].onPressed != null) return candidate; + } + return null; + } + + int? _nextEnabledIndex(int index) { + for (var candidate = index + 1; candidate < widget.actions.length; candidate++) { + if (widget.actions[candidate].onPressed != null) return candidate; + } + return null; } @override @@ -202,10 +217,10 @@ class FocusableActionBarState extends State { Widget _buildButton(int index, bool isKeyboard, Duration duration) { final action = widget.actions[index]; + final enabled = action.onPressed != null; final isFocused = _focusStates[index]; final showFocus = isFocused && isKeyboard; final opacity = isKeyboard && _hasAnyFocus && !isFocused ? 0.6 : 1.0; - final buildState = FocusableActionBuildState( focusNode: _focusNodes[index], isFocused: isFocused, @@ -217,28 +232,29 @@ class FocusableActionBarState extends State { return Focus( focusNode: _focusNodes[index], - autofocus: action.autofocus, + canRequestFocus: enabled, + autofocus: action.autofocus && enabled, descendantsAreFocusable: false, onKeyEvent: (node, event) { if (widget.onBack != null) { final backResult = handleBackKeyAction(event, widget.onBack!); if (backResult != KeyEventResult.ignored) return backResult; } + final previousIndex = _previousEnabledIndex(index); + final nextIndex = _nextEnabledIndex(index); return dpadKeyHandler( onSelect: action.onPressed, - onLeft: index > 0 ? () => _focusNodes[index - 1].requestFocus() : widget.onNavigateLeft, - onRight: index < _focusNodes.length - 1 - ? () => _focusNodes[index + 1].requestFocus() - : widget.onNavigateRight, + onLeft: previousIndex != null ? () => _focusNodes[previousIndex].requestFocus() : widget.onNavigateLeft, + onRight: nextIndex != null ? () => _focusNodes[nextIndex].requestFocus() : widget.onNavigateRight, onDown: widget.onNavigateDown, onUp: widget.onNavigateUp, - // Consume LEFT/RIGHT at the row's first/last button when no edge - // callback is wired, so focus can't fall off the row (#1181). + // Consume LEFT/RIGHT at the row's first/last enabled button when no + // edge callback is wired, so focus can't fall off the row (#1181). trapHorizontalEdges: true, )(node, event); }, child: ClickableCursor( - enabled: action.onPressed != null || action.child != null || customChild != null, + enabled: enabled, child: AnimatedOpacity( opacity: showFocus ? 1.0 : opacity, duration: duration, diff --git a/lib/focus/focusable_button.dart b/lib/focus/focusable_button.dart index fae439e3..f6173003 100644 --- a/lib/focus/focusable_button.dart +++ b/lib/focus/focusable_button.dart @@ -50,12 +50,14 @@ class _FocusableButtonState extends State { final isKeyboard = InputModeTracker.isKeyboardMode(context); final showFocus = _isFocused && isKeyboard; final duration = FocusTheme.getAnimationDuration(context); + final enabled = widget.onPressed != null; // In dpad mode: focused = full opacity, unfocused = dimmed final opacity = isKeyboard && !_isFocused ? 0.6 : 1.0; return FocusableWrapper( - autofocus: widget.autofocus, + autofocus: widget.autofocus && enabled, focusNode: widget.focusNode, + canRequestFocus: enabled, disableScale: true, borderRadius: 100, useBackgroundFocus: widget.useBackgroundFocus, diff --git a/lib/focus/focusable_text_field.dart b/lib/focus/focusable_text_field.dart index c35e930b..f2f7ecd4 100644 --- a/lib/focus/focusable_text_field.dart +++ b/lib/focus/focusable_text_field.dart @@ -8,6 +8,7 @@ import '../utils/platform_detector.dart'; import '../utils/text_input_diagnostics.dart'; import '../widgets/tv_virtual_keyboard.dart'; import 'dpad_navigator.dart'; +import 'key_event_utils.dart'; import 'owned_focus_node_binding.dart'; bool _usesTvKeyboard(bool enableTvKeyboard) => enableTvKeyboard && PlatformDetector.isTV(); @@ -360,16 +361,20 @@ KeyEventResult _moveCaretHorizontally(TextEditingController controller, int delt } if (!selection.isCollapsed) { - final offset = delta < 0 - ? (selection.start < selection.end ? selection.start : selection.end) - : (selection.start > selection.end ? selection.start : selection.end); - controller.selection = TextSelection.collapsed(offset: offset); + final range = expandToGraphemeRange(value.text, selection); + controller.selection = TextSelection.collapsed(offset: delta < 0 ? range.start : range.end); return KeyEventResult.handled; } - final nextOffset = selection.extentOffset + delta; - if (nextOffset < 0 || nextOffset > value.text.length) return KeyEventResult.ignored; - controller.selection = TextSelection.collapsed(offset: nextOffset); + final offset = selection.extentOffset.clamp(0, value.text.length); + if ((delta < 0 && offset == 0) || (delta > 0 && offset == value.text.length)) { + return KeyEventResult.ignored; + } + final codeUnitRange = delta < 0 + ? TextRange(start: offset - 1, end: offset) + : TextRange(start: offset, end: offset + 1); + final range = expandToGraphemeRange(value.text, codeUnitRange); + controller.selection = TextSelection.collapsed(offset: delta < 0 ? range.start : range.end); return KeyEventResult.handled; } @@ -439,25 +444,18 @@ void _backspace({ ValueChanged? onChanged, }) { final value = controller.value; - final range = _normalizedSelectionRange(value); - final start = range.start; - final end = range.end; - if (start != end) { - _replaceTextRange( - controller, - start, - end, - inputFormatters: inputFormatters, - maxLength: maxLength, - onChanged: onChanged, - ); - return; - } - if (start == 0) return; + final selectionRange = _normalizedSelectionRange(value); + final start = selectionRange.start; + final end = selectionRange.end; + if (start == end && start == 0) return; + + final codeUnitRange = start == end ? TextRange(start: start - 1, end: start) : TextRange(start: start, end: end); + final range = expandToGraphemeRange(value.text, codeUnitRange); + if (range.isCollapsed) return; _replaceTextRange( controller, - start - 1, - start, + range.start, + range.end, inputFormatters: inputFormatters, maxLength: maxLength, onChanged: onChanged, @@ -471,25 +469,18 @@ void _deleteForward({ ValueChanged? onChanged, }) { final value = controller.value; - final range = _normalizedSelectionRange(value); - final start = range.start; - final end = range.end; - if (start != end) { - _replaceTextRange( - controller, - start, - end, - inputFormatters: inputFormatters, - maxLength: maxLength, - onChanged: onChanged, - ); - return; - } - if (start >= value.text.length) return; + final selectionRange = _normalizedSelectionRange(value); + final start = selectionRange.start; + final end = selectionRange.end; + if (start == end && start >= value.text.length) return; + + final codeUnitRange = start == end ? TextRange(start: start, end: start + 1) : TextRange(start: start, end: end); + final range = expandToGraphemeRange(value.text, codeUnitRange); + if (range.isCollapsed) return; _replaceTextRange( controller, - start, - start + 1, + range.start, + range.end, inputFormatters: inputFormatters, maxLength: maxLength, onChanged: onChanged, diff --git a/lib/focus/focusable_wrapper.dart b/lib/focus/focusable_wrapper.dart index 9d358dd2..c107295a 100644 --- a/lib/focus/focusable_wrapper.dart +++ b/lib/focus/focusable_wrapper.dart @@ -85,6 +85,12 @@ class FocusableWrapper extends StatefulWidget { /// Optional semantic label for accessibility. final String? semanticLabel; + /// Optional current value announced after [semanticLabel]. + final String? semanticValue; + + /// Optional checked state for toggle-style controls. + final bool? checked; + /// Whether the wrapper can receive focus. final bool canRequestFocus; @@ -147,6 +153,8 @@ class FocusableWrapper extends StatefulWidget { this.scrollAlignment = 0.5, this.useComfortableZone = false, this.semanticLabel, + this.semanticValue, + this.checked, this.canRequestFocus = true, this.onKeyEvent, this.enableLongPress = false, @@ -521,7 +529,17 @@ class _FocusableWrapperState extends State with SingleTickerPr // Add semantics if label provided if (widget.semanticLabel != null) { - result = Semantics(label: widget.semanticLabel, button: widget.onSelect != null, child: result); + result = Semantics( + label: widget.semanticLabel, + value: widget.semanticValue, + button: true, + enabled: widget.onSelect != null, + checked: widget.checked, + onTap: widget.onSelect, + onLongPress: widget.onLongPress, + excludeSemantics: true, + child: result, + ); } if (widget.onSelect != null || widget.onLongPress != null) { diff --git a/lib/focus/input_mode_tracker.dart b/lib/focus/input_mode_tracker.dart index 9324dcd8..85ab29cb 100644 --- a/lib/focus/input_mode_tracker.dart +++ b/lib/focus/input_mode_tracker.dart @@ -121,26 +121,32 @@ class _InputModeTrackerState extends State { @override Widget build(BuildContext context) { - // On Android TV, don't switch to pointer mode from pointer events - // as D-pad can generate synthetic pointer events that would incorrectly - // trigger pointer mode and show a cursor instead of D-pad focus navigation. - // Desktop is exempt even in force-TV mode: its pointer events come from a - // real mouse, which should keep flipping modes (and the cursor) as usual. + // Non-desktop TVs keep keyboard mode across synthetic pointer events, but + // their controls remain pointer-reachable for engine-generated taps. if (TvDetectionService.isTVSync() && !PlatformDetector.isDesktopOS()) { return _InputModeProvider(mode: _mode, child: widget.child); } return Listener( - // Switch to pointer mode on mouse activity onPointerDown: (_) => _setMode(InputMode.pointer), onPointerHover: (_) => _setMode(InputMode.pointer), behavior: HitTestBehavior.translucent, - child: MouseRegion( - cursor: _mode == InputMode.keyboard ? SystemMouseCursors.none : MouseCursor.defer, - child: IgnorePointer( - ignoring: _mode == InputMode.keyboard, - child: _InputModeProvider(mode: _mode, child: widget.child), - ), + child: Stack( + alignment: Alignment.topLeft, + fit: StackFit.passthrough, + children: [ + _InputModeProvider(mode: _mode, child: widget.child), + // Hide the desktop cursor in keyboard mode without excluding the + // application subtree from the current pointer hit test. + if (_mode == InputMode.keyboard) + const Positioned.fill( + child: MouseRegion( + cursor: SystemMouseCursors.none, + opaque: false, + hitTestBehavior: HitTestBehavior.translucent, + ), + ), + ], ), ); } diff --git a/lib/focus/key_event_utils.dart b/lib/focus/key_event_utils.dart index 4f8ab2a9..5262b43c 100644 --- a/lib/focus/key_event_utils.dart +++ b/lib/focus/key_event_utils.dart @@ -123,6 +123,28 @@ KeyEventResult handleOneShotSelect(KeyEvent event, VoidCallback onActivate) { return KeyEventResult.handled; } +/// Expands a UTF-16 [range] to whole extended grapheme clusters in [text]. +/// +/// Flutter selections use UTF-16 code-unit offsets. Custom editors must pass a +/// non-empty range containing the code units they intend to replace; the +/// returned range is normalized, clamped, and safe to use with +/// [String.replaceRange] without splitting a user-perceived character. +TextRange expandToGraphemeRange(String text, TextRange range) { + if (text.isEmpty) return TextRange.empty; + + final first = range.start.clamp(0, text.length); + final second = range.end.clamp(0, text.length); + final start = first <= second ? first : second; + final end = first <= second ? second : first; + if (start == end) return TextRange.collapsed(start); + + final boundary = CharacterBoundary(text); + return TextRange( + start: boundary.getLeadingTextBoundaryAt(start) ?? 0, + end: boundary.getTrailingTextBoundaryAt(end - 1) ?? text.length, + ); +} + /// Creates a [FocusOnKeyEventCallback] that dispatches d-pad / arrow keys to /// the provided directional callbacks. /// diff --git a/lib/focus/locked_hub_controller.dart b/lib/focus/locked_hub_controller.dart index 1d4d8247..89dfa395 100644 --- a/lib/focus/locked_hub_controller.dart +++ b/lib/focus/locked_hub_controller.dart @@ -1,20 +1,20 @@ -/// Manages focus memory for hub navigation. +/// Manages focus memory for one browse surface. /// /// Tracks two things: /// 1. Per-hub memory: Each hub remembers which item was last focused -/// 2. Global column hint: When entering a hub that hasn't been visited, +/// 2. Last column hint: When entering a hub that hasn't been visited, /// we use the column position from the last focused hub as a hint class HubFocusMemory { - static final Map _perHubMemory = {}; - static int _lastColumnHint = 0; + final Map _perHubMemory = {}; + int _lastColumnHint = 0; - static void setForHub(String hubKey, int index) { + void setForHub(String hubKey, int index) { _perHubMemory[hubKey] = index; _lastColumnHint = index; } /// Get the remembered index for a hub, or fall back to column hint - static int getForHub(String hubKey, int itemCount) { + int getForHub(String hubKey, int itemCount) { if (itemCount <= 0) return 0; // If this hub has memory, use it @@ -26,16 +26,10 @@ class HubFocusMemory { return _lastColumnHint.clamp(0, itemCount - 1); } - /// Get only this hub's remembered index, without falling back to the global column hint. - static int getForHubOnly(String hubKey, int itemCount, {int fallback = 0}) { + /// Get only this hub's remembered index, without falling back to the last column hint. + int getForHubOnly(String hubKey, int itemCount, {int fallback = 0}) { if (itemCount <= 0) return 0; final remembered = _perHubMemory[hubKey]; return (remembered ?? fallback).clamp(0, itemCount - 1); } - - /// Clear all memory (e.g., when leaving a screen) - static void clear() { - _perHubMemory.clear(); - _lastColumnHint = 0; - } } diff --git a/lib/i18n/bg.i18n.json b/lib/i18n/bg.i18n.json index 66d4f49d..9bdcbd83 100644 --- a/lib/i18n/bg.i18n.json +++ b/lib/i18n/bg.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "ะ˜ะทั‡ะฐะบะฒะฐะฝะต ะฝะฐ ะพะดะพะฑั€ะตะฝะธะตโ€ฆ", "quickConnectCancel": "ะžั‚ะบะฐะท", "quickConnectExpired": "Quick Connect ะธะทั‚ะตั‡ะต. ะžะฟะธั‚ะฐะนั‚ะต ะพั‚ะฝะพะฒะพ.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "ะžั‚ะบะฐะท", @@ -180,7 +180,7 @@ "watchTogetherRelay": "ะ ะตะปะตะน ััŠั€ะฒัŠั€ ะทะฐ ะณะปะตะดะฐะฝะต ะทะฐะตะดะฝะพ", "watchTogetherRelayDescription": "ะ—ะฐะดะฐะน ัะพะฑัั‚ะฒะตะฝ ั€ะตะปะตะน ััŠั€ะฒัŠั€. ะ’ัะธั‡ะบะธ ั‚ั€ัะฑะฒะฐ ะดะฐ ะธะทะฟะพะปะทะฒะฐั‚ ััŠั‰ะธั ััŠั€ะฒัŠั€.", "watchTogetherRelayHint": "https://my-relay.example.com", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "ะ”ะพะบะปะฐะดะฒะฐะฝะต ะฝะฐ ัั€ะธะฒะพะฒะต", "crashReportingDescription": "ะ˜ะทะฟั€ะฐั‰ะฐะน ะดะพะบะปะฐะดะธ ะทะฐ ัั€ะธะฒะพะฒะต, ะทะฐ ะดะฐ ะฟะพะผะพะณะฝะตัˆ ะทะฐ ะฟะพะดะพะฑั€ัะฒะฐะฝะต ะฝะฐ ะฟั€ะธะปะพะถะตะฝะธะตั‚ะพ", "debugLogging": "ะ›ะพะณะพะฒะต ะทะฐ ะพั‚ัั‚ั€ะฐะฝัะฒะฐะฝะต ะฝะฐ ะณั€ะตัˆะบะธ", @@ -197,12 +197,10 @@ "exportSettings": "ะ•ะบัะฟะพั€ั‚ะธั€ะฐะน ะฝะฐัั‚ั€ะพะนะบะธั‚ะต", "exportSettingsDescription": "ะ—ะฐะฟะฐะทะธ ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะฝะธัั‚ะฐ ัะธ ะฒัŠะฒ ั„ะฐะนะป", "exportSettingsSuccess": "ะะฐัั‚ั€ะพะนะบะธั‚ะต ัะฐ ะตะบัะฟะพั€ั‚ะธั€ะฐะฝะธ", - "exportSettingsFailed": "ะะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ะฑัŠะดะฐั‚ ะตะบัะฟะพั€ั‚ะธั€ะฐะฝะธ", "importSettings": "ะ˜ะผะฟะพั€ั‚ะธั€ะฐะน ะฝะฐัั‚ั€ะพะนะบะธ", "importSettingsDescription": "ะ’ัŠะทัั‚ะฐะฝะพะฒะธ ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะฝะธั ะพั‚ ั„ะฐะนะป", "importSettingsConfirm": "ะขะพะฒะฐ ั‰ะต ะทะฐะผะตะฝะธ ั‚ะตะบัƒั‰ะธั‚ะต ะฒะธ ะฝะฐัั‚ั€ะพะนะบะธ. ะŸั€ะพะดัŠะปะถะฐะฒะฐั‚ะต ะปะธ?", "importSettingsSuccess": "ะะฐัั‚ั€ะพะนะบะธั‚ะต ัะฐ ะธะผะฟะพั€ั‚ะธั€ะฐะฝะธ", - "importSettingsFailed": "ะะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ะฑัŠะดะฐั‚ ะธะผะฟะพั€ั‚ะธั€ะฐะฝะธ", "importSettingsInvalidFile": "ะขะพะทะธ ั„ะฐะนะป ะฝะต ะต ะฒะฐะปะธะดะตะฝ ะตะบัะฟะพั€ั‚ ะฝะฐ ะฝะฐัั‚ั€ะพะนะบะธ ะพั‚ Plezy", "importSettingsNoUser": "ะ’ะปะตะทั‚ะต, ะฟั€ะตะดะธ ะดะฐ ะธะผะฟะพั€ั‚ะธั€ะฐั‚ะต ะฝะฐัั‚ั€ะพะนะบะธ", "shortcutsReset": "ะšะปะฐะฒะธัˆะฝะธั‚ะต ะบะพะผะฑะธะฝะฐั†ะธะธ ัะฐ ะฝัƒะปะธั€ะฐะฝะธ ะดะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต", @@ -217,6 +215,7 @@ "validationErrorDuration": "ะŸั€ะพะดัŠะปะถะธั‚ะตะปะฝะพัั‚ั‚ะฐ ั‚ั€ัะฑะฒะฐ ะดะฐ ะต ะผะตะถะดัƒ ${min} ะธ ${max} ${unit}", "shortcutAlreadyAssigned": "ะšะปะฐะฒะธัˆะฝะฐั‚ะฐ ะบะพะผะฑะธะฝะฐั†ะธั ะฒะตั‡ะต ะต ะฝะฐะทะฝะฐั‡ะตะฝะฐ ะทะฐ ${action}", "shortcutUpdated": "ะšะปะฐะฒะธัˆะฝะฐั‚ะฐ ะบะพะผะฑะธะฝะฐั†ะธั ะต ะพะฑะฝะพะฒะตะฝะฐ ะทะฐ ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะฝะต", "autoSkipIntro": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะฝะต ะฝะฐ ะธะฝั‚ั€ะพ", "autoSkipIntroDescription": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะน ะธะฝั‚ั€ะพ ะผะฐั€ะบะตั€ะธ ัะปะตะด ะฝัะบะพะปะบะพ ัะตะบัƒะฝะดะธ", @@ -242,7 +241,7 @@ "downloadLocationChanged": "ะœะตัั‚ะพะฟะพะปะพะถะตะฝะธะตั‚ะพ ะทะฐ ะธะทั‚ะตะณะปัะฝะธั ะต ะฟั€ะพะผะตะฝะตะฝะพ", "downloadLocationReset": "ะœะตัั‚ะพะฟะพะปะพะถะตะฝะธะตั‚ะพ ะทะฐ ะธะทั‚ะตะณะปัะฝะธั ะต ะฒัŠั€ะฝะฐั‚ะพ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต", "downloadLocationInvalid": "ะ˜ะทะฑั€ะฐะฝะฐั‚ะฐ ะฟะฐะฟะบะฐ ะฝะต ะต ะทะฐะฟะธัะฒะฐะตะผะฐ", - "downloadLocationSelectError": "ะะตัƒัะฟะตัˆะตะฝ ะธะทะฑะพั€ ะฝะฐ ะฟะฐะฟะบะฐ", + "downloadLocationPickerUnavailable": "ะ˜ะทะฑะพั€ัŠั‚ ะฝะฐ ะฟะฐะฟะบะฐ ะฝะต ะต ะฝะฐะปะธั‡ะตะฝ ะฝะฐ ั‚ะพะฒะฐ ัƒัั‚ั€ะพะนัั‚ะฒะพ", "downloadOnWifiOnly": "ะ˜ะทั‚ะตะณะปัะฝะต ัะฐะผะพ ะฟั€ะตะท WiFi", "downloadOnWifiOnlyDescription": "ะŸั€ะตะดะพั‚ะฒั€ะฐั‚ัะฒะฐะน ะธะทั‚ะตะณะปัะฝะธั ะฟั€ะตะท ะผะพะฑะธะปะฝะธ ะดะฐะฝะฝะธ", "autoRemoveWatchedDownloads": "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฒะฐะน ะธะทะณะปะตะดะฐะฝะธั‚ะต ะธะทั‚ะตะณะปัะฝะธั", @@ -443,7 +442,11 @@ "brightness": "ะฏั€ะบะพัั‚", "hexColor": "ะจะตัั‚ะฝะฐะดะตัะตั‚ะธั‡ะตะฝ ั†ะฒัั‚", "expandText": "ะ ะฐะทะณัŠะฝะธ ั‚ะตะบัั‚ะฐ", - "collapseText": "ะกะฒะธะน ั‚ะตะบัั‚ะฐ" + "collapseText": "ะกะฒะธะน ั‚ะตะบัั‚ะฐ", + "alphabetNavigation": "ะะฐะฒะธะณะฐั†ะธั ะฟะพ ะฐะทะฑัƒะบะฐั‚ะฐ", + "alphabetScrollHint": "ะŸะปัŠะทะฝะตั‚ะต ะฝะฐะณะพั€ะต ะธะปะธ ะฝะฐะดะพะปัƒ, ะทะฐ ะดะฐ ะฟั€ะตะผะธะฝะตั‚ะต ะฟะพ ะฑัƒะบะฒะฐ", + "rowColumnPosition": "ะ ะตะด ${row} ะพั‚ ${rowCount}, ะบะพะปะพะฝะฐ ${column} ะพั‚ ${columnCount}", + "rowPosition": "ะ ะตะด ${row} ะพั‚ ${rowCount}" }, "tooltips": { "shufflePlay": "ะ ะฐะทะฑัŠั€ะบะฐะฝะพ ะฒัŠะทะฟั€ะพะธะทะฒะตะถะดะฐะฝะต", @@ -451,6 +454,9 @@ "markAsWatched": "ะœะฐั€ะบะธั€ะฐะน ะบะฐั‚ะพ ะณะปะตะดะฐะฝะพ", "markAsUnwatched": "ะœะฐั€ะบะธั€ะฐะน ะบะฐั‚ะพ ะฝะตะณะปะตะดะฐะฝะพ" }, + "audioTracks": { + "track": "ะัƒะดะธะพ ะฟัŠั‚ะตั‡ะบะฐ ${n}" + }, "videoControls": { "audioLabel": "ะัƒะดะธะพ", "subtitlesLabel": "ะกัƒะฑั‚ะธั‚ั€ะธ", @@ -478,6 +484,8 @@ "playNext": "ะŸัƒัะฝะธ ัะปะตะดะฒะฐั‰ะพั‚ะพ", "playButton": "ะŸัƒัะฝะธ", "pauseButton": "ะŸะฐัƒะทะฐ", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "ะŸั€ะตะฒัŠั€ั‚ะธ ะฝะฐะทะฐะด ${seconds} ัะตะบัƒะฝะดะธ", "seekForwardButton": "ะŸั€ะตะฒัŠั€ั‚ะธ ะฝะฐะฟั€ะตะด ${seconds} ัะตะบัƒะฝะดะธ", "previousButton": "ะŸั€ะตะดะธัˆะตะฝ ะตะฟะธะทะพะด", @@ -553,11 +561,11 @@ "streamInterrupted": "ะŸะพั‚ะพะบัŠั‚ ะฟั€ะตะบัŠัะฝะฐ. ะะฐั‚ะธัะฝะตั‚ะต โ€žะŸัƒัะฝะธโ€œ ะธะปะธ ะฟั€ะตะฒัŠั€ั‚ะตั‚ะต, ะทะฐ ะดะฐ ะพะฟะธั‚ะฐั‚ะต ะพั‚ะฝะพะฒะพ.", "liveStreamInterrupted": "ะŸะพั‚ะพะบัŠั‚ ะฝะฐ ะถะธะฒะพ ะฟั€ะตะบัŠัะฝะฐ. ะะฐั‚ะธัะฝะตั‚ะต โ€žะŸัƒัะฝะธโ€œ, ะทะฐ ะดะฐ ะพะฟะธั‚ะฐั‚ะต ะพั‚ะฝะพะฒะพ.", "fileInfoNotAvailable": "ะ˜ะฝั„ะพั€ะผะฐั†ะธัั‚ะฐ ะทะฐ ั„ะฐะนะปะฐ ะฝะต ะต ะฝะฐะปะธั‡ะฝะฐ", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "ะ“ั€ะตัˆะบะฐ ะฟั€ะธ ะทะฐั€ะตะถะดะฐะฝะต ะฝะฐ ะธะฝั„ะพั€ะผะฐั†ะธั ะทะฐ ั„ะฐะนะปะฐ: ${error}", "errorLoadingSeries": "ะ“ั€ะตัˆะบะฐ ะฟั€ะธ ะทะฐั€ะตะถะดะฐะฝะต ะฝะฐ ัะตั€ะธะฐะปะฐ", "musicNotSupported": "ะ’ัŠะทะฟั€ะพะธะทะฒะตะถะดะฐะฝะตั‚ะพ ะฝะฐ ะผัƒะทะธะบะฐ ะฒัะต ะพั‰ะต ะฝะต ัะต ะฟะพะดะดัŠั€ะถะฐ", @@ -680,6 +688,7 @@ "borrowExplain": "ะ˜ะทะฟะพะปะทะฒะฐะน ะฒั€ัŠะทะบะฐ ะพั‚ ะดั€ัƒะณ ะฟั€ะพั„ะธะป. PIN-ะทะฐั‰ะธั‚ะตะฝะธั‚ะต ะฟั€ะพั„ะธะปะธ ะธะทะธัะบะฒะฐั‚ PIN.", "borrowEmpty": "ะ’ัะต ะพั‰ะต ะฝัะผะฐ ะบะฐะบะฒะพ ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐ.", "borrowEmptySubtitle": "ะŸัŠั€ะฒะพ ัะฒัŠั€ะถะตั‚ะต Plex ะธะปะธ Jellyfin ะบัŠะผ ะดั€ัƒะณ ะฟั€ะพั„ะธะป.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "ะžั‚ ${displayName}", "borrowConnectionBorrowed": "ะ’ั€ัŠะทะบะฐั‚ะฐ ะต ะธะทะฟะพะปะทะฒะฐะฝะฐ.", "borrowFailed": "ะะตัƒัะฟะตัˆะฝะพ ะธะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ ะฒั€ัŠะทะบะฐ.", @@ -944,7 +953,7 @@ "favorites": "ะ›ัŽะฑะธะผะธ", "reorderFavorites": "ะŸั€ะตะฝะฐั€ะตะดะธ ะปัŽะฑะธะผะธั‚ะต", "favoritesLoadFailed": "ะ›ัŽะฑะธะผะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ัะต ะทะฐั€ะตะดัั‚. ะŸั€ะพะฒะตั€ะตั‚ะต ะฒั€ัŠะทะบะฐั‚ะฐ ัะธ ะธ ะพะฟะธั‚ะฐะนั‚ะต ะพั‚ะฝะพะฒะพ.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "ะŸั€ะธััŠะตะดะธะฝะธ ัะต ะบัŠะผ ั‚ะตะบัƒั‰ะฐ ัะตัะธั", "watchFromStart": "ะ“ะปะตะดะฐะน ะพั‚ ะฝะฐั‡ะฐะปะพั‚ะพ (ะฟั€ะตะดะธ ${minutes} ะผะธะฝ)", "watchLive": "ะ“ะปะตะดะฐะน ะฝะฐ ะถะธะฒะพ", diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index 3cfdc961..3480c8fc 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "Venter pรฅ godkendelseโ€ฆ", "quickConnectCancel": "Annullรฉr", "quickConnectExpired": "Quick Connect er udlรธbet. Prรธv igen.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Annuller", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Watch Together-relay", "watchTogetherRelayDescription": "Angiv en brugerdefineret relay. Alle skal bruge samme server.", "watchTogetherRelayHint": "https://min-relay.eksempel.dk", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Fejlrapportering", "crashReportingDescription": "Send fejlrapporter for at hjรฆlpe med at forbedre appen", "debugLogging": "Fejlfindingslogning", @@ -197,12 +197,10 @@ "exportSettings": "Eksportรฉr indstillinger", "exportSettingsDescription": "Gem dine prรฆferencer i en fil", "exportSettingsSuccess": "Indstillinger eksporteret", - "exportSettingsFailed": "Kunne ikke eksportere indstillinger", "importSettings": "Importรฉr indstillinger", "importSettingsDescription": "Gendan prรฆferencer fra en fil", "importSettingsConfirm": "Dette vil erstatte dine nuvรฆrende indstillinger. Fortsรฆt?", "importSettingsSuccess": "Indstillinger importeret", - "importSettingsFailed": "Kunne ikke importere indstillinger", "importSettingsInvalidFile": "Denne fil er ikke en gyldig Plezy-indstillingseksport", "importSettingsNoUser": "Log ind fรธr import af indstillinger", "shortcutsReset": "Genveje nulstillet til standard", @@ -217,6 +215,7 @@ "validationErrorDuration": "Varighed skal vรฆre mellem ${min} og ${max} ${unit}", "shortcutAlreadyAssigned": "Genvej allerede tildelt til ${action}", "shortcutUpdated": "Genvej opdateret for ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Auto-spring", "autoSkipIntro": "Auto-spring intro", "autoSkipIntroDescription": "Spring automatisk intromarkรธrer over efter fรฅ sekunder", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Downloadplacering รฆndret", "downloadLocationReset": "Downloadplacering nulstillet", "downloadLocationInvalid": "Valgt mappe er ikke skrivbar", - "downloadLocationSelectError": "Kunne ikke vรฆlge mappe", + "downloadLocationPickerUnavailable": "Mappevalg er ikke tilgรฆngeligt pรฅ denne enhed", "downloadOnWifiOnly": "Download kun pรฅ WiFi", "downloadOnWifiOnlyDescription": "Forhindre downloads pรฅ mobildata", "autoRemoveWatchedDownloads": "Fjern sete downloads automatisk", @@ -443,7 +442,11 @@ "brightness": "Lysstyrke", "hexColor": "Hexfarve", "expandText": "Udvid tekst", - "collapseText": "Fold tekst sammen" + "collapseText": "Fold tekst sammen", + "alphabetNavigation": "Alfabetnavigation", + "alphabetScrollHint": "Stryg op eller ned for at flytte รฉt bogstav", + "rowColumnPosition": "Rรฆkke ${row} af ${rowCount}, kolonne ${column} af ${columnCount}", + "rowPosition": "Rรฆkke ${row} af ${rowCount}" }, "tooltips": { "shufflePlay": "Afspil tilfรฆldigt", @@ -451,6 +454,9 @@ "markAsWatched": "Markรฉr som set", "markAsUnwatched": "Markรฉr som uset" }, + "audioTracks": { + "track": "Lydspor ${n}" + }, "videoControls": { "audioLabel": "Lyd", "subtitlesLabel": "Undertekster", @@ -478,6 +484,8 @@ "playNext": "Afspil nรฆste", "playButton": "Afspil", "pauseButton": "Pause", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Spol ${seconds} sekunder tilbage", "seekForwardButton": "Spol ${seconds} sekunder frem", "previousButton": "Forrige episode", @@ -553,11 +561,11 @@ "streamInterrupted": "Streamen blev afbrudt. Tryk pรฅ afspil, eller spol for at prรธve igen.", "liveStreamInterrupted": "Livestreamen blev afbrudt. Tryk pรฅ afspil for at prรธve igen.", "fileInfoNotAvailable": "Filinfo ikke tilgรฆngelig", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Fejl ved indlรฆsning af filinfo: ${error}", "errorLoadingSeries": "Fejl ved indlรฆsning af serie", "musicNotSupported": "Musikafspilning understรธttes endnu ikke", @@ -680,6 +688,7 @@ "borrowExplain": "Lรฅn en anden profils forbindelse. PIN-beskyttede profiler krรฆver en PIN.", "borrowEmpty": "Intet at lรฅne endnu.", "borrowEmptySubtitle": "Forbind Plex eller Jellyfin til en anden profil fรธrst.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "Fra ${displayName}", "borrowConnectionBorrowed": "Forbindelse lรฅnt.", "borrowFailed": "Kunne ikke lรฅne forbindelse.", @@ -944,7 +953,7 @@ "favorites": "Favoritter", "reorderFavorites": "Omarranger favoritter", "favoritesLoadFailed": "Favoritter kunne ikke indlรฆses. Kontrollรฉr forbindelsen, og prรธv igen.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Deltag i igangvรฆrende session", "watchFromStart": "Se fra start (${minutes} min siden)", "watchLive": "Se live", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 9636482c..940745db 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "Warte auf Bestรคtigungโ€ฆ", "quickConnectCancel": "Abbrechen", "quickConnectExpired": "Quick Connect ist abgelaufen. Versuche es erneut.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Abbrechen", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Gemeinsam Schauen Relay", "watchTogetherRelayDescription": "Eigenes Relay festlegen. Alle mรผssen denselben Server verwenden.", "watchTogetherRelayHint": "https://mein-relay.beispiel.de", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Absturzberichte", "crashReportingDescription": "Absturzberichte senden, um die App zu verbessern", "debugLogging": "Debug-Protokollierung", @@ -197,12 +197,10 @@ "exportSettings": "Einstellungen exportieren", "exportSettingsDescription": "Speichere deine Einstellungen in einer Datei", "exportSettingsSuccess": "Einstellungen exportiert", - "exportSettingsFailed": "Einstellungen konnten nicht exportiert werden", "importSettings": "Einstellungen importieren", "importSettingsDescription": "Einstellungen aus einer Datei wiederherstellen", "importSettingsConfirm": "Dies ersetzt deine aktuellen Einstellungen. Fortfahren?", "importSettingsSuccess": "Einstellungen importiert", - "importSettingsFailed": "Einstellungen konnten nicht importiert werden", "importSettingsInvalidFile": "Diese Datei ist kein gรผltiger Plezy-Einstellungsexport", "importSettingsNoUser": "Vor dem Import bitte anmelden", "shortcutsReset": "Tastenkรผrzel auf Standard zurรผckgesetzt", @@ -217,6 +215,7 @@ "validationErrorDuration": "Dauer muss zwischen ${min} und ${max} ${unit} liegen", "shortcutAlreadyAssigned": "Tastenkรผrzel bereits zugewiesen an ${action}", "shortcutUpdated": "Tastenkรผrzel aktualisiert fรผr ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Automatisches รœberspringen", "autoSkipIntro": "Intro automatisch รผberspringen", "autoSkipIntroDescription": "Intro-Marker nach wenigen Sekunden automatisch รผberspringen", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Download-Speicherort geรคndert", "downloadLocationReset": "Download-Speicherort auf Standard zurรผckgesetzt", "downloadLocationInvalid": "Ausgewรคhlter Ordner ist nicht beschreibbar", - "downloadLocationSelectError": "Ordnerauswahl fehlgeschlagen", + "downloadLocationPickerUnavailable": "Die Ordnerauswahl ist auf diesem Gerรคt nicht verfรผgbar", "downloadOnWifiOnly": "Nur รผber WLAN herunterladen", "downloadOnWifiOnlyDescription": "Downloads รผber mobile Daten verhindern", "autoRemoveWatchedDownloads": "Gesehene Downloads automatisch entfernen", @@ -443,7 +442,11 @@ "brightness": "Helligkeit", "hexColor": "Hex-Farbe", "expandText": "Text ausklappen", - "collapseText": "Text einklappen" + "collapseText": "Text einklappen", + "alphabetNavigation": "Alphabetische Navigation", + "alphabetScrollHint": "Nach oben oder unten wischen, um einen Buchstaben weiterzugehen", + "rowColumnPosition": "Zeile ${row} von ${rowCount}, Spalte ${column} von ${columnCount}", + "rowPosition": "Zeile ${row} von ${rowCount}" }, "tooltips": { "shufflePlay": "Zufallswiedergabe", @@ -451,6 +454,9 @@ "markAsWatched": "Als gesehen markieren", "markAsUnwatched": "Als ungesehen markieren" }, + "audioTracks": { + "track": "Audiospur ${n}" + }, "videoControls": { "audioLabel": "Audio", "subtitlesLabel": "Untertitel", @@ -478,6 +484,8 @@ "playNext": "Nรคchstes abspielen", "playButton": "Wiedergeben", "pauseButton": "Pause", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "${seconds} Sekunden zurรผck", "seekForwardButton": "${seconds} Sekunden vor", "previousButton": "Vorherige Episode", @@ -553,11 +561,11 @@ "streamInterrupted": "Der Stream wurde unterbrochen. Drรผcke auf Wiedergabe oder spule, um es erneut zu versuchen.", "liveStreamInterrupted": "Der Livestream wurde unterbrochen. Drรผcke auf Wiedergabe, um es erneut zu versuchen.", "fileInfoNotAvailable": "Dateiinfo nicht verfรผgbar", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Fehler beim Laden der Dateiinfo: ${error}", "errorLoadingSeries": "Fehler beim Laden der Serie", "musicNotSupported": "Musikwiedergabe wird noch nicht unterstรผtzt", @@ -680,6 +688,7 @@ "borrowExplain": "Verbindung eines anderen Profils leihen. PIN-geschรผtzte Profile erfordern eine PIN.", "borrowEmpty": "Noch nichts zum Ausleihen.", "borrowEmptySubtitle": "Verbinde zuerst Plex oder Jellyfin mit einem anderen Profil.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "Von ${displayName}", "borrowConnectionBorrowed": "Verbindung ausgeliehen.", "borrowFailed": "Verbindung konnte nicht ausgeliehen werden.", @@ -944,7 +953,7 @@ "favorites": "Favoriten", "reorderFavorites": "Favoriten sortieren", "favoritesLoadFailed": "Favoriten konnten nicht geladen werden. รœberprรผfe deine Verbindung und versuche es erneut.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Laufender Sitzung beitreten", "watchFromStart": "Von Anfang an ansehen (vor ${minutes} Min.)", "watchLive": "Live ansehen", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 52b3a29d..1cff67ed 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -197,12 +197,10 @@ "exportSettings": "Export Settings", "exportSettingsDescription": "Save your preferences to a file", "exportSettingsSuccess": "Settings exported", - "exportSettingsFailed": "Could not export settings", "importSettings": "Import Settings", "importSettingsDescription": "Restore preferences from a file", "importSettingsConfirm": "This will replace your current settings. Continue?", "importSettingsSuccess": "Settings imported", - "importSettingsFailed": "Could not import settings", "importSettingsInvalidFile": "This file isn't a valid Plezy settings export", "importSettingsNoUser": "Sign in before importing settings", "shortcutsReset": "Shortcuts reset to defaults", @@ -217,6 +215,7 @@ "validationErrorDuration": "Duration must be between ${min} and ${max} ${unit}", "shortcutAlreadyAssigned": "Shortcut already assigned to ${action}", "shortcutUpdated": "Shortcut updated for ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Auto Skip", "autoSkipIntro": "Auto Skip Intro", "autoSkipIntroDescription": "Automatically skip intro markers after a few seconds", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Download location changed", "downloadLocationReset": "Download location reset to default", "downloadLocationInvalid": "Selected folder is not writable", - "downloadLocationSelectError": "Failed to select folder", + "downloadLocationPickerUnavailable": "Folder selection is not available on this device", "downloadOnWifiOnly": "Download on WiFi only", "downloadOnWifiOnlyDescription": "Prevent downloads when on cellular data", "autoRemoveWatchedDownloads": "Auto-remove watched downloads", @@ -443,7 +442,11 @@ "brightness": "Brightness", "hexColor": "Hex color", "expandText": "Expand text", - "collapseText": "Collapse text" + "collapseText": "Collapse text", + "alphabetNavigation": "Alphabet navigation", + "alphabetScrollHint": "Swipe up or down to move by letter", + "rowColumnPosition": "Row ${row} of ${rowCount}, column ${column} of ${columnCount}", + "rowPosition": "Row ${row} of ${rowCount}" }, "tooltips": { "shufflePlay": "Shuffle play", @@ -451,6 +454,9 @@ "markAsWatched": "Mark as watched", "markAsUnwatched": "Mark as unwatched" }, + "audioTracks": { + "track": "Audio Track ${n}" + }, "videoControls": { "audioLabel": "Audio", "subtitlesLabel": "Subtitles", @@ -478,6 +484,8 @@ "playNext": "Play Next", "playButton": "Play", "pauseButton": "Pause", + "showPlaybackControls": "Show playback controls", + "hidePlaybackControls": "Hide playback controls", "seekBackwardButton": "Seek backward ${seconds} seconds", "seekForwardButton": "Seek forward ${seconds} seconds", "previousButton": "Previous episode", @@ -680,6 +688,7 @@ "borrowExplain": "Borrow another profile's connection. PIN-protected profiles require a PIN.", "borrowEmpty": "Nothing to borrow yet.", "borrowEmptySubtitle": "Connect Plex or Jellyfin to another profile first.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "From ${displayName}", "borrowConnectionBorrowed": "Connection borrowed.", "borrowFailed": "Failed to borrow connection.", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 2cf4344c..37f35ffb 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "Esperando aprobaciรณnโ€ฆ", "quickConnectCancel": "Cancelar", "quickConnectExpired": "Quick Connect caducรณ. Intรฉntalo de nuevo.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Cancelar", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Relay de Ver Juntos", "watchTogetherRelayDescription": "Define un relay personalizado. Todos deben usar el mismo servidor.", "watchTogetherRelayHint": "https://mi-relay.ejemplo.com", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Informes de Errores", "crashReportingDescription": "Enviar informes de errores para mejorar la aplicaciรณn", "debugLogging": "Registro de Depuraciรณn", @@ -197,12 +197,10 @@ "exportSettings": "Exportar Configuraciรณn", "exportSettingsDescription": "Guardar tus preferencias en un archivo", "exportSettingsSuccess": "Configuraciรณn exportada", - "exportSettingsFailed": "No se pudo exportar la configuraciรณn", "importSettings": "Importar Configuraciรณn", "importSettingsDescription": "Restaurar preferencias desde un archivo", "importSettingsConfirm": "Esto reemplazarรก tu configuraciรณn actual. ยฟContinuar?", "importSettingsSuccess": "Configuraciรณn importada", - "importSettingsFailed": "No se pudo importar la configuraciรณn", "importSettingsInvalidFile": "Este archivo no es una exportaciรณn vรกlida de Plezy", "importSettingsNoUser": "Inicia sesiรณn antes de importar la configuraciรณn", "shortcutsReset": "Atajos restablecidos a los valores predeterminados", @@ -217,6 +215,7 @@ "validationErrorDuration": "La duraciรณn debe estar entre ${min} y ${max} ${unit}", "shortcutAlreadyAssigned": "El atajo ya estรก asignado a ${action}", "shortcutUpdated": "Atajo actualizado para ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Salto automรกtico", "autoSkipIntro": "Saltar Intro automรกticamente", "autoSkipIntroDescription": "Saltar automรกticamente los marcadores de intro despuรฉs de unos segundos", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Ubicaciรณn de descarga cambiada", "downloadLocationReset": "Ubicaciรณn de descarga restablecida al predeterminado", "downloadLocationInvalid": "La carpeta seleccionada no tiene permisos de escritura", - "downloadLocationSelectError": "Error al seleccionar la carpeta", + "downloadLocationPickerUnavailable": "La selecciรณn de carpetas no estรก disponible en este dispositivo", "downloadOnWifiOnly": "Descargar solo con WiFi", "downloadOnWifiOnlyDescription": "Evitar descargas cuando se usan datos mรณviles", "autoRemoveWatchedDownloads": "Eliminar descargas vistas automรกticamente", @@ -443,7 +442,11 @@ "brightness": "Brillo", "hexColor": "Color hexadecimal", "expandText": "Expandir texto", - "collapseText": "Contraer texto" + "collapseText": "Contraer texto", + "alphabetNavigation": "Navegaciรณn alfabรฉtica", + "alphabetScrollHint": "Desliza hacia arriba o abajo para avanzar por letra", + "rowColumnPosition": "Fila ${row} de ${rowCount}, columna ${column} de ${columnCount}", + "rowPosition": "Fila ${row} de ${rowCount}" }, "tooltips": { "shufflePlay": "Reproducciรณn aleatoria", @@ -451,6 +454,9 @@ "markAsWatched": "Marcar como visto", "markAsUnwatched": "Marcar como no visto" }, + "audioTracks": { + "track": "Pista de audio ${n}" + }, "videoControls": { "audioLabel": "Audio", "subtitlesLabel": "Subtรญtulos", @@ -478,6 +484,8 @@ "playNext": "Reproducir siguiente", "playButton": "Reproducir", "pauseButton": "Pausa", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Retroceder ${seconds} segundos", "seekForwardButton": "Avanzar ${seconds} segundos", "previousButton": "Episodio anterior", @@ -553,11 +561,11 @@ "streamInterrupted": "La reproducciรณn se interrumpiรณ. Pulsa reproducir o avanza para volver a intentarlo.", "liveStreamInterrupted": "La transmisiรณn en vivo se interrumpiรณ. Pulsa reproducir para volver a intentarlo.", "fileInfoNotAvailable": "Informaciรณn de archivo no disponible", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Error al cargar info de archivo: ${error}", "errorLoadingSeries": "Error al cargar la serie", "musicNotSupported": "La reproducciรณn de mรบsica aรบn no estรก soportada", @@ -680,6 +688,7 @@ "borrowExplain": "Toma prestada la conexiรณn de otro perfil. Los perfiles protegidos con PIN requieren un PIN.", "borrowEmpty": "Nada para tomar prestado todavรญa.", "borrowEmptySubtitle": "Conecta Plex o Jellyfin primero a otro perfil.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "De ${displayName}", "borrowConnectionBorrowed": "Conexiรณn tomada prestada.", "borrowFailed": "No se pudo tomar prestada la conexiรณn.", @@ -944,7 +953,7 @@ "favorites": "Favoritos", "reorderFavorites": "Reordenar favoritos", "favoritesLoadFailed": "No se pudieron cargar los favoritos. Comprueba tu conexiรณn e intรฉntalo de nuevo.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Unirse a sesiรณn en curso", "watchFromStart": "Ver desde el inicio (hace ${minutes} min)", "watchLive": "Ver en vivo", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index ba1b77f9..d2f97848 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "En attente d'approbationโ€ฆ", "quickConnectCancel": "Annuler", "quickConnectExpired": "Quick Connect a expirรฉ. Rรฉessayez.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Annuler", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Relais Regarder Ensemble", "watchTogetherRelayDescription": "Dรฉfinir un relay personnalisรฉ. Tout le monde doit utiliser le mรชme serveur.", "watchTogetherRelayHint": "https://mon-relais.exemple.fr", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Rapports de plantage", "crashReportingDescription": "Envoyer des rapports de plantage pour amรฉliorer l'application", "debugLogging": "Journalisation de dรฉbogage", @@ -197,12 +197,10 @@ "exportSettings": "Exporter les paramรจtres", "exportSettingsDescription": "Enregistrer vos prรฉfรฉrences dans un fichier", "exportSettingsSuccess": "Paramรจtres exportรฉs", - "exportSettingsFailed": "Impossible d'exporter les paramรจtres", "importSettings": "Importer les paramรจtres", "importSettingsDescription": "Restaurer les prรฉfรฉrences depuis un fichier", "importSettingsConfirm": "Cela remplacera vos paramรจtres actuels. Continuer ?", "importSettingsSuccess": "Paramรจtres importรฉs", - "importSettingsFailed": "Impossible d'importer les paramรจtres", "importSettingsInvalidFile": "Ce fichier n'est pas un export Plezy valide", "importSettingsNoUser": "Connectez-vous avant d'importer les paramรจtres", "shortcutsReset": "Raccourcis rรฉinitialisรฉs aux valeurs par dรฉfaut", @@ -217,6 +215,7 @@ "validationErrorDuration": "La durรฉe doit รชtre comprise entre ${min} et ${max} ${unit}", "shortcutAlreadyAssigned": "Raccourci dรฉjร  attribuรฉ ร  ${action}", "shortcutUpdated": "Raccourci mis ร  jour pour ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Skip automatique", "autoSkipIntro": "Skip automatique de l'introduction", "autoSkipIntroDescription": "Skipper automatiquement l'introduction aprรจs quelques secondes", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Emplacement de tรฉlรฉchargement modifiรฉ", "downloadLocationReset": "Emplacement de tรฉlรฉchargement rรฉinitialisรฉ ร  la valeur par dรฉfaut", "downloadLocationInvalid": "Le dossier sรฉlectionnรฉ n'est pas accessible en รฉcriture", - "downloadLocationSelectError": "ร‰chec de la sรฉlection du dossier", + "downloadLocationPickerUnavailable": "La sรฉlection de dossier nโ€™est pas disponible sur cet appareil", "downloadOnWifiOnly": "Tรฉlรฉcharger uniquement via WiFi", "downloadOnWifiOnlyDescription": "Empรชcher les tรฉlรฉchargements lorsque vous utilisez les donnรฉes cellulaires", "autoRemoveWatchedDownloads": "Supprimer automatiquement les tรฉlรฉchargements vus", @@ -443,7 +442,11 @@ "brightness": "Luminositรฉ", "hexColor": "Couleur hexadรฉcimale", "expandText": "Dรฉvelopper le texte", - "collapseText": "Replier le texte" + "collapseText": "Replier le texte", + "alphabetNavigation": "Navigation alphabรฉtique", + "alphabetScrollHint": "Balayez vers le haut ou le bas pour changer de lettre", + "rowColumnPosition": "Ligne ${row} sur ${rowCount}, colonne ${column} sur ${columnCount}", + "rowPosition": "Ligne ${row} sur ${rowCount}" }, "tooltips": { "shufflePlay": "Lecture alรฉatoire", @@ -451,6 +454,9 @@ "markAsWatched": "Marquรฉ comme vu", "markAsUnwatched": "Marquรฉ comme non vu" }, + "audioTracks": { + "track": "Piste audio ${n}" + }, "videoControls": { "audioLabel": "Audio", "subtitlesLabel": "Sous-titres", @@ -478,6 +484,8 @@ "playNext": "Lire l'รฉpisode suivant", "playButton": "Lire", "pauseButton": "Pause", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Reculer de ${seconds} secondes", "seekForwardButton": "Avancer de ${seconds} secondes", "previousButton": "ร‰pisode prรฉcรฉdent", @@ -553,11 +561,11 @@ "streamInterrupted": "La lecture a รฉtรฉ interrompue. Appuyez sur Lecture ou avancez pour rรฉessayer.", "liveStreamInterrupted": "Le direct a รฉtรฉ interrompu. Appuyez sur Lecture pour rรฉessayer.", "fileInfoNotAvailable": "Informations sur le fichier non disponibles", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Erreur lors du chargement des informations sur le fichier: ${error}", "errorLoadingSeries": "Erreur lors du chargement de la sรฉrie", "musicNotSupported": "La lecture de musique n'est pas encore prise en charge", @@ -680,6 +688,7 @@ "borrowExplain": "Emprunter la connexion d'un autre profil. Les profils protรฉgรฉs par PIN exigent un PIN.", "borrowEmpty": "Rien ร  emprunter pour le moment.", "borrowEmptySubtitle": "Connectez d'abord Plex ou Jellyfin ร  un autre profil.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "De ${displayName}", "borrowConnectionBorrowed": "Connexion empruntรฉe.", "borrowFailed": "Impossible d'emprunter la connexion.", @@ -944,7 +953,7 @@ "favorites": "Favoris", "reorderFavorites": "Rรฉorganiser les favoris", "favoritesLoadFailed": "Impossible de charger les favoris. Vรฉrifiez votre connexion et rรฉessayez.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Rejoindre la session en cours", "watchFromStart": "Regarder depuis le dรฉbut (il y a ${minutes} min)", "watchLive": "Regarder en direct", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 6eba0dbf..9d16c8ea 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "In attesa di approvazioneโ€ฆ", "quickConnectCancel": "Annulla", "quickConnectExpired": "Quick Connect scaduto. Riprova.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Cancella", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Relay Guarda Insieme", "watchTogetherRelayDescription": "Imposta un relay personalizzato. Tutti devono usare lo stesso server.", "watchTogetherRelayHint": "https://mio-relay.esempio.it", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Segnalazione errori", "crashReportingDescription": "Invia segnalazioni di errori per migliorare l'app", "debugLogging": "Log di debug", @@ -197,12 +197,10 @@ "exportSettings": "Esporta impostazioni", "exportSettingsDescription": "Salva le tue preferenze in un file", "exportSettingsSuccess": "Impostazioni esportate", - "exportSettingsFailed": "Impossibile esportare le impostazioni", "importSettings": "Importa impostazioni", "importSettingsDescription": "Ripristina le preferenze da un file", "importSettingsConfirm": "Questa azione sostituirร  le impostazioni attuali. Continuare?", "importSettingsSuccess": "Impostazioni importate", - "importSettingsFailed": "Impossibile importare le impostazioni", "importSettingsInvalidFile": "Questo file non รจ un'esportazione Plezy valida", "importSettingsNoUser": "Accedi prima di importare le impostazioni", "shortcutsReset": "Scorciatoie ripristinate alle impostazioni predefinite", @@ -217,6 +215,7 @@ "validationErrorDuration": "la durata deve essere compresa tra ${min} e ${max} ${unit}", "shortcutAlreadyAssigned": "Scorciatoia giร  assegnata a ${action}", "shortcutUpdated": "Scorciatoia aggiornata per ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Salto Automatico", "autoSkipIntro": "Salta Intro Automaticamente", "autoSkipIntroDescription": "Salta automaticamente i marcatori dell'intro dopo alcuni secondi", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Posizione di download modificata", "downloadLocationReset": "Posizione di download ripristinata a predefinita", "downloadLocationInvalid": "La cartella selezionata non รจ scrivibile", - "downloadLocationSelectError": "Impossibile selezionare la cartella", + "downloadLocationPickerUnavailable": "La selezione della cartella non รจ disponibile su questo dispositivo", "downloadOnWifiOnly": "Scarica solo con WiFi", "downloadOnWifiOnlyDescription": "Impedisci i download quando si utilizza la rete dati cellulare", "autoRemoveWatchedDownloads": "Rimuovi automaticamente i download visti", @@ -443,7 +442,11 @@ "brightness": "Luminositร ", "hexColor": "Colore esadecimale", "expandText": "Espandi il testo", - "collapseText": "Comprimi il testo" + "collapseText": "Comprimi il testo", + "alphabetNavigation": "Navigazione alfabetica", + "alphabetScrollHint": "Scorri verso l'alto o il basso per cambiare lettera", + "rowColumnPosition": "Riga ${row} di ${rowCount}, colonna ${column} di ${columnCount}", + "rowPosition": "Riga ${row} di ${rowCount}" }, "tooltips": { "shufflePlay": "Riproduzione casuale", @@ -451,6 +454,9 @@ "markAsWatched": "Segna come visto", "markAsUnwatched": "Segna come non visto" }, + "audioTracks": { + "track": "Traccia audio ${n}" + }, "videoControls": { "audioLabel": "Audio", "subtitlesLabel": "Sottotitoli", @@ -478,6 +484,8 @@ "playNext": "Riproduci successivo", "playButton": "Riproduci", "pauseButton": "Pausa", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Riavvolgi di ${seconds} secondi", "seekForwardButton": "Avanza di ${seconds} secondi", "previousButton": "Episodio precedente", @@ -553,11 +561,11 @@ "streamInterrupted": "La riproduzione si รจ interrotta. Premi Riproduci o scorri per riprovare.", "liveStreamInterrupted": "La diretta si รจ interrotta. Premi Riproduci per riprovare.", "fileInfoNotAvailable": "Informazioni sul file non disponibili", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Errore caricamento informazioni sul file: ${error}", "errorLoadingSeries": "Errore caricamento serie", "musicNotSupported": "La riproduzione musicale non รจ ancora supportata", @@ -680,6 +688,7 @@ "borrowExplain": "Prendi in prestito la connessione di un altro profilo. I profili protetti da PIN richiedono un PIN.", "borrowEmpty": "Nulla da prendere in prestito al momento.", "borrowEmptySubtitle": "Collega prima Plex o Jellyfin a un altro profilo.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "Da ${displayName}", "borrowConnectionBorrowed": "Connessione presa in prestito.", "borrowFailed": "Impossibile prendere in prestito la connessione.", @@ -944,7 +953,7 @@ "favorites": "Preferiti", "reorderFavorites": "Riordina preferiti", "favoritesLoadFailed": "Impossibile caricare i preferiti. Controlla la connessione e riprova.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Partecipa alla sessione in corso", "watchFromStart": "Guarda dall'inizio (${minutes} min fa)", "watchLive": "Guarda in diretta", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index b63f6cd8..49f40905 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "ๆ‰ฟ่ชใ‚’ๅพ…ใฃใฆใ„ใพใ™โ€ฆ", "quickConnectCancel": "ใ‚ญใƒฃใƒณใ‚ปใƒซ", "quickConnectExpired": "Quick Connectใฎๆœ‰ๅŠนๆœŸ้™ใŒๅˆ‡ใ‚Œใพใ—ใŸใ€‚ใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "ใ‚ญใƒฃใƒณใ‚ปใƒซ", @@ -180,7 +180,7 @@ "watchTogetherRelay": "ไธ€็ท’ใซ่ฆ–่ดใƒชใƒฌใƒผใ‚ตใƒผใƒใƒผ", "watchTogetherRelayDescription": "ใ‚ซใ‚นใ‚ฟใƒ ใƒชใƒฌใƒผใ‚’่จญๅฎšใ—ใพใ™ใ€‚ๅ…จๅ“กใŒๅŒใ˜ใ‚ตใƒผใƒใƒผใ‚’ไฝฟใ†ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚", "watchTogetherRelayHint": "https://my-relay.example.com", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "ใ‚ฏใƒฉใƒƒใ‚ทใƒฅใƒฌใƒใƒผใƒˆ", "crashReportingDescription": "ใ‚ขใƒ—ใƒชใฎๆ”นๅ–„ใซๅฝน็ซ‹ใคใ‚ฏใƒฉใƒƒใ‚ทใƒฅใƒฌใƒใƒผใƒˆใ‚’้€ไฟก", "debugLogging": "ใƒ‡ใƒใƒƒใ‚ฐใƒญใ‚ฐ", @@ -197,12 +197,10 @@ "exportSettings": "่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆ", "exportSettingsDescription": "่จญๅฎšใ‚’ใƒ•ใ‚กใ‚คใƒซใซไฟๅญ˜", "exportSettingsSuccess": "่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใ—ใพใ—ใŸ", - "exportSettingsFailed": "่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใงใใพใ›ใ‚“ใงใ—ใŸ", "importSettings": "่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆ", "importSettingsDescription": "ใƒ•ใ‚กใ‚คใƒซใ‹ใ‚‰่จญๅฎšใ‚’ๅพฉๅ…ƒ", "importSettingsConfirm": "็พๅœจใฎ่จญๅฎšใ‚’็ฝฎใๆ›ใˆใพใ™ใ€‚็ถš่กŒใ—ใพใ™ใ‹๏ผŸ", "importSettingsSuccess": "่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใพใ—ใŸ", - "importSettingsFailed": "่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใงใใพใ›ใ‚“ใงใ—ใŸ", "importSettingsInvalidFile": "ใ“ใฎใƒ•ใ‚กใ‚คใƒซใฏๆœ‰ๅŠนใชPlezyใฎ่จญๅฎšใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใงใฏใ‚ใ‚Šใพใ›ใ‚“", "importSettingsNoUser": "่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใ™ใ‚‹ๅ‰ใซใ‚ตใ‚คใƒณใ‚คใƒณใ—ใฆใใ ใ•ใ„", "shortcutsReset": "ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚’ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใซใƒชใ‚ปใƒƒใƒˆใ—ใพใ—ใŸ", @@ -217,6 +215,7 @@ "validationErrorDuration": "ๆ™‚้–“ใฏ${min}ใ‹ใ‚‰${max} ${unit}ใฎ้–“ใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™", "shortcutAlreadyAssigned": "ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใฏๆ—ขใซ${action}ใซๅ‰ฒใ‚Šๅฝ“ใฆใ‚‰ใ‚Œใฆใ„ใพใ™", "shortcutUpdated": "${action}ใฎใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚’ๆ›ดๆ–ฐใ—ใพใ—ใŸ", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "่‡ชๅ‹•ใ‚นใ‚ญใƒƒใƒ—", "autoSkipIntro": "ใ‚คใƒณใƒˆใƒญใ‚’่‡ชๅ‹•ใ‚นใ‚ญใƒƒใƒ—", "autoSkipIntroDescription": "ๆ•ฐ็ง’ๅพŒใซใ‚คใƒณใƒˆใƒญใƒžใƒผใ‚ซใƒผใ‚’่‡ชๅ‹•็š„ใซใ‚นใ‚ญใƒƒใƒ—", @@ -242,7 +241,7 @@ "downloadLocationChanged": "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅ ดๆ‰€ใ‚’ๅค‰ๆ›ดใ—ใพใ—ใŸ", "downloadLocationReset": "ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅ ดๆ‰€ใ‚’ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใซใƒชใ‚ปใƒƒใƒˆใ—ใพใ—ใŸ", "downloadLocationInvalid": "้ธๆŠžใ—ใŸใƒ•ใ‚ฉใƒซใƒ€ใฏๆ›ธใ่พผใฟใงใใพใ›ใ‚“", - "downloadLocationSelectError": "ใƒ•ใ‚ฉใƒซใƒ€ใฎ้ธๆŠžใซๅคฑๆ•—ใ—ใพใ—ใŸ", + "downloadLocationPickerUnavailable": "ใ“ใฎใƒ‡ใƒใ‚คใ‚นใงใฏใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠžใงใใพใ›ใ‚“", "downloadOnWifiOnly": "WiFiใฎใฟใงใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰", "downloadOnWifiOnlyDescription": "ใƒขใƒใ‚คใƒซใƒ‡ใƒผใ‚ฟ้€šไฟกๆ™‚ใฎใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ‚’้˜ฒๆญข", "autoRemoveWatchedDownloads": "่ฆ–่ดๆธˆใฟใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใฎ่‡ชๅ‹•ๅ‰Š้™ค", @@ -443,7 +442,11 @@ "brightness": "ๆ˜Žใ‚‹ใ•", "hexColor": "16้€ฒใ‚ซใƒฉใƒผ", "expandText": "ใƒ†ใ‚ญใ‚นใƒˆใ‚’ๅฑ•้–‹", - "collapseText": "ใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆŠ˜ใ‚ŠใŸใŸใ‚€" + "collapseText": "ใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆŠ˜ใ‚ŠใŸใŸใ‚€", + "alphabetNavigation": "ใ‚ขใƒซใƒ•ใ‚กใƒ™ใƒƒใƒˆใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณ", + "alphabetScrollHint": "ไธŠไธ‹ใซใ‚นใƒฏใ‚คใƒ—ใ—ใฆๆ–‡ๅญ—ใ”ใจใซ็งปๅ‹•", + "rowColumnPosition": "${rowCount}่กŒไธญ${row}่กŒใ€${columnCount}ๅˆ—ไธญ${column}ๅˆ—", + "rowPosition": "${rowCount}่กŒไธญ${row}่กŒ" }, "tooltips": { "shufflePlay": "ใ‚ทใƒฃใƒƒใƒ•ใƒซๅ†็”Ÿ", @@ -451,6 +454,9 @@ "markAsWatched": "่ฆ–่ดๆธˆใฟใซใ™ใ‚‹", "markAsUnwatched": "ๆœช่ฆ–่ดใซใ™ใ‚‹" }, + "audioTracks": { + "track": "้Ÿณๅฃฐใƒˆใƒฉใƒƒใ‚ฏ${n}" + }, "videoControls": { "audioLabel": "้Ÿณๅฃฐ", "subtitlesLabel": "ๅญ—ๅน•", @@ -478,6 +484,8 @@ "playNext": "ๆฌกใ‚’ๅ†็”Ÿ", "playButton": "ๅ†็”Ÿ", "pauseButton": "ไธ€ๆ™‚ๅœๆญข", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "${seconds}็ง’ๆˆปใ‚‹", "seekForwardButton": "${seconds}็ง’้€ฒใ‚€", "previousButton": "ๅ‰ใฎใ‚จใƒ”ใ‚ฝใƒผใƒ‰", @@ -552,11 +560,11 @@ "streamInterrupted": "ใ‚นใƒˆใƒชใƒผใƒ ใŒไธญๆ–ญใ•ใ‚Œใพใ—ใŸใ€‚ๅ†็”Ÿใ‚’ๆŠผใ™ใ‹ใ‚ทใƒผใ‚ฏใ—ใฆๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„ใ€‚", "liveStreamInterrupted": "ใƒฉใ‚คใƒ–ใ‚นใƒˆใƒชใƒผใƒ ใŒไธญๆ–ญใ•ใ‚Œใพใ—ใŸใ€‚ๅ†็”Ÿใ‚’ๆŠผใ—ใฆๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„ใ€‚", "fileInfoNotAvailable": "ใƒ•ใ‚กใ‚คใƒซๆƒ…ๅ ฑใŒๅˆฉ็”จใงใใพใ›ใ‚“", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "ใƒ•ใ‚กใ‚คใƒซๆƒ…ๅ ฑใฎ่ชญใฟ่พผใฟใ‚จใƒฉใƒผ: ${error}", "errorLoadingSeries": "ใ‚ทใƒชใƒผใ‚บใฎ่ชญใฟ่พผใฟใ‚จใƒฉใƒผ", "musicNotSupported": "้Ÿณๆฅฝใฎๅ†็”Ÿใฏใพใ ใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใพใ›ใ‚“", @@ -679,6 +687,7 @@ "borrowExplain": "ๅˆฅใฎใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใฎๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใ—ใพใ™ใ€‚PINไฟ่ญทใ•ใ‚ŒใŸใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใซใฏPINใŒๅฟ…่ฆใงใ™ใ€‚", "borrowEmpty": "ใพใ ๅ€Ÿใ‚Šใ‚‹ใ‚‚ใฎใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚", "borrowEmptySubtitle": "ใพใšๅˆฅใฎใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใซPlexใพใŸใฏJellyfinใ‚’ๆŽฅ็ถšใ—ใฆใใ ใ•ใ„ใ€‚", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "${displayName}ใ‹ใ‚‰", "borrowConnectionBorrowed": "ๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใ—ใพใ—ใŸใ€‚", "borrowFailed": "ๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใงใใพใ›ใ‚“ใงใ—ใŸใ€‚", @@ -942,7 +951,7 @@ "favorites": "ใŠๆฐ—ใซๅ…ฅใ‚Š", "reorderFavorites": "ใŠๆฐ—ใซๅ…ฅใ‚Šใ‚’ไธฆในๆ›ฟใˆ", "favoritesLoadFailed": "ใŠๆฐ—ใซๅ…ฅใ‚Šใ‚’่ชญใฟ่พผใ‚ใพใ›ใ‚“ใงใ—ใŸใ€‚ๆŽฅ็ถšใ‚’็ขบ่ชใ—ใฆใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "้€ฒ่กŒไธญใฎใ‚ปใƒƒใ‚ทใƒงใƒณใซๅ‚ๅŠ ", "watchFromStart": "ๆœ€ๅˆใ‹ใ‚‰่ฆ–่ด๏ผˆ${minutes}ๅˆ†ๅ‰ใซ้–‹ๅง‹๏ผ‰", "watchLive": "ใƒฉใ‚คใƒ–ใง่ฆ–่ด", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 0e955d95..5c361658 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "์Šน์ธ ๋Œ€๊ธฐ ์ค‘โ€ฆ", "quickConnectCancel": "์ทจ์†Œ", "quickConnectExpired": "Quick Connect๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "์ทจ์†Œ", @@ -180,7 +180,7 @@ "watchTogetherRelay": "ํ•จ๊ป˜ ๋ณด๊ธฐ ๋ฆด๋ ˆ์ด", "watchTogetherRelayDescription": "์‚ฌ์šฉ์ž ์ง€์ • ๋ฆด๋ ˆ์ด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋‘ ๊ฐ™์€ ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", "watchTogetherRelayHint": "https://my-relay.example.com", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "์ถฉ๋Œ ๋ณด๊ณ ", "crashReportingDescription": "์•ฑ ๊ฐœ์„ ์„ ์œ„ํ•ด ์ถฉ๋Œ ๋ณด๊ณ ์„œ ์ „์†ก", "debugLogging": "๋””๋ฒ„๊ทธ ๋กœ๊น…", @@ -197,12 +197,10 @@ "exportSettings": "์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ", "exportSettingsDescription": "๊ธฐ๋ณธ ์„ค์ •์„ ํŒŒ์ผ๋กœ ์ €์žฅ", "exportSettingsSuccess": "์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ ์™„๋ฃŒ", - "exportSettingsFailed": "์„ค์ •์„ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", "importSettings": "์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ", "importSettingsDescription": "ํŒŒ์ผ์—์„œ ๊ธฐ๋ณธ ์„ค์ • ๋ณต์›", "importSettingsConfirm": "ํ˜„์žฌ ์„ค์ •์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", "importSettingsSuccess": "์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ ์™„๋ฃŒ", - "importSettingsFailed": "์„ค์ •์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", "importSettingsInvalidFile": "์œ ํšจํ•œ Plezy ์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ ํŒŒ์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค", "importSettingsNoUser": "์„ค์ •์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์ „์— ๋กœ๊ทธ์ธํ•˜์„ธ์š”", "shortcutsReset": "๋‹จ์ถ•ํ‚ค๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค", @@ -217,6 +215,7 @@ "validationErrorDuration": "๊ธฐ๊ฐ„์€ ${min}๊ณผ ${max} ${unit} ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค", "shortcutAlreadyAssigned": "๋‹จ์ถ•ํ‚ค๊ฐ€ ์ด๋ฏธ ${action}์— ํ• ๋‹น ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", "shortcutUpdated": "๋‹จ์ถ•ํ‚ค๊ฐ€ ${action}์— ๋Œ€ํ•ด ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "์ž๋™ ๊ฑด๋„ˆ๋›ฐ๊ธฐ", "autoSkipIntro": "์ž๋™์œผ๋กœ ์˜คํ”„๋‹ ๊ฑด๋„ˆ๋›ฐ๊ธฐ", "autoSkipIntroDescription": "๋ช‡ ์ดˆ ํ›„ ์˜คํ”„๋‹์„ ์ž๋™์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ", @@ -242,7 +241,7 @@ "downloadLocationChanged": "๋‹ค์šด๋กœ๋“œ ์œ„์น˜๊ฐ€ ๋ณ€๊ฒฝ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", "downloadLocationReset": "๋‹ค์šด๋กœ๋“œ ์œ„์น˜๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์„ค์ • ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", "downloadLocationInvalid": "์„ ํƒํ•œ ํด๋”์— ์“ฐ๊ธฐ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค", - "downloadLocationSelectError": "ํด๋” ์„ ํƒ ์‹คํŒจ", + "downloadLocationPickerUnavailable": "์ด ๊ธฐ๊ธฐ์—์„œ๋Š” ํด๋”๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", "downloadOnWifiOnly": "WiFi ์—ฐ๊ฒฐ ์‹œ์—๋งŒ ๋‹ค์šด๋กœ๋“œ", "downloadOnWifiOnlyDescription": "์…€๋ฃฐ๋Ÿฌ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ ์‹œ ๋‹ค์šด๋กœ๋“œ ๋ถˆ๊ฐ€", "autoRemoveWatchedDownloads": "์‹œ์ฒญํ•œ ๋‹ค์šด๋กœ๋“œ ์ž๋™ ์‚ญ์ œ", @@ -443,7 +442,11 @@ "brightness": "๋ฐ๊ธฐ", "hexColor": "16์ง„์ˆ˜ ์ƒ‰์ƒ", "expandText": "ํ…์ŠคํŠธ ํŽผ์น˜๊ธฐ", - "collapseText": "ํ…์ŠคํŠธ ์ ‘๊ธฐ" + "collapseText": "ํ…์ŠคํŠธ ์ ‘๊ธฐ", + "alphabetNavigation": "์•ŒํŒŒ๋ฒณ ํƒ์ƒ‰", + "alphabetScrollHint": "์œ„์•„๋ž˜๋กœ ์Šค์™€์ดํ”„ํ•˜์—ฌ ๊ธ€์ž๋ณ„๋กœ ์ด๋™", + "rowColumnPosition": "${rowCount}ํ–‰ ์ค‘ ${row}ํ–‰, ${columnCount}์—ด ์ค‘ ${column}์—ด", + "rowPosition": "${rowCount}ํ–‰ ์ค‘ ${row}ํ–‰" }, "tooltips": { "shufflePlay": "๋ฌด์ž‘์œ„ ์žฌ์ƒ", @@ -451,6 +454,9 @@ "markAsWatched": "์‹œ์ฒญ ์™„๋ฃŒ๋กœ ํ‘œ์‹œ", "markAsUnwatched": "์‹œ์ฒญ ์•ˆ ํ•จ์œผ๋กœ ํ‘œ์‹œ" }, + "audioTracks": { + "track": "์˜ค๋””์˜ค ํŠธ๋ž™ ${n}" + }, "videoControls": { "audioLabel": "์˜ค๋””์˜ค", "subtitlesLabel": "์ž๋ง‰", @@ -478,6 +484,8 @@ "playNext": "๋‹ค์Œ ์žฌ์ƒ", "playButton": "์žฌ์ƒ", "pauseButton": "์ผ์‹œ์ •์ง€", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "${seconds} ์ดˆ ๋’ค๋กœ", "seekForwardButton": "${seconds} ์ดˆ ์•ž์œผ๋กœ", "previousButton": "์ด์ „ ์—ํ”ผ์†Œ๋“œ", @@ -552,11 +560,11 @@ "streamInterrupted": "์ŠคํŠธ๋ฆผ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์žฌ์ƒ์„ ๋ˆ„๋ฅด๊ฑฐ๋‚˜ ํƒ์ƒ‰ํ•˜์—ฌ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.", "liveStreamInterrupted": "๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆผ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์žฌ์ƒ์„ ๋ˆŒ๋Ÿฌ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.", "fileInfoNotAvailable": "ํŒŒ์ผ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "ํŒŒ์ผ ์ •๋ณด ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜: ${error}", "errorLoadingSeries": "์‹œ๋ฆฌ์ฆˆ ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜", "musicNotSupported": "์Œ์•… ์žฌ์ƒ ๋ฏธ์ง€์›", @@ -679,6 +687,7 @@ "borrowExplain": "๋‹ค๋ฅธ ํ”„๋กœํ•„์˜ ์—ฐ๊ฒฐ์„ ๋นŒ๋ฆฝ๋‹ˆ๋‹ค. PIN์œผ๋กœ ๋ณดํ˜ธ๋œ ํ”„๋กœํ•„์—๋Š” PIN์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", "borrowEmpty": "์•„์ง ๋นŒ๋ฆด ๊ฒƒ์ด ์—†์Šต๋‹ˆ๋‹ค.", "borrowEmptySubtitle": "๋จผ์ € ๋‹ค๋ฅธ ํ”„๋กœํ•„์— Plex ๋˜๋Š” Jellyfin์„ ์—ฐ๊ฒฐํ•˜์„ธ์š”.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "${displayName}์—์„œ", "borrowConnectionBorrowed": "์—ฐ๊ฒฐ์„ ๋นŒ๋ ธ์Šต๋‹ˆ๋‹ค.", "borrowFailed": "์—ฐ๊ฒฐ์„ ๋นŒ๋ฆฌ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.", @@ -942,7 +951,7 @@ "favorites": "์ฆ๊ฒจ์ฐพ๊ธฐ", "reorderFavorites": "์ฆ๊ฒจ์ฐพ๊ธฐ ์ˆœ์„œ ๋ณ€๊ฒฝ", "favoritesLoadFailed": "์ฆ๊ฒจ์ฐพ๊ธฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "์ง„ํ–‰ ์ค‘์ธ ์„ธ์…˜ ์ฐธ์—ฌ", "watchFromStart": "์ฒ˜์Œ๋ถ€ํ„ฐ ์‹œ์ฒญ (${minutes}๋ถ„ ์ „ ์‹œ์ž‘)", "watchLive": "์‹ค์‹œ๊ฐ„ ์‹œ์ฒญ", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index d7660f76..532c8fc2 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "Venter pรฅ godkjenningโ€ฆ", "quickConnectCancel": "Avbryt", "quickConnectExpired": "Quick Connect er utlรธpt. Prรธv igjen.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Avbryt", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Se Sammen-relay", "watchTogetherRelayDescription": "Angi en egendefinert relay. Alle mรฅ bruke samme server.", "watchTogetherRelayHint": "https://min-relay.eksempel.no", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Krasjrapportering", "crashReportingDescription": "Send krasjrapporter for รฅ hjelpe med รฅ forbedre appen", "debugLogging": "Feilsรธkingslogging", @@ -197,12 +197,10 @@ "exportSettings": "Eksporter innstillinger", "exportSettingsDescription": "Lagre innstillingene i en fil", "exportSettingsSuccess": "Innstillinger eksportert", - "exportSettingsFailed": "Kunne ikke eksportere innstillinger", "importSettings": "Importer innstillinger", "importSettingsDescription": "Gjenopprett innstillinger fra en fil", "importSettingsConfirm": "Dette vil erstatte nรฅvรฆrende innstillinger. Fortsette?", "importSettingsSuccess": "Innstillinger importert", - "importSettingsFailed": "Kunne ikke importere innstillinger", "importSettingsInvalidFile": "Denne filen er ikke en gyldig Plezy-innstillingseksport", "importSettingsNoUser": "Logg inn fรธr import av innstillinger", "shortcutsReset": "Snarveier tilbakestilt til standard", @@ -217,6 +215,7 @@ "validationErrorDuration": "Varigheten mรฅ vรฆre mellom ${min} og ${max} ${unit}", "shortcutAlreadyAssigned": "Snarvei allerede tilordnet til ${action}", "shortcutUpdated": "Snarvei oppdatert for ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Automatisk hopp", "autoSkipIntro": "Hopp over intro automatisk", "autoSkipIntroDescription": "Hopp automatisk over intromarkรธrer etter noen sekunder", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Nedlastingsplassering endret", "downloadLocationReset": "Nedlastingsplassering tilbakestilt til standard", "downloadLocationInvalid": "Valgt mappe er ikke skrivbar", - "downloadLocationSelectError": "Kunne ikke velge mappe", + "downloadLocationPickerUnavailable": "Mappevalg er ikke tilgjengelig pรฅ denne enheten", "downloadOnWifiOnly": "Last ned kun pรฅ WiFi", "downloadOnWifiOnlyDescription": "Forhindre nedlastinger pรฅ mobildata", "autoRemoveWatchedDownloads": "Fjern sette nedlastinger automatisk", @@ -443,7 +442,11 @@ "brightness": "Lysstyrke", "hexColor": "Heksadesimal farge", "expandText": "Utvid tekst", - "collapseText": "Fold sammen tekst" + "collapseText": "Fold sammen tekst", + "alphabetNavigation": "Alfabetisk navigasjon", + "alphabetScrollHint": "Sveip opp eller ned for รฅ flytte รฉn bokstav", + "rowColumnPosition": "Rad ${row} av ${rowCount}, kolonne ${column} av ${columnCount}", + "rowPosition": "Rad ${row} av ${rowCount}" }, "tooltips": { "shufflePlay": "Tilfeldig avspilling", @@ -451,6 +454,9 @@ "markAsWatched": "Merk som sett", "markAsUnwatched": "Merk som usett" }, + "audioTracks": { + "track": "Lydspor ${n}" + }, "videoControls": { "audioLabel": "Lyd", "subtitlesLabel": "Undertekster", @@ -478,6 +484,8 @@ "playNext": "Spill neste", "playButton": "Spill av", "pauseButton": "Pause", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Spol tilbake ${seconds} sekunder", "seekForwardButton": "Spol fremover ${seconds} sekunder", "previousButton": "Forrige episode", @@ -553,11 +561,11 @@ "streamInterrupted": "Avspillingen ble avbrutt. Trykk pรฅ Spill av eller spol for รฅ prรธve pรฅ nytt.", "liveStreamInterrupted": "Direktesendingen ble avbrutt. Trykk pรฅ Spill av for รฅ prรธve pรฅ nytt.", "fileInfoNotAvailable": "Filinformasjon ikke tilgjengelig", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Feil ved lasting av filinformasjon: ${error}", "errorLoadingSeries": "Feil ved lasting av serie", "musicNotSupported": "Musikkavspilling stรธttes ikke ennรฅ", @@ -680,6 +688,7 @@ "borrowExplain": "Lรฅn en annen profils tilkobling. PIN-beskyttede profiler krever PIN.", "borrowEmpty": "Ingenting รฅ lรฅne enda.", "borrowEmptySubtitle": "Koble Plex eller Jellyfin til en annen profil fรธrst.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "Fra ${displayName}", "borrowConnectionBorrowed": "Tilkobling lรฅnt.", "borrowFailed": "Kunne ikke lรฅne tilkoblingen.", @@ -944,7 +953,7 @@ "favorites": "Favoritter", "reorderFavorites": "Endre rekkefรธlge pรฅ favoritter", "favoritesLoadFailed": "Kunne ikke laste inn favoritter. Kontroller tilkoblingen og prรธv pรฅ nytt.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Bli med i pรฅgรฅende รธkt", "watchFromStart": "Se fra starten (${minutes} min siden)", "watchLive": "Se direkte", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 7e369c3d..4c50e2b8 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "Wachten op goedkeuringโ€ฆ", "quickConnectCancel": "Annuleren", "quickConnectExpired": "Quick Connect is verlopen. Probeer opnieuw.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Annuleren", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Samen Kijken Relay", "watchTogetherRelayDescription": "Stel een aangepaste relay in. Iedereen moet dezelfde server gebruiken.", "watchTogetherRelayHint": "https://mijn-relay.voorbeeld.nl", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Crashrapportage", "crashReportingDescription": "Crashrapporten verzenden om de app te verbeteren", "debugLogging": "Debug logging", @@ -197,12 +197,10 @@ "exportSettings": "Instellingen exporteren", "exportSettingsDescription": "Sla je voorkeuren op in een bestand", "exportSettingsSuccess": "Instellingen geรซxporteerd", - "exportSettingsFailed": "Kon instellingen niet exporteren", "importSettings": "Instellingen importeren", "importSettingsDescription": "Voorkeuren herstellen vanuit een bestand", "importSettingsConfirm": "Hiermee worden je huidige instellingen vervangen. Doorgaan?", "importSettingsSuccess": "Instellingen geรฏmporteerd", - "importSettingsFailed": "Kon instellingen niet importeren", "importSettingsInvalidFile": "Dit bestand is geen geldige Plezy-export", "importSettingsNoUser": "Meld je aan voordat je instellingen importeert", "shortcutsReset": "Sneltoetsen gereset naar standaard", @@ -217,6 +215,7 @@ "validationErrorDuration": "Duur moet tussen ${min} en ${max} ${unit} zijn", "shortcutAlreadyAssigned": "Sneltoets al toegewezen aan ${action}", "shortcutUpdated": "Sneltoets bijgewerkt voor ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Automatisch Overslaan", "autoSkipIntro": "Intro Automatisch Overslaan", "autoSkipIntroDescription": "Intro-markeringen na enkele seconden automatisch overslaan", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Downloadlocatie gewijzigd", "downloadLocationReset": "Downloadlocatie hersteld naar standaard", "downloadLocationInvalid": "Geselecteerde map is niet beschrijfbaar", - "downloadLocationSelectError": "Kan map niet selecteren", + "downloadLocationPickerUnavailable": "Mapselectie is niet beschikbaar op dit apparaat", "downloadOnWifiOnly": "Alleen via WiFi downloaden", "downloadOnWifiOnlyDescription": "Voorkom downloads bij gebruik van mobiele data", "autoRemoveWatchedDownloads": "Bekeken downloads automatisch verwijderen", @@ -443,7 +442,11 @@ "brightness": "Helderheid", "hexColor": "Hexkleur", "expandText": "Tekst uitvouwen", - "collapseText": "Tekst samenvouwen" + "collapseText": "Tekst samenvouwen", + "alphabetNavigation": "Alfabetische navigatie", + "alphabetScrollHint": "Veeg omhoog of omlaag om per letter te bewegen", + "rowColumnPosition": "Rij ${row} van ${rowCount}, kolom ${column} van ${columnCount}", + "rowPosition": "Rij ${row} van ${rowCount}" }, "tooltips": { "shufflePlay": "Willekeurig afspelen", @@ -451,6 +454,9 @@ "markAsWatched": "Markeer als gekeken", "markAsUnwatched": "Markeer als ongekeken" }, + "audioTracks": { + "track": "Audiospoor ${n}" + }, "videoControls": { "audioLabel": "Audio", "subtitlesLabel": "Ondertitels", @@ -478,6 +484,8 @@ "playNext": "Volgende afspelen", "playButton": "Afspelen", "pauseButton": "Pauzeren", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Terugspoelen ${seconds} seconden", "seekForwardButton": "Vooruitspoelen ${seconds} seconden", "previousButton": "Vorige aflevering", @@ -553,11 +561,11 @@ "streamInterrupted": "De stream is onderbroken. Druk op afspelen of spoel om het opnieuw te proberen.", "liveStreamInterrupted": "De livestream is onderbroken. Druk op afspelen om het opnieuw te proberen.", "fileInfoNotAvailable": "Bestand informatie niet beschikbaar", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Fout bij laden bestand info: ${error}", "errorLoadingSeries": "Fout bij laden serie", "musicNotSupported": "Muziek afspelen wordt nog niet ondersteund", @@ -680,6 +688,7 @@ "borrowExplain": "Leen de verbinding van een ander profiel. PIN-beveiligde profielen vereisen een PIN.", "borrowEmpty": "Nog niets te lenen.", "borrowEmptySubtitle": "Verbind Plex of Jellyfin eerst met een ander profiel.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "Van ${displayName}", "borrowConnectionBorrowed": "Verbinding geleend.", "borrowFailed": "Kan verbinding niet lenen.", @@ -944,7 +953,7 @@ "favorites": "Favorieten", "reorderFavorites": "Favorieten herordenen", "favoritesLoadFailed": "Favorieten konden niet worden geladen. Controleer je verbinding en probeer het opnieuw.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Deelnemen aan lopende sessie", "watchFromStart": "Kijk vanaf het begin (${minutes} min geleden)", "watchLive": "Live kijken", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 72038214..7b90de2c 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "Oczekiwanie na zatwierdzenieโ€ฆ", "quickConnectCancel": "Anuluj", "quickConnectExpired": "Quick Connect wygasล‚. Sprรณbuj ponownie.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Anuluj", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Relay Oglฤ…daj Razem", "watchTogetherRelayDescription": "Ustaw wล‚asny relay. Wszyscy muszฤ… uลผywaฤ‡ tego samego serwera.", "watchTogetherRelayHint": "https://moj-relay.przyklad.pl", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Raportowanie bล‚ฤ™dรณw", "crashReportingDescription": "Wysyล‚aj raporty o bล‚ฤ™dach, aby pomรณc ulepszyฤ‡ aplikacjฤ™", "debugLogging": "Logowanie debugowania", @@ -197,12 +197,10 @@ "exportSettings": "Eksportuj ustawienia", "exportSettingsDescription": "Zapisz swoje preferencje do pliku", "exportSettingsSuccess": "Ustawienia wyeksportowane", - "exportSettingsFailed": "Nie moลผna wyeksportowaฤ‡ ustawieล„", "importSettings": "Importuj ustawienia", "importSettingsDescription": "Przywrรณฤ‡ preferencje z pliku", "importSettingsConfirm": "Bieลผฤ…ce ustawienia zostanฤ… zastฤ…pione. Kontynuowaฤ‡?", "importSettingsSuccess": "Ustawienia zaimportowane", - "importSettingsFailed": "Nie moลผna zaimportowaฤ‡ ustawieล„", "importSettingsInvalidFile": "Ten plik nie jest prawidล‚owym eksportem Plezy", "importSettingsNoUser": "Zaloguj siฤ™ przed importem ustawieล„", "shortcutsReset": "Skrรณty przywrรณcone do domyล›lnych", @@ -217,6 +215,7 @@ "validationErrorDuration": "Czas musi byฤ‡ miฤ™dzy ${min} a ${max} ${unit}", "shortcutAlreadyAssigned": "Skrรณt jest juลผ przypisany do ${action}", "shortcutUpdated": "Skrรณt zaktualizowany dla ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Automatyczne pomijanie", "autoSkipIntro": "Automatyczne pomijanie intro", "autoSkipIntroDescription": "Automatycznie pomijaj znaczniki intro po kilku sekundach", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Lokalizacja pobierania zmieniona", "downloadLocationReset": "Lokalizacja pobierania przywrรณcona do domyล›lnej", "downloadLocationInvalid": "Wybrany folder nie jest zapisywalny", - "downloadLocationSelectError": "Nie udaล‚o siฤ™ wybraฤ‡ folderu", + "downloadLocationPickerUnavailable": "Wybรณr folderu nie jest dostฤ™pny na tym urzฤ…dzeniu", "downloadOnWifiOnly": "Pobieraj tylko przez WiFi", "downloadOnWifiOnlyDescription": "Blokuj pobieranie na danych komรณrkowych", "autoRemoveWatchedDownloads": "Automatycznie usuwaj obejrzane pobrania", @@ -443,7 +442,11 @@ "brightness": "Jasnoล›ฤ‡", "hexColor": "Kolor szesnastkowy", "expandText": "Rozwiล„ tekst", - "collapseText": "Zwiล„ tekst" + "collapseText": "Zwiล„ tekst", + "alphabetNavigation": "Nawigacja alfabetyczna", + "alphabetScrollHint": "Przesuล„ w gรณrฤ™ lub w dรณล‚, aby przejล›ฤ‡ o literฤ™", + "rowColumnPosition": "Wiersz ${row} z ${rowCount}, kolumna ${column} z ${columnCount}", + "rowPosition": "Wiersz ${row} z ${rowCount}" }, "tooltips": { "shufflePlay": "Odtwarzanie losowe", @@ -451,6 +454,9 @@ "markAsWatched": "Oznacz jako obejrzane", "markAsUnwatched": "Oznacz jako nieobejrzane" }, + "audioTracks": { + "track": "ลšcieลผka audio ${n}" + }, "videoControls": { "audioLabel": "Audio", "subtitlesLabel": "Napisy", @@ -478,6 +484,8 @@ "playNext": "Odtwรณrz nastฤ™pny", "playButton": "Odtwรณrz", "pauseButton": "Pauza", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Przewiล„ do tyล‚u o ${seconds} sekund", "seekForwardButton": "Przewiล„ do przodu o ${seconds} sekund", "previousButton": "Poprzedni odcinek", @@ -555,11 +563,11 @@ "streamInterrupted": "Strumieล„ zostaล‚ przerwany. Naciล›nij odtwarzanie lub przewiล„, aby sprรณbowaฤ‡ ponownie.", "liveStreamInterrupted": "Transmisja na ลผywo zostaล‚a przerwana. Naciล›nij odtwarzanie, aby sprรณbowaฤ‡ ponownie.", "fileInfoNotAvailable": "Informacje o pliku niedostฤ™pne", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Bล‚ฤ…d ล‚adowania informacji o pliku: ${error}", "errorLoadingSeries": "Bล‚ฤ…d ล‚adowania serialu", "musicNotSupported": "Odtwarzanie muzyki nie jest jeszcze obsล‚ugiwane", @@ -682,6 +690,7 @@ "borrowExplain": "Poลผycz poล‚ฤ…czenie z innego profilu. Profile chronione PIN wymagajฤ… PIN-u.", "borrowEmpty": "Nic do poลผyczenia.", "borrowEmptySubtitle": "Najpierw poล‚ฤ…cz Plex lub Jellyfin z innym profilem.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "Od ${displayName}", "borrowConnectionBorrowed": "Poล‚ฤ…czenie poลผyczone.", "borrowFailed": "Nie udaล‚o siฤ™ poลผyczyฤ‡ poล‚ฤ…czenia.", @@ -948,7 +957,7 @@ "favorites": "Ulubione", "reorderFavorites": "Zmieล„ kolejnoล›ฤ‡ ulubionych", "favoritesLoadFailed": "Nie udaล‚o siฤ™ wczytaฤ‡ ulubionych. Sprawdลบ poล‚ฤ…czenie i sprรณbuj ponownie.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Doล‚ฤ…cz do trwajฤ…cej sesji", "watchFromStart": "Oglฤ…daj od poczฤ…tku (${minutes} min temu)", "watchLive": "Oglฤ…daj na ลผywo", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index 2cd061e3..fc976110 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "A aguardar aprovaรงรฃoโ€ฆ", "quickConnectCancel": "Cancelar", "quickConnectExpired": "Quick Connect expirou. Tente novamente.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Cancelar", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Relay do Assistir Juntos", "watchTogetherRelayDescription": "Defina um relay personalizado. Todos devem usar o mesmo servidor.", "watchTogetherRelayHint": "https://meu-relay.exemplo.com.br", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Relatรณrio de Erros", "crashReportingDescription": "Enviar relatรณrios de erros para ajudar a melhorar o app", "debugLogging": "Log de Depuraรงรฃo", @@ -197,12 +197,10 @@ "exportSettings": "Exportar Configuraรงรตes", "exportSettingsDescription": "Salve suas preferรชncias em um arquivo", "exportSettingsSuccess": "Configuraรงรตes exportadas", - "exportSettingsFailed": "Nรฃo foi possรญvel exportar as configuraรงรตes", "importSettings": "Importar Configuraรงรตes", "importSettingsDescription": "Restaurar preferรชncias a partir de um arquivo", "importSettingsConfirm": "Isso substituirรก suas configuraรงรตes atuais. Continuar?", "importSettingsSuccess": "Configuraรงรตes importadas", - "importSettingsFailed": "Nรฃo foi possรญvel importar as configuraรงรตes", "importSettingsInvalidFile": "Este arquivo nรฃo รฉ uma exportaรงรฃo vรกlida do Plezy", "importSettingsNoUser": "Entre na conta antes de importar as configuraรงรตes", "shortcutsReset": "Atalhos redefinidos para o padrรฃo", @@ -217,6 +215,7 @@ "validationErrorDuration": "A duraรงรฃo deve ser entre ${min} e ${max} ${unit}", "shortcutAlreadyAssigned": "Atalho jรก atribuรญdo a ${action}", "shortcutUpdated": "Atalho atualizado para ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Pular Automaticamente", "autoSkipIntro": "Pular Intro Automaticamente", "autoSkipIntroDescription": "Pular marcadores de intro automaticamente apรณs alguns segundos", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Local de download alterado", "downloadLocationReset": "Local de download redefinido para padrรฃo", "downloadLocationInvalid": "A pasta selecionada nรฃo permite gravaรงรฃo", - "downloadLocationSelectError": "Falha ao selecionar pasta", + "downloadLocationPickerUnavailable": "A seleรงรฃo de pasta nรฃo estรก disponรญvel neste dispositivo", "downloadOnWifiOnly": "Baixar apenas no WiFi", "downloadOnWifiOnlyDescription": "Impedir downloads quando em dados mรณveis", "autoRemoveWatchedDownloads": "Remover downloads assistidos automaticamente", @@ -443,7 +442,11 @@ "brightness": "Brilho", "hexColor": "Cor hexadecimal", "expandText": "Expandir texto", - "collapseText": "Recolher texto" + "collapseText": "Recolher texto", + "alphabetNavigation": "Navegaรงรฃo alfabรฉtica", + "alphabetScrollHint": "Deslize para cima ou para baixo para avanรงar por letra", + "rowColumnPosition": "Linha ${row} de ${rowCount}, coluna ${column} de ${columnCount}", + "rowPosition": "Linha ${row} de ${rowCount}" }, "tooltips": { "shufflePlay": "Reproduรงรฃo aleatรณria", @@ -451,6 +454,9 @@ "markAsWatched": "Marcar como assistido", "markAsUnwatched": "Marcar como nรฃo assistido" }, + "audioTracks": { + "track": "Faixa de รกudio ${n}" + }, "videoControls": { "audioLabel": "รudio", "subtitlesLabel": "Legendas", @@ -478,6 +484,8 @@ "playNext": "Reproduzir Prรณximo", "playButton": "Reproduzir", "pauseButton": "Pausar", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Retroceder ${seconds} segundos", "seekForwardButton": "Avanรงar ${seconds} segundos", "previousButton": "Episรณdio anterior", @@ -553,11 +561,11 @@ "streamInterrupted": "A transmissรฃo foi interrompida. Toque em reproduzir ou avance para tentar novamente.", "liveStreamInterrupted": "A transmissรฃo ao vivo foi interrompida. Toque em reproduzir para tentar novamente.", "fileInfoNotAvailable": "Informaรงรตes do arquivo nรฃo disponรญveis", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Erro ao carregar info do arquivo: ${error}", "errorLoadingSeries": "Erro ao carregar sรฉrie", "musicNotSupported": "Reproduรงรฃo de mรบsica ainda nรฃo รฉ suportada", @@ -680,6 +688,7 @@ "borrowExplain": "Use a conexรฃo de outro perfil. Perfis protegidos por PIN exigem PIN.", "borrowEmpty": "Nada para emprestar ainda.", "borrowEmptySubtitle": "Conecte Plex ou Jellyfin a outro perfil primeiro.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "De ${displayName}", "borrowConnectionBorrowed": "Conexรฃo tomada emprestada.", "borrowFailed": "Nรฃo foi possรญvel tomar a conexรฃo emprestada.", @@ -944,7 +953,7 @@ "favorites": "Favoritos", "reorderFavorites": "Reordenar favoritos", "favoritesLoadFailed": "Nรฃo foi possรญvel carregar os favoritos. Verifique sua conexรฃo e tente novamente.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Entrar na sessรฃo em andamento", "watchFromStart": "Assistir do inรญcio (${minutes} min atrรกs)", "watchLive": "Assistir ao vivo", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index 2929356f..a7d8743a 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "ะžะถะธะดะฐะฝะธะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธัโ€ฆ", "quickConnectCancel": "ะžั‚ะผะตะฝะฐ", "quickConnectExpired": "ะกั€ะพะบ Quick Connect ะธัั‚ะตะบ. ะŸะพะฟั€ะพะฑัƒะนั‚ะต ัะฝะพะฒะฐ.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "ะžั‚ะผะตะฝะฐ", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Relay ัะพะฒะผะตัั‚ะฝะพะณะพ ะฟั€ะพัะผะพั‚ั€ะฐ", "watchTogetherRelayDescription": "ะ—ะฐะดะฐะนั‚ะต ัะฒะพะน relay. ะ’ัะต ะดะพะปะถะฝั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะดะธะฝ ัะตั€ะฒะตั€.", "watchTogetherRelayHint": "https://my-relay.example.com", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "ะžั‚ั‡ั‘ั‚ั‹ ะพะฑ ะพัˆะธะฑะบะฐั…", "crashReportingDescription": "ะžั‚ะฟั€ะฐะฒะปัั‚ัŒ ะพั‚ั‡ั‘ั‚ั‹ ะพะฑ ะพัˆะธะฑะบะฐั… ะดะปั ัƒะปัƒั‡ัˆะตะฝะธั ะฟั€ะธะปะพะถะตะฝะธั", "debugLogging": "ะ–ัƒั€ะฝะฐะป ะพั‚ะปะฐะดะบะธ", @@ -197,12 +197,10 @@ "exportSettings": "ะญะบัะฟะพั€ั‚ ะฝะฐัั‚ั€ะพะตะบ", "exportSettingsDescription": "ะกะพั…ั€ะฐะฝะธั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ ะฒ ั„ะฐะนะป", "exportSettingsSuccess": "ะะฐัั‚ั€ะพะนะบะธ ัะบัะฟะพั€ั‚ะธั€ะพะฒะฐะฝั‹", - "exportSettingsFailed": "ะะต ัƒะดะฐะปะพััŒ ัะบัะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ", "importSettings": "ะ˜ะผะฟะพั€ั‚ ะฝะฐัั‚ั€ะพะตะบ", "importSettingsDescription": "ะ’ะพััั‚ะฐะฝะพะฒะธั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ ะธะท ั„ะฐะนะปะฐ", "importSettingsConfirm": "ะญั‚ะพ ะทะฐะผะตะฝะธั‚ ะฒะฐัˆะธ ั‚ะตะบัƒั‰ะธะต ะฝะฐัั‚ั€ะพะนะบะธ. ะŸั€ะพะดะพะปะถะธั‚ัŒ?", "importSettingsSuccess": "ะะฐัั‚ั€ะพะนะบะธ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะฝั‹", - "importSettingsFailed": "ะะต ัƒะดะฐะปะพััŒ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ", "importSettingsInvalidFile": "ะญั‚ะพั‚ ั„ะฐะนะป ะฝะต ัะฒะปัะตั‚ัั ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะผ ัะบัะฟะพั€ั‚ะพะผ ะฝะฐัั‚ั€ะพะตะบ Plezy", "importSettingsNoUser": "ะ’ะพะนะดะธั‚ะต ะฒ ัะธัั‚ะตะผัƒ ะฟะตั€ะตะด ะธะผะฟะพั€ั‚ะพะผ ะฝะฐัั‚ั€ะพะตะบ", "shortcutsReset": "ะ“ะพั€ัั‡ะธะต ะบะปะฐะฒะธัˆะธ ัะฑั€ะพัˆะตะฝั‹ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", @@ -217,6 +215,7 @@ "validationErrorDuration": "ะ”ะปะธั‚ะตะปัŒะฝะพัั‚ัŒ ะดะพะปะถะฝะฐ ะฑั‹ั‚ัŒ ะพั‚ ${min} ะดะพ ${max} ${unit}", "shortcutAlreadyAssigned": "ะšะปะฐะฒะธัˆะฐ ัƒะถะต ะฝะฐะทะฝะฐั‡ะตะฝะฐ ะดะปั ${action}", "shortcutUpdated": "ะšะปะฐะฒะธัˆะฐ ะพะฑะฝะพะฒะปะตะฝะฐ ะดะปั ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "ะะฒั‚ะพะฟั€ะพะฟัƒัะบ", "autoSkipIntro": "ะะฒั‚ะพะฟั€ะพะฟัƒัะบ ะฒัั‚ัƒะฟะปะตะฝะธั", "autoSkipIntroDescription": "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟั€ะพะฟัƒัะบะฐั‚ัŒ ะผะฐั€ะบะตั€ั‹ ะฒัั‚ัƒะฟะปะตะฝะธั ั‡ะตั€ะตะท ะฝะตัะบะพะปัŒะบะพ ัะตะบัƒะฝะด", @@ -242,7 +241,7 @@ "downloadLocationChanged": "ะœะตัั‚ะพ ะทะฐะณั€ัƒะทะบะธ ะธะทะผะตะฝะตะฝะพ", "downloadLocationReset": "ะœะตัั‚ะพ ะทะฐะณั€ัƒะทะบะธ ัะฑั€ะพัˆะตะฝะพ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ", "downloadLocationInvalid": "ะ’ั‹ะฑั€ะฐะฝะฝะฐั ะฟะฐะฟะบะฐ ะฝะตะดะพัั‚ัƒะฟะฝะฐ ะดะปั ะทะฐะฟะธัะธ", - "downloadLocationSelectError": "ะะต ัƒะดะฐะปะพััŒ ะฒั‹ะฑั€ะฐั‚ัŒ ะฟะฐะฟะบัƒ", + "downloadLocationPickerUnavailable": "ะ’ั‹ะฑะพั€ ะฟะฐะฟะบะธ ะฝะตะดะพัั‚ัƒะฟะตะฝ ะฝะฐ ัั‚ะพะผ ัƒัั‚ั€ะพะนัั‚ะฒะต", "downloadOnWifiOnly": "ะ—ะฐะณั€ัƒะถะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟะพ WiFi", "downloadOnWifiOnlyDescription": "ะ—ะฐะฟั€ะตั‚ะธั‚ัŒ ะทะฐะณั€ัƒะทะบัƒ ะฟะพ ะผะพะฑะธะปัŒะฝั‹ะผ ะดะฐะฝะฝั‹ะผ", "autoRemoveWatchedDownloads": "ะะฒั‚ะพัƒะดะฐะปะตะฝะธะต ะฟั€ะพัะผะพั‚ั€ะตะฝะฝั‹ั… ะทะฐะณั€ัƒะทะพะบ", @@ -443,7 +442,11 @@ "brightness": "ะฏั€ะบะพัั‚ัŒ", "hexColor": "ะจะตัั‚ะฝะฐะดั†ะฐั‚ะตั€ะธั‡ะฝั‹ะน ั†ะฒะตั‚", "expandText": "ะ ะฐะทะฒะตั€ะฝัƒั‚ัŒ ั‚ะตะบัั‚", - "collapseText": "ะกะฒะตั€ะฝัƒั‚ัŒ ั‚ะตะบัั‚" + "collapseText": "ะกะฒะตั€ะฝัƒั‚ัŒ ั‚ะตะบัั‚", + "alphabetNavigation": "ะะฐะฒะธะณะฐั†ะธั ะฟะพ ะฐะปั„ะฐะฒะธั‚ัƒ", + "alphabetScrollHint": "ะŸั€ะพะฒะตะดะธั‚ะต ะฒะฒะตั€ั… ะธะปะธ ะฒะฝะธะท ะดะปั ะฟะตั€ะตั…ะพะดะฐ ะฟะพ ะฑัƒะบะฒะฐะผ", + "rowColumnPosition": "ะกั‚ั€ะพะบะฐ ${row} ะธะท ${rowCount}, ัั‚ะพะปะฑะตั† ${column} ะธะท ${columnCount}", + "rowPosition": "ะกั‚ั€ะพะบะฐ ${row} ะธะท ${rowCount}" }, "tooltips": { "shufflePlay": "ะกะปัƒั‡ะฐะนะฝะพะต ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต", @@ -451,6 +454,9 @@ "markAsWatched": "ะžั‚ะผะตั‚ะธั‚ัŒ ะบะฐะบ ะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะต", "markAsUnwatched": "ะžั‚ะผะตั‚ะธั‚ัŒ ะบะฐะบ ะฝะตะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะต" }, + "audioTracks": { + "track": "ะัƒะดะธะพะดะพั€ะพะถะบะฐ ${n}" + }, "videoControls": { "audioLabel": "ะัƒะดะธะพ", "subtitlesLabel": "ะกัƒะฑั‚ะธั‚ั€ั‹", @@ -478,6 +484,8 @@ "playNext": "ะกะปะตะดัƒัŽั‰ะตะต", "playButton": "ะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธ", "pauseButton": "ะŸะฐัƒะทะฐ", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "ะŸะตั€ะตะผะพั‚ะบะฐ ะฝะฐะทะฐะด ะฝะฐ ${seconds} ัะตะบัƒะฝะด", "seekForwardButton": "ะŸะตั€ะตะผะพั‚ะบะฐ ะฒะฟะตั€ั‘ะด ะฝะฐ ${seconds} ัะตะบัƒะฝะด", "previousButton": "ะŸั€ะตะดั‹ะดัƒั‰ะธะน ัะฟะธะทะพะด", @@ -555,11 +563,11 @@ "streamInterrupted": "ะŸะพั‚ะพะบ ะฟั€ะตั€ะฒะฐะปัั. ะะฐะถะผะธั‚ะต ยซะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธยป ะธะปะธ ะฟะตั€ะตะผะพั‚ะฐะนั‚ะต, ั‡ั‚ะพะฑั‹ ะฟะพะฒั‚ะพั€ะธั‚ัŒ ะฟะพะฟั‹ั‚ะบัƒ.", "liveStreamInterrupted": "ะŸั€ัะผะฐั ั‚ั€ะฐะฝัะปัั†ะธั ะฟั€ะตั€ะฒะฐะปะฐััŒ. ะะฐะถะผะธั‚ะต ยซะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธยป, ั‡ั‚ะพะฑั‹ ะฟะพะฒั‚ะพั€ะธั‚ัŒ ะฟะพะฟั‹ั‚ะบัƒ.", "fileInfoNotAvailable": "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ั„ะฐะนะปะต ะฝะตะดะพัั‚ัƒะฟะฝะฐ", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "ะžัˆะธะฑะบะฐ ะทะฐะณั€ัƒะทะบะธ ะธะฝั„ะพั€ะผะฐั†ะธะธ ะพ ั„ะฐะนะปะต: ${error}", "errorLoadingSeries": "ะžัˆะธะฑะบะฐ ะทะฐะณั€ัƒะทะบะธ ัะตั€ะธะฐะปะฐ", "musicNotSupported": "ะ’ะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต ะผัƒะทั‹ะบะธ ะฟะพะบะฐ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั", @@ -682,6 +690,7 @@ "borrowExplain": "ะ—ะฐะธะผัั‚ะฒัƒะนั‚ะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะดั€ัƒะณะพะณะพ ะฟั€ะพั„ะธะปั. ะ”ะปั ะฟั€ะพั„ะธะปะตะน ั PIN ะฝัƒะถะตะฝ PIN.", "borrowEmpty": "ะŸะพะบะฐ ะฝะตั‡ะตะณะพ ะทะฐะธะผัั‚ะฒะพะฒะฐั‚ัŒ.", "borrowEmptySubtitle": "ะกะฝะฐั‡ะฐะปะฐ ะฟะพะดะบะปัŽั‡ะธั‚ะต Plex ะธะปะธ Jellyfin ะบ ะดั€ัƒะณะพะผัƒ ะฟั€ะพั„ะธะปัŽ.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "ะ˜ะท ${displayName}", "borrowConnectionBorrowed": "ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะทะฐะธะผัั‚ะฒะพะฒะฐะฝะพ.", "borrowFailed": "ะะต ัƒะดะฐะปะพััŒ ะทะฐะธะผัั‚ะฒะพะฒะฐั‚ัŒ ะฟะพะดะบะปัŽั‡ะตะฝะธะต.", @@ -948,7 +957,7 @@ "favorites": "ะ˜ะทะฑั€ะฐะฝะฝะพะต", "reorderFavorites": "ะ˜ะทะผะตะฝะธั‚ัŒ ะฟะพั€ัะดะพะบ ะธะทะฑั€ะฐะฝะฝะพะณะพ", "favoritesLoadFailed": "ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ ะธะทะฑั€ะฐะฝะฝะพะต. ะŸั€ะพะฒะตั€ัŒั‚ะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะธ ะฟะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "ะŸั€ะธัะพะตะดะธะฝะธั‚ัŒัั ะบ ั‚ะตะบัƒั‰ะตะผัƒ ัะตะฐะฝััƒ", "watchFromStart": "ะกะผะพั‚ั€ะตั‚ัŒ ัะฝะฐั‡ะฐะปะฐ (${minutes} ะผะธะฝ. ะฝะฐะทะฐะด)", "watchLive": "ะกะผะพั‚ั€ะตั‚ัŒ ะฒ ะฟั€ัะผะพะผ ัั„ะธั€ะต", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 901990ae..b4ba6095 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,7 +4,7 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 16 -/// Strings: 23027 (1439 per locale) +/// Strings: 23082 (1442 per locale) // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_bg.g.dart b/lib/i18n/strings_bg.g.dart index acd97fdc..047b1be7 100644 --- a/lib/i18n/strings_bg.g.dart +++ b/lib/i18n/strings_bg.g.dart @@ -52,6 +52,7 @@ class TranslationsBg extends Translations with BaseTranslations 'ะ˜ะทั‡ะฐะบะฒะฐะฝะต ะฝะฐ ะพะดะพะฑั€ะตะฝะธะตโ€ฆ'; @override String get quickConnectCancel => 'ะžั‚ะบะฐะท'; @override String get quickConnectExpired => 'Quick Connect ะธะทั‚ะตั‡ะต. ะžะฟะธั‚ะฐะนั‚ะต ะพั‚ะฝะพะฒะพ.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsBg extends TranslationsSettingsEn { @override String get libraryDensity => 'ะŸะปัŠั‚ะฝะพัั‚ ะฝะฐ ะฑะธะฑะปะธะพั‚ะตะบะฐั‚ะฐ'; @override String get compact => 'ะšะพะผะฟะฐะบั‚ะฝะฐ'; @override String get comfortable => 'ะฃะดะพะฑะฝะฐ'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'ะ ะตะถะธะผ ะฝะฐ ะธะทะณะปะตะด'; @override String get gridView => 'ะœั€ะตะถะฐ'; @override String get listView => 'ะกะฟะธััŠะบ'; @@ -311,7 +310,7 @@ class _TranslationsSettingsBg extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'ะ ะตะปะตะน ััŠั€ะฒัŠั€ ะทะฐ ะณะปะตะดะฐะฝะต ะทะฐะตะดะฝะพ'; @override String get watchTogetherRelayDescription => 'ะ—ะฐะดะฐะน ัะพะฑัั‚ะฒะตะฝ ั€ะตะปะตะน ััŠั€ะฒัŠั€. ะ’ัะธั‡ะบะธ ั‚ั€ัะฑะฒะฐ ะดะฐ ะธะทะฟะพะปะทะฒะฐั‚ ััŠั‰ะธั ััŠั€ะฒัŠั€.'; @override String get watchTogetherRelayHint => 'https://my-relay.example.com'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'ะ”ะพะบะปะฐะดะฒะฐะฝะต ะฝะฐ ัั€ะธะฒะพะฒะต'; @override String get crashReportingDescription => 'ะ˜ะทะฟั€ะฐั‰ะฐะน ะดะพะบะปะฐะดะธ ะทะฐ ัั€ะธะฒะพะฒะต, ะทะฐ ะดะฐ ะฟะพะผะพะณะฝะตัˆ ะทะฐ ะฟะพะดะพะฑั€ัะฒะฐะฝะต ะฝะฐ ะฟั€ะธะปะพะถะตะฝะธะตั‚ะพ'; @override String get debugLogging => 'ะ›ะพะณะพะฒะต ะทะฐ ะพั‚ัั‚ั€ะฐะฝัะฒะฐะฝะต ะฝะฐ ะณั€ะตัˆะบะธ'; @@ -328,12 +327,10 @@ class _TranslationsSettingsBg extends TranslationsSettingsEn { @override String get exportSettings => 'ะ•ะบัะฟะพั€ั‚ะธั€ะฐะน ะฝะฐัั‚ั€ะพะนะบะธั‚ะต'; @override String get exportSettingsDescription => 'ะ—ะฐะฟะฐะทะธ ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะฝะธัั‚ะฐ ัะธ ะฒัŠะฒ ั„ะฐะนะป'; @override String get exportSettingsSuccess => 'ะะฐัั‚ั€ะพะนะบะธั‚ะต ัะฐ ะตะบัะฟะพั€ั‚ะธั€ะฐะฝะธ'; - @override String get exportSettingsFailed => 'ะะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ะฑัŠะดะฐั‚ ะตะบัะฟะพั€ั‚ะธั€ะฐะฝะธ'; @override String get importSettings => 'ะ˜ะผะฟะพั€ั‚ะธั€ะฐะน ะฝะฐัั‚ั€ะพะนะบะธ'; @override String get importSettingsDescription => 'ะ’ัŠะทัั‚ะฐะฝะพะฒะธ ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะฝะธั ะพั‚ ั„ะฐะนะป'; @override String get importSettingsConfirm => 'ะขะพะฒะฐ ั‰ะต ะทะฐะผะตะฝะธ ั‚ะตะบัƒั‰ะธั‚ะต ะฒะธ ะฝะฐัั‚ั€ะพะนะบะธ. ะŸั€ะพะดัŠะปะถะฐะฒะฐั‚ะต ะปะธ?'; @override String get importSettingsSuccess => 'ะะฐัั‚ั€ะพะนะบะธั‚ะต ัะฐ ะธะผะฟะพั€ั‚ะธั€ะฐะฝะธ'; - @override String get importSettingsFailed => 'ะะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ะฑัŠะดะฐั‚ ะธะผะฟะพั€ั‚ะธั€ะฐะฝะธ'; @override String get importSettingsInvalidFile => 'ะขะพะทะธ ั„ะฐะนะป ะฝะต ะต ะฒะฐะปะธะดะตะฝ ะตะบัะฟะพั€ั‚ ะฝะฐ ะฝะฐัั‚ั€ะพะนะบะธ ะพั‚ Plezy'; @override String get importSettingsNoUser => 'ะ’ะปะตะทั‚ะต, ะฟั€ะตะดะธ ะดะฐ ะธะผะฟะพั€ั‚ะธั€ะฐั‚ะต ะฝะฐัั‚ั€ะพะนะบะธ'; @override String get shortcutsReset => 'ะšะปะฐะฒะธัˆะฝะธั‚ะต ะบะพะผะฑะธะฝะฐั†ะธะธ ัะฐ ะฝัƒะปะธั€ะฐะฝะธ ะดะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต'; @@ -348,6 +345,7 @@ class _TranslationsSettingsBg extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'ะŸั€ะพะดัŠะปะถะธั‚ะตะปะฝะพัั‚ั‚ะฐ ั‚ั€ัะฑะฒะฐ ะดะฐ ะต ะผะตะถะดัƒ ${min} ะธ ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'ะšะปะฐะฒะธัˆะฝะฐั‚ะฐ ะบะพะผะฑะธะฝะฐั†ะธั ะฒะตั‡ะต ะต ะฝะฐะทะฝะฐั‡ะตะฝะฐ ะทะฐ ${action}'; @override String shortcutUpdated({required Object action}) => 'ะšะปะฐะฒะธัˆะฝะฐั‚ะฐ ะบะพะผะฑะธะฝะฐั†ะธั ะต ะพะฑะฝะพะฒะตะฝะฐ ะทะฐ ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะฝะต'; @override String get autoSkipIntro => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะฝะต ะฝะฐ ะธะฝั‚ั€ะพ'; @override String get autoSkipIntroDescription => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะน ะธะฝั‚ั€ะพ ะผะฐั€ะบะตั€ะธ ัะปะตะด ะฝัะบะพะปะบะพ ัะตะบัƒะฝะดะธ'; @@ -373,7 +371,7 @@ class _TranslationsSettingsBg extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'ะœะตัั‚ะพะฟะพะปะพะถะตะฝะธะตั‚ะพ ะทะฐ ะธะทั‚ะตะณะปัะฝะธั ะต ะฟั€ะพะผะตะฝะตะฝะพ'; @override String get downloadLocationReset => 'ะœะตัั‚ะพะฟะพะปะพะถะตะฝะธะตั‚ะพ ะทะฐ ะธะทั‚ะตะณะปัะฝะธั ะต ะฒัŠั€ะฝะฐั‚ะพ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต'; @override String get downloadLocationInvalid => 'ะ˜ะทะฑั€ะฐะฝะฐั‚ะฐ ะฟะฐะฟะบะฐ ะฝะต ะต ะทะฐะฟะธัะฒะฐะตะผะฐ'; - @override String get downloadLocationSelectError => 'ะะตัƒัะฟะตัˆะตะฝ ะธะทะฑะพั€ ะฝะฐ ะฟะฐะฟะบะฐ'; + @override String get downloadLocationPickerUnavailable => 'ะ˜ะทะฑะพั€ัŠั‚ ะฝะฐ ะฟะฐะฟะบะฐ ะฝะต ะต ะฝะฐะปะธั‡ะตะฝ ะฝะฐ ั‚ะพะฒะฐ ัƒัั‚ั€ะพะนัั‚ะฒะพ'; @override String get downloadOnWifiOnly => 'ะ˜ะทั‚ะตะณะปัะฝะต ัะฐะผะพ ะฟั€ะตะท WiFi'; @override String get downloadOnWifiOnlyDescription => 'ะŸั€ะตะดะพั‚ะฒั€ะฐั‚ัะฒะฐะน ะธะทั‚ะตะณะปัะฝะธั ะฟั€ะตะท ะผะพะฑะธะปะฝะธ ะดะฐะฝะฝะธ'; @override String get autoRemoveWatchedDownloads => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฒะฐะน ะธะทะณะปะตะดะฐะฝะธั‚ะต ะธะทั‚ะตะณะปัะฝะธั'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityBg extends TranslationsAccessibilityEn { @override String get hexColor => 'ะจะตัั‚ะฝะฐะดะตัะตั‚ะธั‡ะตะฝ ั†ะฒัั‚'; @override String get expandText => 'ะ ะฐะทะณัŠะฝะธ ั‚ะตะบัั‚ะฐ'; @override String get collapseText => 'ะกะฒะธะน ั‚ะตะบัั‚ะฐ'; + @override String get alphabetNavigation => 'ะะฐะฒะธะณะฐั†ะธั ะฟะพ ะฐะทะฑัƒะบะฐั‚ะฐ'; + @override String get alphabetScrollHint => 'ะŸะปัŠะทะฝะตั‚ะต ะฝะฐะณะพั€ะต ะธะปะธ ะฝะฐะดะพะปัƒ, ะทะฐ ะดะฐ ะฟั€ะตะผะธะฝะตั‚ะต ะฟะพ ะฑัƒะบะฒะฐ'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'ะ ะตะด ${row} ะพั‚ ${rowCount}, ะบะพะปะพะฝะฐ ${column} ะพั‚ ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'ะ ะตะด ${row} ะพั‚ ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsBg extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'ะœะฐั€ะบะธั€ะฐะน ะบะฐั‚ะพ ะฝะตะณะปะตะดะฐะฝะพ'; } +// Path: audioTracks +class _TranslationsAudioTracksBg extends TranslationsAudioTracksEn { + _TranslationsAudioTracksBg._(TranslationsBg root) : this._root = root, super.internal(root); + + final TranslationsBg _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'ะัƒะดะธะพ ะฟัŠั‚ะตั‡ะบะฐ ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsBg extends TranslationsVideoControlsEn { _TranslationsVideoControlsBg._(TranslationsBg root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesBg extends TranslationsMessagesEn { @override String get streamInterrupted => 'ะŸะพั‚ะพะบัŠั‚ ะฟั€ะตะบัŠัะฝะฐ. ะะฐั‚ะธัะฝะตั‚ะต โ€žะŸัƒัะฝะธโ€œ ะธะปะธ ะฟั€ะตะฒัŠั€ั‚ะตั‚ะต, ะทะฐ ะดะฐ ะพะฟะธั‚ะฐั‚ะต ะพั‚ะฝะพะฒะพ.'; @override String get liveStreamInterrupted => 'ะŸะพั‚ะพะบัŠั‚ ะฝะฐ ะถะธะฒะพ ะฟั€ะตะบัŠัะฝะฐ. ะะฐั‚ะธัะฝะตั‚ะต โ€žะŸัƒัะฝะธโ€œ, ะทะฐ ะดะฐ ะพะฟะธั‚ะฐั‚ะต ะพั‚ะฝะพะฒะพ.'; @override String get fileInfoNotAvailable => 'ะ˜ะฝั„ะพั€ะผะฐั†ะธัั‚ะฐ ะทะฐ ั„ะฐะนะปะฐ ะฝะต ะต ะฝะฐะปะธั‡ะฝะฐ'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'ะ“ั€ะตัˆะบะฐ ะฟั€ะธ ะทะฐั€ะตะถะดะฐะฝะต ะฝะฐ ะธะฝั„ะพั€ะผะฐั†ะธั ะทะฐ ั„ะฐะนะปะฐ: ${error}'; @override String get errorLoadingSeries => 'ะ“ั€ะตัˆะบะฐ ะฟั€ะธ ะทะฐั€ะตะถะดะฐะฝะต ะฝะฐ ัะตั€ะธะฐะปะฐ'; @override String get musicNotSupported => 'ะ’ัŠะทะฟั€ะพะธะทะฒะตะถะดะฐะฝะตั‚ะพ ะฝะฐ ะผัƒะทะธะบะฐ ะฒัะต ะพั‰ะต ะฝะต ัะต ะฟะพะดะดัŠั€ะถะฐ'; @@ -867,6 +879,7 @@ class _TranslationsProfilesBg extends TranslationsProfilesEn { @override String get borrowExplain => 'ะ˜ะทะฟะพะปะทะฒะฐะน ะฒั€ัŠะทะบะฐ ะพั‚ ะดั€ัƒะณ ะฟั€ะพั„ะธะป. PIN-ะทะฐั‰ะธั‚ะตะฝะธั‚ะต ะฟั€ะพั„ะธะปะธ ะธะทะธัะบะฒะฐั‚ PIN.'; @override String get borrowEmpty => 'ะ’ัะต ะพั‰ะต ะฝัะผะฐ ะบะฐะบะฒะพ ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐ.'; @override String get borrowEmptySubtitle => 'ะŸัŠั€ะฒะพ ัะฒัŠั€ะถะตั‚ะต Plex ะธะปะธ Jellyfin ะบัŠะผ ะดั€ัƒะณ ะฟั€ะพั„ะธะป.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'ะžั‚ ${displayName}'; @override String get borrowConnectionBorrowed => 'ะ’ั€ัŠะทะบะฐั‚ะฐ ะต ะธะทะฟะพะปะทะฒะฐะฝะฐ.'; @override String get borrowFailed => 'ะะตัƒัะฟะตัˆะฝะพ ะธะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ ะฒั€ัŠะทะบะฐ.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvBg extends TranslationsLiveTvEn { @override String get favorites => 'ะ›ัŽะฑะธะผะธ'; @override String get reorderFavorites => 'ะŸั€ะตะฝะฐั€ะตะดะธ ะปัŽะฑะธะผะธั‚ะต'; @override String get favoritesLoadFailed => 'ะ›ัŽะฑะธะผะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ัะต ะทะฐั€ะตะดัั‚. ะŸั€ะพะฒะตั€ะตั‚ะต ะฒั€ัŠะทะบะฐั‚ะฐ ัะธ ะธ ะพะฟะธั‚ะฐะนั‚ะต ะพั‚ะฝะพะฒะพ.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'ะŸั€ะธััŠะตะดะธะฝะธ ัะต ะบัŠะผ ั‚ะตะบัƒั‰ะฐ ัะตัะธั'; @override String watchFromStart({required Object minutes}) => 'ะ“ะปะตะดะฐะน ะพั‚ ะฝะฐั‡ะฐะปะพั‚ะพ (ะฟั€ะตะดะธ ${minutes} ะผะธะฝ)'; @override String get watchLive => 'ะ“ะปะตะดะฐะน ะฝะฐ ะถะธะฒะพ'; @@ -2149,7 +2162,7 @@ extension on TranslationsBg { 'auth.quickConnectWaiting' => 'ะ˜ะทั‡ะฐะบะฒะฐะฝะต ะฝะฐ ะพะดะพะฑั€ะตะฝะธะตโ€ฆ', 'auth.quickConnectCancel' => 'ะžั‚ะบะฐะท', 'auth.quickConnectExpired' => 'Quick Connect ะธะทั‚ะตั‡ะต. ะžะฟะธั‚ะฐะนั‚ะต ะพั‚ะฝะพะฒะพ.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'ะžั‚ะบะฐะท', 'common.save' => 'ะ—ะฐะฟะฐะทะธ', 'common.close' => 'ะ—ะฐั‚ะฒะพั€ะธ', @@ -2233,8 +2246,6 @@ extension on TranslationsBg { 'settings.libraryDensity' => 'ะŸะปัŠั‚ะฝะพัั‚ ะฝะฐ ะฑะธะฑะปะธะพั‚ะตะบะฐั‚ะฐ', 'settings.compact' => 'ะšะพะผะฟะฐะบั‚ะฝะฐ', 'settings.comfortable' => 'ะฃะดะพะฑะฝะฐ', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'ะ ะตะถะธะผ ะฝะฐ ะธะทะณะปะตะด', 'settings.gridView' => 'ะœั€ะตะถะฐ', 'settings.listView' => 'ะกะฟะธััŠะบ', @@ -2304,7 +2315,7 @@ extension on TranslationsBg { 'settings.watchTogetherRelay' => 'ะ ะตะปะตะน ััŠั€ะฒัŠั€ ะทะฐ ะณะปะตะดะฐะฝะต ะทะฐะตะดะฝะพ', 'settings.watchTogetherRelayDescription' => 'ะ—ะฐะดะฐะน ัะพะฑัั‚ะฒะตะฝ ั€ะตะปะตะน ััŠั€ะฒัŠั€. ะ’ัะธั‡ะบะธ ั‚ั€ัะฑะฒะฐ ะดะฐ ะธะทะฟะพะปะทะฒะฐั‚ ััŠั‰ะธั ััŠั€ะฒัŠั€.', 'settings.watchTogetherRelayHint' => 'https://my-relay.example.com', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'ะ”ะพะบะปะฐะดะฒะฐะฝะต ะฝะฐ ัั€ะธะฒะพะฒะต', 'settings.crashReportingDescription' => 'ะ˜ะทะฟั€ะฐั‰ะฐะน ะดะพะบะปะฐะดะธ ะทะฐ ัั€ะธะฒะพะฒะต, ะทะฐ ะดะฐ ะฟะพะผะพะณะฝะตัˆ ะทะฐ ะฟะพะดะพะฑั€ัะฒะฐะฝะต ะฝะฐ ะฟั€ะธะปะพะถะตะฝะธะตั‚ะพ', 'settings.debugLogging' => 'ะ›ะพะณะพะฒะต ะทะฐ ะพั‚ัั‚ั€ะฐะฝัะฒะฐะฝะต ะฝะฐ ะณั€ะตัˆะบะธ', @@ -2321,12 +2332,10 @@ extension on TranslationsBg { 'settings.exportSettings' => 'ะ•ะบัะฟะพั€ั‚ะธั€ะฐะน ะฝะฐัั‚ั€ะพะนะบะธั‚ะต', 'settings.exportSettingsDescription' => 'ะ—ะฐะฟะฐะทะธ ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะฝะธัั‚ะฐ ัะธ ะฒัŠะฒ ั„ะฐะนะป', 'settings.exportSettingsSuccess' => 'ะะฐัั‚ั€ะพะนะบะธั‚ะต ัะฐ ะตะบัะฟะพั€ั‚ะธั€ะฐะฝะธ', - 'settings.exportSettingsFailed' => 'ะะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ะฑัŠะดะฐั‚ ะตะบัะฟะพั€ั‚ะธั€ะฐะฝะธ', 'settings.importSettings' => 'ะ˜ะผะฟะพั€ั‚ะธั€ะฐะน ะฝะฐัั‚ั€ะพะนะบะธ', 'settings.importSettingsDescription' => 'ะ’ัŠะทัั‚ะฐะฝะพะฒะธ ะฟั€ะตะดะฟะพั‡ะธั‚ะฐะฝะธั ะพั‚ ั„ะฐะนะป', 'settings.importSettingsConfirm' => 'ะขะพะฒะฐ ั‰ะต ะทะฐะผะตะฝะธ ั‚ะตะบัƒั‰ะธั‚ะต ะฒะธ ะฝะฐัั‚ั€ะพะนะบะธ. ะŸั€ะพะดัŠะปะถะฐะฒะฐั‚ะต ะปะธ?', 'settings.importSettingsSuccess' => 'ะะฐัั‚ั€ะพะนะบะธั‚ะต ัะฐ ะธะผะฟะพั€ั‚ะธั€ะฐะฝะธ', - 'settings.importSettingsFailed' => 'ะะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ะฑัŠะดะฐั‚ ะธะผะฟะพั€ั‚ะธั€ะฐะฝะธ', 'settings.importSettingsInvalidFile' => 'ะขะพะทะธ ั„ะฐะนะป ะฝะต ะต ะฒะฐะปะธะดะตะฝ ะตะบัะฟะพั€ั‚ ะฝะฐ ะฝะฐัั‚ั€ะพะนะบะธ ะพั‚ Plezy', 'settings.importSettingsNoUser' => 'ะ’ะปะตะทั‚ะต, ะฟั€ะตะดะธ ะดะฐ ะธะผะฟะพั€ั‚ะธั€ะฐั‚ะต ะฝะฐัั‚ั€ะพะนะบะธ', 'settings.shortcutsReset' => 'ะšะปะฐะฒะธัˆะฝะธั‚ะต ะบะพะผะฑะธะฝะฐั†ะธะธ ัะฐ ะฝัƒะปะธั€ะฐะฝะธ ะดะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต', @@ -2341,6 +2350,7 @@ extension on TranslationsBg { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'ะŸั€ะพะดัŠะปะถะธั‚ะตะปะฝะพัั‚ั‚ะฐ ั‚ั€ัะฑะฒะฐ ะดะฐ ะต ะผะตะถะดัƒ ${min} ะธ ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'ะšะปะฐะฒะธัˆะฝะฐั‚ะฐ ะบะพะผะฑะธะฝะฐั†ะธั ะฒะตั‡ะต ะต ะฝะฐะทะฝะฐั‡ะตะฝะฐ ะทะฐ ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'ะšะปะฐะฒะธัˆะฝะฐั‚ะฐ ะบะพะผะฑะธะฝะฐั†ะธั ะต ะพะฑะฝะพะฒะตะฝะฐ ะทะฐ ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะฝะต', 'settings.autoSkipIntro' => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะฝะต ะฝะฐ ะธะฝั‚ั€ะพ', 'settings.autoSkipIntroDescription' => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตัะบะฐั‡ะฐะน ะธะฝั‚ั€ะพ ะผะฐั€ะบะตั€ะธ ัะปะตะด ะฝัะบะพะปะบะพ ัะตะบัƒะฝะดะธ', @@ -2366,7 +2376,7 @@ extension on TranslationsBg { 'settings.downloadLocationChanged' => 'ะœะตัั‚ะพะฟะพะปะพะถะตะฝะธะตั‚ะพ ะทะฐ ะธะทั‚ะตะณะปัะฝะธั ะต ะฟั€ะพะผะตะฝะตะฝะพ', 'settings.downloadLocationReset' => 'ะœะตัั‚ะพะฟะพะปะพะถะตะฝะธะตั‚ะพ ะทะฐ ะธะทั‚ะตะณะปัะฝะธั ะต ะฒัŠั€ะฝะฐั‚ะพ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต', 'settings.downloadLocationInvalid' => 'ะ˜ะทะฑั€ะฐะฝะฐั‚ะฐ ะฟะฐะฟะบะฐ ะฝะต ะต ะทะฐะฟะธัะฒะฐะตะผะฐ', - 'settings.downloadLocationSelectError' => 'ะะตัƒัะฟะตัˆะตะฝ ะธะทะฑะพั€ ะฝะฐ ะฟะฐะฟะบะฐ', + 'settings.downloadLocationPickerUnavailable' => 'ะ˜ะทะฑะพั€ัŠั‚ ะฝะฐ ะฟะฐะฟะบะฐ ะฝะต ะต ะฝะฐะปะธั‡ะตะฝ ะฝะฐ ั‚ะพะฒะฐ ัƒัั‚ั€ะพะนัั‚ะฒะพ', 'settings.downloadOnWifiOnly' => 'ะ˜ะทั‚ะตะณะปัะฝะต ัะฐะผะพ ะฟั€ะตะท WiFi', 'settings.downloadOnWifiOnlyDescription' => 'ะŸั€ะตะดะพั‚ะฒั€ะฐั‚ัะฒะฐะน ะธะทั‚ะตะณะปัะฝะธั ะฟั€ะตะท ะผะพะฑะธะปะฝะธ ะดะฐะฝะฝะธ', 'settings.autoRemoveWatchedDownloads' => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฒะฐะน ะธะทะณะปะตะดะฐะฝะธั‚ะต ะธะทั‚ะตะณะปัะฝะธั', @@ -2554,10 +2564,15 @@ extension on TranslationsBg { 'accessibility.hexColor' => 'ะจะตัั‚ะฝะฐะดะตัะตั‚ะธั‡ะตะฝ ั†ะฒัั‚', 'accessibility.expandText' => 'ะ ะฐะทะณัŠะฝะธ ั‚ะตะบัั‚ะฐ', 'accessibility.collapseText' => 'ะกะฒะธะน ั‚ะตะบัั‚ะฐ', + 'accessibility.alphabetNavigation' => 'ะะฐะฒะธะณะฐั†ะธั ะฟะพ ะฐะทะฑัƒะบะฐั‚ะฐ', + 'accessibility.alphabetScrollHint' => 'ะŸะปัŠะทะฝะตั‚ะต ะฝะฐะณะพั€ะต ะธะปะธ ะฝะฐะดะพะปัƒ, ะทะฐ ะดะฐ ะฟั€ะตะผะธะฝะตั‚ะต ะฟะพ ะฑัƒะบะฒะฐ', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'ะ ะตะด ${row} ะพั‚ ${rowCount}, ะบะพะปะพะฝะฐ ${column} ะพั‚ ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'ะ ะตะด ${row} ะพั‚ ${rowCount}', 'tooltips.shufflePlay' => 'ะ ะฐะทะฑัŠั€ะบะฐะฝะพ ะฒัŠะทะฟั€ะพะธะทะฒะตะถะดะฐะฝะต', 'tooltips.playTrailer' => 'ะŸัƒัะฝะธ ั‚ั€ะตะนะปัŠั€', 'tooltips.markAsWatched' => 'ะœะฐั€ะบะธั€ะฐะน ะบะฐั‚ะพ ะณะปะตะดะฐะฝะพ', 'tooltips.markAsUnwatched' => 'ะœะฐั€ะบะธั€ะฐะน ะบะฐั‚ะพ ะฝะตะณะปะตะดะฐะฝะพ', + 'audioTracks.track' => ({required Object n}) => 'ะัƒะดะธะพ ะฟัŠั‚ะตั‡ะบะฐ ${n}', 'videoControls.audioLabel' => 'ะัƒะดะธะพ', 'videoControls.subtitlesLabel' => 'ะกัƒะฑั‚ะธั‚ั€ะธ', 'videoControls.resetToZero' => 'ะัƒะปะธั€ะฐะน ะดะพ 0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsBg { 'messages.markedAsUnwatched' => 'ะœะฐั€ะบะธั€ะฐะฝะพ ะบะฐั‚ะพ ะฝะตะณะปะตะดะฐะฝะพ', 'messages.markedAsWatchedOffline' => 'ะœะฐั€ะบะธั€ะฐะฝะพ ะบะฐั‚ะพ ะณะปะตะดะฐะฝะพ (ั‰ะต ัะต ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐ, ะบะพะณะฐั‚ะพ ัั‚ะต ะพะฝะปะฐะนะฝ)', 'messages.markedAsUnwatchedOffline' => 'ะœะฐั€ะบะธั€ะฐะฝะพ ะบะฐั‚ะพ ะฝะตะณะปะตะดะฐะฝะพ (ั‰ะต ัะต ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐ, ะบะพะณะฐั‚ะพ ัั‚ะต ะพะฝะปะฐะนะฝ)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฝะฐั‚ะพ: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('bg'))(n, one: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฝะฐั‚ะพ ${n} ะณะปะตะดะฐะฝะพ ะธะทั‚ะตะณะปัะฝะต', other: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฝะฐั‚ะธ ${n} ะณะปะตะดะฐะฝะธ ะธะทั‚ะตะณะปัะฝะธั', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฝะฐั‚ะพ: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('bg'))(n, one: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฝะฐั‚ะพ ${n} ะณะปะตะดะฐะฝะพ ะธะทั‚ะตะณะปัะฝะต', other: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะตะผะฐั…ะฝะฐั‚ะธ ${n} ะณะปะตะดะฐะฝะธ ะธะทั‚ะตะณะปัะฝะธั', ), 'messages.removedFromContinueWatching' => 'ะŸั€ะตะผะฐั…ะฝะฐั‚ะพ ะพั‚ ะฟั€ะพะดัŠะปะถะฐะฒะฐะฝะต ะฝะฐ ะณะปะตะดะฐะฝะตั‚ะพ', 'messages.errorLoading' => ({required Object error}) => 'ะ“ั€ะตัˆะบะฐ: ${error}', 'messages.streamInterrupted' => 'ะŸะพั‚ะพะบัŠั‚ ะฟั€ะตะบัŠัะฝะฐ. ะะฐั‚ะธัะฝะตั‚ะต โ€žะŸัƒัะฝะธโ€œ ะธะปะธ ะฟั€ะตะฒัŠั€ั‚ะตั‚ะต, ะทะฐ ะดะฐ ะพะฟะธั‚ะฐั‚ะต ะพั‚ะฝะพะฒะพ.', 'messages.liveStreamInterrupted' => 'ะŸะพั‚ะพะบัŠั‚ ะฝะฐ ะถะธะฒะพ ะฟั€ะตะบัŠัะฝะฐ. ะะฐั‚ะธัะฝะตั‚ะต โ€žะŸัƒัะฝะธโ€œ, ะทะฐ ะดะฐ ะพะฟะธั‚ะฐั‚ะต ะพั‚ะฝะพะฒะพ.', 'messages.fileInfoNotAvailable' => 'ะ˜ะฝั„ะพั€ะผะฐั†ะธัั‚ะฐ ะทะฐ ั„ะฐะนะปะฐ ะฝะต ะต ะฝะฐะปะธั‡ะฝะฐ', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'ะ“ั€ะตัˆะบะฐ ะฟั€ะธ ะทะฐั€ะตะถะดะฐะฝะต ะฝะฐ ะธะฝั„ะพั€ะผะฐั†ะธั ะทะฐ ั„ะฐะนะปะฐ: ${error}', 'messages.errorLoadingSeries' => 'ะ“ั€ะตัˆะบะฐ ะฟั€ะธ ะทะฐั€ะตะถะดะฐะฝะต ะฝะฐ ัะตั€ะธะฐะปะฐ', 'messages.musicNotSupported' => 'ะ’ัŠะทะฟั€ะพะธะทะฒะตะถะดะฐะฝะตั‚ะพ ะฝะฐ ะผัƒะทะธะบะฐ ะฒัะต ะพั‰ะต ะฝะต ัะต ะฟะพะดะดัŠั€ะถะฐ', @@ -2772,6 +2787,7 @@ extension on TranslationsBg { 'profiles.borrowExplain' => 'ะ˜ะทะฟะพะปะทะฒะฐะน ะฒั€ัŠะทะบะฐ ะพั‚ ะดั€ัƒะณ ะฟั€ะพั„ะธะป. PIN-ะทะฐั‰ะธั‚ะตะฝะธั‚ะต ะฟั€ะพั„ะธะปะธ ะธะทะธัะบะฒะฐั‚ PIN.', 'profiles.borrowEmpty' => 'ะ’ัะต ะพั‰ะต ะฝัะผะฐ ะบะฐะบะฒะพ ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐ.', 'profiles.borrowEmptySubtitle' => 'ะŸัŠั€ะฒะพ ัะฒัŠั€ะถะตั‚ะต Plex ะธะปะธ Jellyfin ะบัŠะผ ะดั€ัƒะณ ะฟั€ะพั„ะธะป.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'ะžั‚ ${displayName}', 'profiles.borrowConnectionBorrowed' => 'ะ’ั€ัŠะทะบะฐั‚ะฐ ะต ะธะทะฟะพะปะทะฒะฐะฝะฐ.', 'profiles.borrowFailed' => 'ะะตัƒัะฟะตัˆะฝะพ ะธะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ ะฒั€ัŠะทะบะฐ.', @@ -2997,7 +3013,7 @@ extension on TranslationsBg { 'liveTv.favorites' => 'ะ›ัŽะฑะธะผะธ', 'liveTv.reorderFavorites' => 'ะŸั€ะตะฝะฐั€ะตะดะธ ะปัŽะฑะธะผะธั‚ะต', 'liveTv.favoritesLoadFailed' => 'ะ›ัŽะฑะธะผะธั‚ะต ะฝะต ะผะพะถะฐั…ะฐ ะดะฐ ัะต ะทะฐั€ะตะดัั‚. ะŸั€ะพะฒะตั€ะตั‚ะต ะฒั€ัŠะทะบะฐั‚ะฐ ัะธ ะธ ะพะฟะธั‚ะฐะนั‚ะต ะพั‚ะฝะพะฒะพ.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'ะŸั€ะธััŠะตะดะธะฝะธ ัะต ะบัŠะผ ั‚ะตะบัƒั‰ะฐ ัะตัะธั', 'liveTv.watchFromStart' => ({required Object minutes}) => 'ะ“ะปะตะดะฐะน ะพั‚ ะฝะฐั‡ะฐะปะพั‚ะพ (ะฟั€ะตะดะธ ${minutes} ะผะธะฝ)', 'liveTv.watchLive' => 'ะ“ะปะตะดะฐะน ะฝะฐ ะถะธะฒะพ', @@ -3157,11 +3173,11 @@ extension on TranslationsBg { 'watchTogether.participantLeft' => ({required Object name}) => '${name} ะฝะฐะฟัƒัะฝะฐ', 'watchTogether.participantPaused' => ({required Object name}) => '${name} ะฟะพัั‚ะฐะฒะธ ะฝะฐ ะฟะฐัƒะทะฐ', 'watchTogether.participantResumed' => ({required Object name}) => '${name} ะฟั€ะพะดัŠะปะถะธ', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} ะฟั€ะตะฒัŠั€ั‚ั', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} ะฑัƒั„ะตั€ะธั€ะฐ', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} ะต ั ะฟะพ-ัั‚ะฐั€ะฐ ะฒะตั€ัะธั ะฝะฐ ะฟั€ะธะปะพะถะตะฝะธะตั‚ะพ โ€” ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐะฝะตั‚ะพ ะฝะต ะต ะฝะฐะปะธั‡ะฝะพ', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'ะŸั€ะพะดัŠะปะถะฐะฒะฐะฝะต ะฑะตะท ${name}', 'watchTogether.waitingForParticipants' => 'ะ˜ะทั‡ะฐะบะฒะฐะฝะต ะดั€ัƒะณะธั‚ะต ะดะฐ ะทะฐั€ะตะดัั‚...', 'watchTogether.waitingForName' => ({required Object name}) => 'ะ˜ะทั‡ะฐะบะฒะฐะฝะต ะฝะฐ ${name}...', diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index 811d3063..34560dc1 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -52,6 +52,7 @@ class TranslationsDa extends Translations with BaseTranslations 'Venter pรฅ godkendelseโ€ฆ'; @override String get quickConnectCancel => 'Annullรฉr'; @override String get quickConnectExpired => 'Quick Connect er udlรธbet. Prรธv igen.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsDa extends TranslationsSettingsEn { @override String get libraryDensity => 'Bibliotekstรฆthed'; @override String get compact => 'Kompakt'; @override String get comfortable => 'Komfortabel'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Visningstilstand'; @override String get gridView => 'Gitter'; @override String get listView => 'Liste'; @@ -311,7 +310,7 @@ class _TranslationsSettingsDa extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Watch Together-relay'; @override String get watchTogetherRelayDescription => 'Angiv en brugerdefineret relay. Alle skal bruge samme server.'; @override String get watchTogetherRelayHint => 'https://min-relay.eksempel.dk'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Fejlrapportering'; @override String get crashReportingDescription => 'Send fejlrapporter for at hjรฆlpe med at forbedre appen'; @override String get debugLogging => 'Fejlfindingslogning'; @@ -328,12 +327,10 @@ class _TranslationsSettingsDa extends TranslationsSettingsEn { @override String get exportSettings => 'Eksportรฉr indstillinger'; @override String get exportSettingsDescription => 'Gem dine prรฆferencer i en fil'; @override String get exportSettingsSuccess => 'Indstillinger eksporteret'; - @override String get exportSettingsFailed => 'Kunne ikke eksportere indstillinger'; @override String get importSettings => 'Importรฉr indstillinger'; @override String get importSettingsDescription => 'Gendan prรฆferencer fra en fil'; @override String get importSettingsConfirm => 'Dette vil erstatte dine nuvรฆrende indstillinger. Fortsรฆt?'; @override String get importSettingsSuccess => 'Indstillinger importeret'; - @override String get importSettingsFailed => 'Kunne ikke importere indstillinger'; @override String get importSettingsInvalidFile => 'Denne fil er ikke en gyldig Plezy-indstillingseksport'; @override String get importSettingsNoUser => 'Log ind fรธr import af indstillinger'; @override String get shortcutsReset => 'Genveje nulstillet til standard'; @@ -348,6 +345,7 @@ class _TranslationsSettingsDa extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Varighed skal vรฆre mellem ${min} og ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'Genvej allerede tildelt til ${action}'; @override String shortcutUpdated({required Object action}) => 'Genvej opdateret for ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Auto-spring'; @override String get autoSkipIntro => 'Auto-spring intro'; @override String get autoSkipIntroDescription => 'Spring automatisk intromarkรธrer over efter fรฅ sekunder'; @@ -373,7 +371,7 @@ class _TranslationsSettingsDa extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Downloadplacering รฆndret'; @override String get downloadLocationReset => 'Downloadplacering nulstillet'; @override String get downloadLocationInvalid => 'Valgt mappe er ikke skrivbar'; - @override String get downloadLocationSelectError => 'Kunne ikke vรฆlge mappe'; + @override String get downloadLocationPickerUnavailable => 'Mappevalg er ikke tilgรฆngeligt pรฅ denne enhed'; @override String get downloadOnWifiOnly => 'Download kun pรฅ WiFi'; @override String get downloadOnWifiOnlyDescription => 'Forhindre downloads pรฅ mobildata'; @override String get autoRemoveWatchedDownloads => 'Fjern sete downloads automatisk'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityDa extends TranslationsAccessibilityEn { @override String get hexColor => 'Hexfarve'; @override String get expandText => 'Udvid tekst'; @override String get collapseText => 'Fold tekst sammen'; + @override String get alphabetNavigation => 'Alfabetnavigation'; + @override String get alphabetScrollHint => 'Stryg op eller ned for at flytte รฉt bogstav'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Rรฆkke ${row} af ${rowCount}, kolonne ${column} af ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Rรฆkke ${row} af ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsDa extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Markรฉr som uset'; } +// Path: audioTracks +class _TranslationsAudioTracksDa extends TranslationsAudioTracksEn { + _TranslationsAudioTracksDa._(TranslationsDa root) : this._root = root, super.internal(root); + + final TranslationsDa _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Lydspor ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsDa extends TranslationsVideoControlsEn { _TranslationsVideoControlsDa._(TranslationsDa root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesDa extends TranslationsMessagesEn { @override String get streamInterrupted => 'Streamen blev afbrudt. Tryk pรฅ afspil, eller spol for at prรธve igen.'; @override String get liveStreamInterrupted => 'Livestreamen blev afbrudt. Tryk pรฅ afspil for at prรธve igen.'; @override String get fileInfoNotAvailable => 'Filinfo ikke tilgรฆngelig'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Fejl ved indlรฆsning af filinfo: ${error}'; @override String get errorLoadingSeries => 'Fejl ved indlรฆsning af serie'; @override String get musicNotSupported => 'Musikafspilning understรธttes endnu ikke'; @@ -867,6 +879,7 @@ class _TranslationsProfilesDa extends TranslationsProfilesEn { @override String get borrowExplain => 'Lรฅn en anden profils forbindelse. PIN-beskyttede profiler krรฆver en PIN.'; @override String get borrowEmpty => 'Intet at lรฅne endnu.'; @override String get borrowEmptySubtitle => 'Forbind Plex eller Jellyfin til en anden profil fรธrst.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'Fra ${displayName}'; @override String get borrowConnectionBorrowed => 'Forbindelse lรฅnt.'; @override String get borrowFailed => 'Kunne ikke lรฅne forbindelse.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvDa extends TranslationsLiveTvEn { @override String get favorites => 'Favoritter'; @override String get reorderFavorites => 'Omarranger favoritter'; @override String get favoritesLoadFailed => 'Favoritter kunne ikke indlรฆses. Kontrollรฉr forbindelsen, og prรธv igen.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Deltag i igangvรฆrende session'; @override String watchFromStart({required Object minutes}) => 'Se fra start (${minutes} min siden)'; @override String get watchLive => 'Se live'; @@ -2149,7 +2162,7 @@ extension on TranslationsDa { 'auth.quickConnectWaiting' => 'Venter pรฅ godkendelseโ€ฆ', 'auth.quickConnectCancel' => 'Annullรฉr', 'auth.quickConnectExpired' => 'Quick Connect er udlรธbet. Prรธv igen.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Annuller', 'common.save' => 'Gem', 'common.close' => 'Luk', @@ -2233,8 +2246,6 @@ extension on TranslationsDa { 'settings.libraryDensity' => 'Bibliotekstรฆthed', 'settings.compact' => 'Kompakt', 'settings.comfortable' => 'Komfortabel', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Visningstilstand', 'settings.gridView' => 'Gitter', 'settings.listView' => 'Liste', @@ -2304,7 +2315,7 @@ extension on TranslationsDa { 'settings.watchTogetherRelay' => 'Watch Together-relay', 'settings.watchTogetherRelayDescription' => 'Angiv en brugerdefineret relay. Alle skal bruge samme server.', 'settings.watchTogetherRelayHint' => 'https://min-relay.eksempel.dk', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Fejlrapportering', 'settings.crashReportingDescription' => 'Send fejlrapporter for at hjรฆlpe med at forbedre appen', 'settings.debugLogging' => 'Fejlfindingslogning', @@ -2321,12 +2332,10 @@ extension on TranslationsDa { 'settings.exportSettings' => 'Eksportรฉr indstillinger', 'settings.exportSettingsDescription' => 'Gem dine prรฆferencer i en fil', 'settings.exportSettingsSuccess' => 'Indstillinger eksporteret', - 'settings.exportSettingsFailed' => 'Kunne ikke eksportere indstillinger', 'settings.importSettings' => 'Importรฉr indstillinger', 'settings.importSettingsDescription' => 'Gendan prรฆferencer fra en fil', 'settings.importSettingsConfirm' => 'Dette vil erstatte dine nuvรฆrende indstillinger. Fortsรฆt?', 'settings.importSettingsSuccess' => 'Indstillinger importeret', - 'settings.importSettingsFailed' => 'Kunne ikke importere indstillinger', 'settings.importSettingsInvalidFile' => 'Denne fil er ikke en gyldig Plezy-indstillingseksport', 'settings.importSettingsNoUser' => 'Log ind fรธr import af indstillinger', 'settings.shortcutsReset' => 'Genveje nulstillet til standard', @@ -2341,6 +2350,7 @@ extension on TranslationsDa { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'Varighed skal vรฆre mellem ${min} og ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Genvej allerede tildelt til ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Genvej opdateret for ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Auto-spring', 'settings.autoSkipIntro' => 'Auto-spring intro', 'settings.autoSkipIntroDescription' => 'Spring automatisk intromarkรธrer over efter fรฅ sekunder', @@ -2366,7 +2376,7 @@ extension on TranslationsDa { 'settings.downloadLocationChanged' => 'Downloadplacering รฆndret', 'settings.downloadLocationReset' => 'Downloadplacering nulstillet', 'settings.downloadLocationInvalid' => 'Valgt mappe er ikke skrivbar', - 'settings.downloadLocationSelectError' => 'Kunne ikke vรฆlge mappe', + 'settings.downloadLocationPickerUnavailable' => 'Mappevalg er ikke tilgรฆngeligt pรฅ denne enhed', 'settings.downloadOnWifiOnly' => 'Download kun pรฅ WiFi', 'settings.downloadOnWifiOnlyDescription' => 'Forhindre downloads pรฅ mobildata', 'settings.autoRemoveWatchedDownloads' => 'Fjern sete downloads automatisk', @@ -2554,10 +2564,15 @@ extension on TranslationsDa { 'accessibility.hexColor' => 'Hexfarve', 'accessibility.expandText' => 'Udvid tekst', 'accessibility.collapseText' => 'Fold tekst sammen', + 'accessibility.alphabetNavigation' => 'Alfabetnavigation', + 'accessibility.alphabetScrollHint' => 'Stryg op eller ned for at flytte รฉt bogstav', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Rรฆkke ${row} af ${rowCount}, kolonne ${column} af ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Rรฆkke ${row} af ${rowCount}', 'tooltips.shufflePlay' => 'Afspil tilfรฆldigt', 'tooltips.playTrailer' => 'Afspil trailer', 'tooltips.markAsWatched' => 'Markรฉr som set', 'tooltips.markAsUnwatched' => 'Markรฉr som uset', + 'audioTracks.track' => ({required Object n}) => 'Lydspor ${n}', 'videoControls.audioLabel' => 'Lyd', 'videoControls.subtitlesLabel' => 'Undertekster', 'videoControls.resetToZero' => 'Nulstil til 0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsDa { 'messages.markedAsUnwatched' => 'Markeret som uset', 'messages.markedAsWatchedOffline' => 'Markeret som set (synkroniseres online)', 'messages.markedAsUnwatchedOffline' => 'Markeret som uset (synkroniseres online)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatisk fjernet: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('da'))(n, one: 'Fjernede automatisk ${n} set download', other: 'Fjernede automatisk ${n} sete downloads', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatisk fjernet: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('da'))(n, one: 'Fjernede automatisk ${n} set download', other: 'Fjernede automatisk ${n} sete downloads', ), 'messages.removedFromContinueWatching' => 'Fjernet fra Fortsรฆt med at se', 'messages.errorLoading' => ({required Object error}) => 'Fejl: ${error}', 'messages.streamInterrupted' => 'Streamen blev afbrudt. Tryk pรฅ afspil, eller spol for at prรธve igen.', 'messages.liveStreamInterrupted' => 'Livestreamen blev afbrudt. Tryk pรฅ afspil for at prรธve igen.', 'messages.fileInfoNotAvailable' => 'Filinfo ikke tilgรฆngelig', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fejl ved indlรฆsning af filinfo: ${error}', 'messages.errorLoadingSeries' => 'Fejl ved indlรฆsning af serie', 'messages.musicNotSupported' => 'Musikafspilning understรธttes endnu ikke', @@ -2772,6 +2787,7 @@ extension on TranslationsDa { 'profiles.borrowExplain' => 'Lรฅn en anden profils forbindelse. PIN-beskyttede profiler krรฆver en PIN.', 'profiles.borrowEmpty' => 'Intet at lรฅne endnu.', 'profiles.borrowEmptySubtitle' => 'Forbind Plex eller Jellyfin til en anden profil fรธrst.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'Fra ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Forbindelse lรฅnt.', 'profiles.borrowFailed' => 'Kunne ikke lรฅne forbindelse.', @@ -2997,7 +3013,7 @@ extension on TranslationsDa { 'liveTv.favorites' => 'Favoritter', 'liveTv.reorderFavorites' => 'Omarranger favoritter', 'liveTv.favoritesLoadFailed' => 'Favoritter kunne ikke indlรฆses. Kontrollรฉr forbindelsen, og prรธv igen.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Deltag i igangvรฆrende session', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Se fra start (${minutes} min siden)', 'liveTv.watchLive' => 'Se live', @@ -3157,11 +3173,11 @@ extension on TranslationsDa { 'watchTogether.participantLeft' => ({required Object name}) => '${name} forlod', 'watchTogether.participantPaused' => ({required Object name}) => '${name} satte pรฅ pause', 'watchTogether.participantResumed' => ({required Object name}) => '${name} genoptog', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} spoled', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} bufferer', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} bruger en รฆldre appversion โ€” synkronisering er ikke tilgรฆngelig', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Fortsรฆtter uden ${name}', 'watchTogether.waitingForParticipants' => 'Venter pรฅ at andre indlรฆser...', 'watchTogether.waitingForName' => ({required Object name}) => 'Venter pรฅ ${name}...', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 0672887d..9f75d61d 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -52,6 +52,7 @@ class TranslationsDe extends Translations with BaseTranslations 'Warte auf Bestรคtigungโ€ฆ'; @override String get quickConnectCancel => 'Abbrechen'; @override String get quickConnectExpired => 'Quick Connect ist abgelaufen. Versuche es erneut.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -311,7 +312,7 @@ class _TranslationsSettingsDe extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Gemeinsam Schauen Relay'; @override String get watchTogetherRelayDescription => 'Eigenes Relay festlegen. Alle mรผssen denselben Server verwenden.'; @override String get watchTogetherRelayHint => 'https://mein-relay.beispiel.de'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Absturzberichte'; @override String get crashReportingDescription => 'Absturzberichte senden, um die App zu verbessern'; @override String get debugLogging => 'Debug-Protokollierung'; @@ -328,12 +329,10 @@ class _TranslationsSettingsDe extends TranslationsSettingsEn { @override String get exportSettings => 'Einstellungen exportieren'; @override String get exportSettingsDescription => 'Speichere deine Einstellungen in einer Datei'; @override String get exportSettingsSuccess => 'Einstellungen exportiert'; - @override String get exportSettingsFailed => 'Einstellungen konnten nicht exportiert werden'; @override String get importSettings => 'Einstellungen importieren'; @override String get importSettingsDescription => 'Einstellungen aus einer Datei wiederherstellen'; @override String get importSettingsConfirm => 'Dies ersetzt deine aktuellen Einstellungen. Fortfahren?'; @override String get importSettingsSuccess => 'Einstellungen importiert'; - @override String get importSettingsFailed => 'Einstellungen konnten nicht importiert werden'; @override String get importSettingsInvalidFile => 'Diese Datei ist kein gรผltiger Plezy-Einstellungsexport'; @override String get importSettingsNoUser => 'Vor dem Import bitte anmelden'; @override String get shortcutsReset => 'Tastenkรผrzel auf Standard zurรผckgesetzt'; @@ -348,6 +347,7 @@ class _TranslationsSettingsDe extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Dauer muss zwischen ${min} und ${max} ${unit} liegen'; @override String shortcutAlreadyAssigned({required Object action}) => 'Tastenkรผrzel bereits zugewiesen an ${action}'; @override String shortcutUpdated({required Object action}) => 'Tastenkรผrzel aktualisiert fรผr ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Automatisches รœberspringen'; @override String get autoSkipIntro => 'Intro automatisch รผberspringen'; @override String get autoSkipIntroDescription => 'Intro-Marker nach wenigen Sekunden automatisch รผberspringen'; @@ -373,7 +373,7 @@ class _TranslationsSettingsDe extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Download-Speicherort geรคndert'; @override String get downloadLocationReset => 'Download-Speicherort auf Standard zurรผckgesetzt'; @override String get downloadLocationInvalid => 'Ausgewรคhlter Ordner ist nicht beschreibbar'; - @override String get downloadLocationSelectError => 'Ordnerauswahl fehlgeschlagen'; + @override String get downloadLocationPickerUnavailable => 'Die Ordnerauswahl ist auf diesem Gerรคt nicht verfรผgbar'; @override String get downloadOnWifiOnly => 'Nur รผber WLAN herunterladen'; @override String get downloadOnWifiOnlyDescription => 'Downloads รผber mobile Daten verhindern'; @override String get autoRemoveWatchedDownloads => 'Gesehene Downloads automatisch entfernen'; @@ -591,6 +591,10 @@ class _TranslationsAccessibilityDe extends TranslationsAccessibilityEn { @override String get hexColor => 'Hex-Farbe'; @override String get expandText => 'Text ausklappen'; @override String get collapseText => 'Text einklappen'; + @override String get alphabetNavigation => 'Alphabetische Navigation'; + @override String get alphabetScrollHint => 'Nach oben oder unten wischen, um einen Buchstaben weiterzugehen'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Zeile ${row} von ${rowCount}, Spalte ${column} von ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Zeile ${row} von ${rowCount}'; } // Path: tooltips @@ -606,6 +610,16 @@ class _TranslationsTooltipsDe extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Als ungesehen markieren'; } +// Path: audioTracks +class _TranslationsAudioTracksDe extends TranslationsAudioTracksEn { + _TranslationsAudioTracksDe._(TranslationsDe root) : this._root = root, super.internal(root); + + final TranslationsDe _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Audiospur ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsDe extends TranslationsVideoControlsEn { _TranslationsVideoControlsDe._(TranslationsDe root) : this._root = root, super.internal(root); @@ -712,11 +726,11 @@ class _TranslationsMessagesDe extends TranslationsMessagesEn { @override String get streamInterrupted => 'Der Stream wurde unterbrochen. Drรผcke auf Wiedergabe oder spule, um es erneut zu versuchen.'; @override String get liveStreamInterrupted => 'Der Livestream wurde unterbrochen. Drรผcke auf Wiedergabe, um es erneut zu versuchen.'; @override String get fileInfoNotAvailable => 'Dateiinfo nicht verfรผgbar'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Fehler beim Laden der Dateiinfo: ${error}'; @override String get errorLoadingSeries => 'Fehler beim Laden der Serie'; @override String get musicNotSupported => 'Musikwiedergabe wird noch nicht unterstรผtzt'; @@ -867,6 +881,7 @@ class _TranslationsProfilesDe extends TranslationsProfilesEn { @override String get borrowExplain => 'Verbindung eines anderen Profils leihen. PIN-geschรผtzte Profile erfordern eine PIN.'; @override String get borrowEmpty => 'Noch nichts zum Ausleihen.'; @override String get borrowEmptySubtitle => 'Verbinde zuerst Plex oder Jellyfin mit einem anderen Profil.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'Von ${displayName}'; @override String get borrowConnectionBorrowed => 'Verbindung ausgeliehen.'; @override String get borrowFailed => 'Verbindung konnte nicht ausgeliehen werden.'; @@ -1150,7 +1165,7 @@ class _TranslationsLiveTvDe extends TranslationsLiveTvEn { @override String get favorites => 'Favoriten'; @override String get reorderFavorites => 'Favoriten sortieren'; @override String get favoritesLoadFailed => 'Favoriten konnten nicht geladen werden. รœberprรผfe deine Verbindung und versuche es erneut.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Laufender Sitzung beitreten'; @override String watchFromStart({required Object minutes}) => 'Von Anfang an ansehen (vor ${minutes} Min.)'; @override String get watchLive => 'Live ansehen'; @@ -2149,7 +2164,7 @@ extension on TranslationsDe { 'auth.quickConnectWaiting' => 'Warte auf Bestรคtigungโ€ฆ', 'auth.quickConnectCancel' => 'Abbrechen', 'auth.quickConnectExpired' => 'Quick Connect ist abgelaufen. Versuche es erneut.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Abbrechen', 'common.save' => 'Speichern', 'common.close' => 'SchlieรŸen', @@ -2304,7 +2319,7 @@ extension on TranslationsDe { 'settings.watchTogetherRelay' => 'Gemeinsam Schauen Relay', 'settings.watchTogetherRelayDescription' => 'Eigenes Relay festlegen. Alle mรผssen denselben Server verwenden.', 'settings.watchTogetherRelayHint' => 'https://mein-relay.beispiel.de', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Absturzberichte', 'settings.crashReportingDescription' => 'Absturzberichte senden, um die App zu verbessern', 'settings.debugLogging' => 'Debug-Protokollierung', @@ -2321,12 +2336,10 @@ extension on TranslationsDe { 'settings.exportSettings' => 'Einstellungen exportieren', 'settings.exportSettingsDescription' => 'Speichere deine Einstellungen in einer Datei', 'settings.exportSettingsSuccess' => 'Einstellungen exportiert', - 'settings.exportSettingsFailed' => 'Einstellungen konnten nicht exportiert werden', 'settings.importSettings' => 'Einstellungen importieren', 'settings.importSettingsDescription' => 'Einstellungen aus einer Datei wiederherstellen', 'settings.importSettingsConfirm' => 'Dies ersetzt deine aktuellen Einstellungen. Fortfahren?', 'settings.importSettingsSuccess' => 'Einstellungen importiert', - 'settings.importSettingsFailed' => 'Einstellungen konnten nicht importiert werden', 'settings.importSettingsInvalidFile' => 'Diese Datei ist kein gรผltiger Plezy-Einstellungsexport', 'settings.importSettingsNoUser' => 'Vor dem Import bitte anmelden', 'settings.shortcutsReset' => 'Tastenkรผrzel auf Standard zurรผckgesetzt', @@ -2341,6 +2354,7 @@ extension on TranslationsDe { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'Dauer muss zwischen ${min} und ${max} ${unit} liegen', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Tastenkรผrzel bereits zugewiesen an ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Tastenkรผrzel aktualisiert fรผr ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Automatisches รœberspringen', 'settings.autoSkipIntro' => 'Intro automatisch รผberspringen', 'settings.autoSkipIntroDescription' => 'Intro-Marker nach wenigen Sekunden automatisch รผberspringen', @@ -2366,7 +2380,7 @@ extension on TranslationsDe { 'settings.downloadLocationChanged' => 'Download-Speicherort geรคndert', 'settings.downloadLocationReset' => 'Download-Speicherort auf Standard zurรผckgesetzt', 'settings.downloadLocationInvalid' => 'Ausgewรคhlter Ordner ist nicht beschreibbar', - 'settings.downloadLocationSelectError' => 'Ordnerauswahl fehlgeschlagen', + 'settings.downloadLocationPickerUnavailable' => 'Die Ordnerauswahl ist auf diesem Gerรคt nicht verfรผgbar', 'settings.downloadOnWifiOnly' => 'Nur รผber WLAN herunterladen', 'settings.downloadOnWifiOnlyDescription' => 'Downloads รผber mobile Daten verhindern', 'settings.autoRemoveWatchedDownloads' => 'Gesehene Downloads automatisch entfernen', @@ -2554,10 +2568,15 @@ extension on TranslationsDe { 'accessibility.hexColor' => 'Hex-Farbe', 'accessibility.expandText' => 'Text ausklappen', 'accessibility.collapseText' => 'Text einklappen', + 'accessibility.alphabetNavigation' => 'Alphabetische Navigation', + 'accessibility.alphabetScrollHint' => 'Nach oben oder unten wischen, um einen Buchstaben weiterzugehen', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Zeile ${row} von ${rowCount}, Spalte ${column} von ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Zeile ${row} von ${rowCount}', 'tooltips.shufflePlay' => 'Zufallswiedergabe', 'tooltips.playTrailer' => 'Trailer abspielen', 'tooltips.markAsWatched' => 'Als gesehen markieren', 'tooltips.markAsUnwatched' => 'Als ungesehen markieren', + 'audioTracks.track' => ({required Object n}) => 'Audiospur ${n}', 'videoControls.audioLabel' => 'Audio', 'videoControls.subtitlesLabel' => 'Untertitel', 'videoControls.resetToZero' => 'Auf 0 ms zurรผcksetzen', @@ -2642,22 +2661,22 @@ extension on TranslationsDe { 'videoControls.searchLanguages' => 'Sprachen suchen...', 'messages.markedAsWatched' => 'Als gesehen markiert', 'messages.markedAsUnwatched' => 'Als ungesehen markiert', + _ => null, + } ?? switch (path) { 'messages.markedAsWatchedOffline' => 'Als gesehen markiert (wird synchronisiert, wenn online)', 'messages.markedAsUnwatchedOffline' => 'Als ungesehen markiert (wird synchronisiert, wenn online)', 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatisch entfernt: ${title}', 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('de'))(n, one: 'Automatisch entfernt: ${n} angesehener Download', other: 'Automatisch entfernt: ${n} angesehene Downloads', ), - _ => null, - } ?? switch (path) { 'messages.removedFromContinueWatching' => 'Aus โ€šWeiterschauen\' entfernt', 'messages.errorLoading' => ({required Object error}) => 'Fehler: ${error}', 'messages.streamInterrupted' => 'Der Stream wurde unterbrochen. Drรผcke auf Wiedergabe oder spule, um es erneut zu versuchen.', 'messages.liveStreamInterrupted' => 'Der Livestream wurde unterbrochen. Drรผcke auf Wiedergabe, um es erneut zu versuchen.', 'messages.fileInfoNotAvailable' => 'Dateiinfo nicht verfรผgbar', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fehler beim Laden der Dateiinfo: ${error}', 'messages.errorLoadingSeries' => 'Fehler beim Laden der Serie', 'messages.musicNotSupported' => 'Musikwiedergabe wird noch nicht unterstรผtzt', @@ -2772,6 +2791,7 @@ extension on TranslationsDe { 'profiles.borrowExplain' => 'Verbindung eines anderen Profils leihen. PIN-geschรผtzte Profile erfordern eine PIN.', 'profiles.borrowEmpty' => 'Noch nichts zum Ausleihen.', 'profiles.borrowEmptySubtitle' => 'Verbinde zuerst Plex oder Jellyfin mit einem anderen Profil.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'Von ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Verbindung ausgeliehen.', 'profiles.borrowFailed' => 'Verbindung konnte nicht ausgeliehen werden.', @@ -2997,7 +3017,7 @@ extension on TranslationsDe { 'liveTv.favorites' => 'Favoriten', 'liveTv.reorderFavorites' => 'Favoriten sortieren', 'liveTv.favoritesLoadFailed' => 'Favoriten konnten nicht geladen werden. รœberprรผfe deine Verbindung und versuche es erneut.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Laufender Sitzung beitreten', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Von Anfang an ansehen (vor ${minutes} Min.)', 'liveTv.watchLive' => 'Live ansehen', @@ -3155,13 +3175,13 @@ extension on TranslationsDe { 'watchTogether.failedToOpenCurrentPlayback' => 'Aktuelle Wiedergabe konnte nicht geรถffnet werden', 'watchTogether.participantJoined' => ({required Object name}) => '${name} ist beigetreten', 'watchTogether.participantLeft' => ({required Object name}) => '${name} hat die Sitzung verlassen', + _ => null, + } ?? switch (path) { 'watchTogether.participantPaused' => ({required Object name}) => '${name} hat pausiert', 'watchTogether.participantResumed' => ({required Object name}) => '${name} hat fortgesetzt', 'watchTogether.participantSeeked' => ({required Object name}) => '${name} hat gespult', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} puffert', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} verwendet eine รคltere Appversion โ€” Synchronisierung nicht verfรผgbar', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Fortfahren ohne ${name}', 'watchTogether.waitingForParticipants' => 'Warte auf andere zum Laden...', 'watchTogether.waitingForName' => ({required Object name}) => 'Warten auf ${name}...', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 68d61429..b88ddcdf 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -53,6 +53,7 @@ class Translations with BaseTranslations { late final TranslationsRateSheetEn rateSheet = TranslationsRateSheetEn.internal(_root); late final TranslationsAccessibilityEn accessibility = TranslationsAccessibilityEn.internal(_root); late final TranslationsTooltipsEn tooltips = TranslationsTooltipsEn.internal(_root); + late final TranslationsAudioTracksEn audioTracks = TranslationsAudioTracksEn.internal(_root); late final TranslationsVideoControlsEn videoControls = TranslationsVideoControlsEn.internal(_root); late final TranslationsMessagesEn messages = TranslationsMessagesEn.internal(_root); late final TranslationsSubtitlingStylingEn subtitlingStyling = TranslationsSubtitlingStylingEn.internal(_root); @@ -662,6 +663,9 @@ class TranslationsSettingsEn { /// en: 'Send crash reports to help improve the app' String get crashReportingDescription => 'Send crash reports to help improve the app'; + /// en: 'After disabling, reports already accepted, queued, or being sent may finish. Enabling again takes effect when Plezy restarts.' + String get crashReportingRestartRequired => 'After disabling, reports already accepted, queued, or being sent may finish. Enabling again takes effect when Plezy restarts.'; + /// en: 'Debug Logging' String get debugLogging => 'Debug Logging'; @@ -704,9 +708,6 @@ class TranslationsSettingsEn { /// en: 'Settings exported' String get exportSettingsSuccess => 'Settings exported'; - /// en: 'Could not export settings' - String get exportSettingsFailed => 'Could not export settings'; - /// en: 'Import Settings' String get importSettings => 'Import Settings'; @@ -719,9 +720,6 @@ class TranslationsSettingsEn { /// en: 'Settings imported' String get importSettingsSuccess => 'Settings imported'; - /// en: 'Could not import settings' - String get importSettingsFailed => 'Could not import settings'; - /// en: 'This file isn't a valid Plezy settings export' String get importSettingsInvalidFile => 'This file isn\'t a valid Plezy settings export'; @@ -764,6 +762,9 @@ class TranslationsSettingsEn { /// en: 'Shortcut updated for ${action}' String shortcutUpdated({required Object action}) => 'Shortcut updated for ${action}'; + /// en: 'Could not save changes. Try again.' + String get saveFailed => 'Could not save changes. Try again.'; + /// en: 'Auto Skip' String get autoSkip => 'Auto Skip'; @@ -839,8 +840,8 @@ class TranslationsSettingsEn { /// en: 'Selected folder is not writable' String get downloadLocationInvalid => 'Selected folder is not writable'; - /// en: 'Failed to select folder' - String get downloadLocationSelectError => 'Failed to select folder'; + /// en: 'Folder selection is not available on this device' + String get downloadLocationPickerUnavailable => 'Folder selection is not available on this device'; /// en: 'Download on WiFi only' String get downloadOnWifiOnly => 'Download on WiFi only'; @@ -1383,6 +1384,18 @@ class TranslationsAccessibilityEn { /// en: 'Collapse text' String get collapseText => 'Collapse text'; + + /// en: 'Alphabet navigation' + String get alphabetNavigation => 'Alphabet navigation'; + + /// en: 'Swipe up or down to move by letter' + String get alphabetScrollHint => 'Swipe up or down to move by letter'; + + /// en: 'Row ${row} of ${rowCount}, column ${column} of ${columnCount}' + String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Row ${row} of ${rowCount}, column ${column} of ${columnCount}'; + + /// en: 'Row ${row} of ${rowCount}' + String rowPosition({required Object row, required Object rowCount}) => 'Row ${row} of ${rowCount}'; } // Path: tooltips @@ -1406,6 +1419,18 @@ class TranslationsTooltipsEn { String get markAsUnwatched => 'Mark as unwatched'; } +// Path: audioTracks +class TranslationsAudioTracksEn { + TranslationsAudioTracksEn.internal(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Audio Track ${n}' + String track({required Object n}) => 'Audio Track ${n}'; +} + // Path: videoControls class TranslationsVideoControlsEn { TranslationsVideoControlsEn.internal(this._root); @@ -1492,6 +1517,12 @@ class TranslationsVideoControlsEn { /// en: 'Pause' String get pauseButton => 'Pause'; + /// en: 'Show playback controls' + String get showPlaybackControls => 'Show playback controls'; + + /// en: 'Hide playback controls' + String get hidePlaybackControls => 'Hide playback controls'; + /// en: 'Seek backward ${seconds} seconds' String seekBackwardButton({required Object seconds}) => 'Seek backward ${seconds} seconds'; @@ -2079,6 +2110,9 @@ class TranslationsProfilesEn { /// en: 'Connect Plex or Jellyfin to another profile first.' String get borrowEmptySubtitle => 'Connect Plex or Jellyfin to another profile first.'; + /// en: 'Available connections could not be loaded. Try again.' + String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; + /// en: 'From ${displayName}' String borrowFromProfile({required Object displayName}) => 'From ${displayName}'; @@ -5186,6 +5220,7 @@ extension on Translations { 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Crash Reporting', 'settings.crashReportingDescription' => 'Send crash reports to help improve the app', + 'settings.crashReportingRestartRequired' => 'After disabling, reports already accepted, queued, or being sent may finish. Enabling again takes effect when Plezy restarts.', 'settings.debugLogging' => 'Debug Logging', 'settings.debugLoggingDescription' => 'Enable detailed logging for troubleshooting', 'settings.viewLogs' => 'View Logs', @@ -5200,12 +5235,10 @@ extension on Translations { 'settings.exportSettings' => 'Export Settings', 'settings.exportSettingsDescription' => 'Save your preferences to a file', 'settings.exportSettingsSuccess' => 'Settings exported', - 'settings.exportSettingsFailed' => 'Could not export settings', 'settings.importSettings' => 'Import Settings', 'settings.importSettingsDescription' => 'Restore preferences from a file', 'settings.importSettingsConfirm' => 'This will replace your current settings. Continue?', 'settings.importSettingsSuccess' => 'Settings imported', - 'settings.importSettingsFailed' => 'Could not import settings', 'settings.importSettingsInvalidFile' => 'This file isn\'t a valid Plezy settings export', 'settings.importSettingsNoUser' => 'Sign in before importing settings', 'settings.shortcutsReset' => 'Shortcuts reset to defaults', @@ -5220,6 +5253,7 @@ extension on Translations { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'Duration must be between ${min} and ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Shortcut already assigned to ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Shortcut updated for ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Auto Skip', 'settings.autoSkipIntro' => 'Auto Skip Intro', 'settings.autoSkipIntroDescription' => 'Automatically skip intro markers after a few seconds', @@ -5245,7 +5279,7 @@ extension on Translations { 'settings.downloadLocationChanged' => 'Download location changed', 'settings.downloadLocationReset' => 'Download location reset to default', 'settings.downloadLocationInvalid' => 'Selected folder is not writable', - 'settings.downloadLocationSelectError' => 'Failed to select folder', + 'settings.downloadLocationPickerUnavailable' => 'Folder selection is not available on this device', 'settings.downloadOnWifiOnly' => 'Download on WiFi only', 'settings.downloadOnWifiOnlyDescription' => 'Prevent downloads when on cellular data', 'settings.autoRemoveWatchedDownloads' => 'Auto-remove watched downloads', @@ -5433,10 +5467,15 @@ extension on Translations { 'accessibility.hexColor' => 'Hex color', 'accessibility.expandText' => 'Expand text', 'accessibility.collapseText' => 'Collapse text', + 'accessibility.alphabetNavigation' => 'Alphabet navigation', + 'accessibility.alphabetScrollHint' => 'Swipe up or down to move by letter', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Row ${row} of ${rowCount}, column ${column} of ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Row ${row} of ${rowCount}', 'tooltips.shufflePlay' => 'Shuffle play', 'tooltips.playTrailer' => 'Play trailer', 'tooltips.markAsWatched' => 'Mark as watched', 'tooltips.markAsUnwatched' => 'Mark as unwatched', + 'audioTracks.track' => ({required Object n}) => 'Audio Track ${n}', 'videoControls.audioLabel' => 'Audio', 'videoControls.subtitlesLabel' => 'Subtitles', 'videoControls.resetToZero' => 'Reset to 0ms', @@ -5463,6 +5502,8 @@ extension on Translations { 'videoControls.playNext' => 'Play Next', 'videoControls.playButton' => 'Play', 'videoControls.pauseButton' => 'Pause', + 'videoControls.showPlaybackControls' => 'Show playback controls', + 'videoControls.hidePlaybackControls' => 'Hide playback controls', 'videoControls.seekBackwardButton' => ({required Object seconds}) => 'Seek backward ${seconds} seconds', 'videoControls.seekForwardButton' => ({required Object seconds}) => 'Seek forward ${seconds} seconds', 'videoControls.previousButton' => 'Previous episode', @@ -5518,6 +5559,8 @@ extension on Translations { 'videoControls.subtitleDownloaded' => 'Subtitle downloaded', 'videoControls.subtitleDownloadedNotApplied' => 'Subtitle downloaded, but it could not be selected', 'videoControls.subtitleDownloadFailed' => 'Failed to download subtitle', + _ => null, + } ?? switch (path) { 'videoControls.searchLanguages' => 'Search languages...', 'messages.markedAsWatched' => 'Marked as watched', @@ -5526,8 +5569,6 @@ extension on Translations { 'messages.markedAsUnwatchedOffline' => 'Marked as unwatched (will sync when online)', 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Auto-removed: ${title}', 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: 'Auto-removed ${n} watched download', other: 'Auto-removed ${n} watched downloads', ), - _ => null, - } ?? switch (path) { 'messages.removedFromContinueWatching' => 'Removed from Continue Watching', 'messages.errorLoading' => ({required Object error}) => 'Error: ${error}', 'messages.streamInterrupted' => 'The stream was interrupted. Press play or seek to retry.', @@ -5652,6 +5693,7 @@ extension on Translations { 'profiles.borrowExplain' => 'Borrow another profile\'s connection. PIN-protected profiles require a PIN.', 'profiles.borrowEmpty' => 'Nothing to borrow yet.', 'profiles.borrowEmptySubtitle' => 'Connect Plex or Jellyfin to another profile first.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'From ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Connection borrowed.', 'profiles.borrowFailed' => 'Failed to borrow connection.', @@ -6032,6 +6074,8 @@ extension on Translations { 'watchTogether.currentPlayback' => 'Current Playback', 'watchTogether.joinCurrentPlayback' => 'Join Current Playback', 'watchTogether.joinCurrentPlaybackDescription' => 'Jump back into what the host is currently watching', + _ => null, + } ?? switch (path) { 'watchTogether.failedToOpenCurrentPlayback' => 'Failed to open current playback', 'watchTogether.participantJoined' => ({required Object name}) => '${name} joined', @@ -6041,8 +6085,6 @@ extension on Translations { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} seeked', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} is buffering', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} is on an older app version โ€” sync unavailable', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Resuming without ${name}', 'watchTogether.waitingForParticipants' => 'Waiting for others to load...', 'watchTogether.waitingForName' => ({required Object name}) => 'Waiting for ${name}...', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 9571b1d4..5601f2ea 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -52,6 +52,7 @@ class TranslationsEs extends Translations with BaseTranslations 'Esperando aprobaciรณnโ€ฆ'; @override String get quickConnectCancel => 'Cancelar'; @override String get quickConnectExpired => 'Quick Connect caducรณ. Intรฉntalo de nuevo.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsEs extends TranslationsSettingsEn { @override String get libraryDensity => 'Densidad de Biblioteca'; @override String get compact => 'Compacto'; @override String get comfortable => 'Cรณmodo'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Modo de Vista'; @override String get gridView => 'Cuadrรญcula'; @override String get listView => 'Lista'; @@ -311,7 +310,7 @@ class _TranslationsSettingsEs extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Relay de Ver Juntos'; @override String get watchTogetherRelayDescription => 'Define un relay personalizado. Todos deben usar el mismo servidor.'; @override String get watchTogetherRelayHint => 'https://mi-relay.ejemplo.com'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Informes de Errores'; @override String get crashReportingDescription => 'Enviar informes de errores para mejorar la aplicaciรณn'; @override String get debugLogging => 'Registro de Depuraciรณn'; @@ -328,12 +327,10 @@ class _TranslationsSettingsEs extends TranslationsSettingsEn { @override String get exportSettings => 'Exportar Configuraciรณn'; @override String get exportSettingsDescription => 'Guardar tus preferencias en un archivo'; @override String get exportSettingsSuccess => 'Configuraciรณn exportada'; - @override String get exportSettingsFailed => 'No se pudo exportar la configuraciรณn'; @override String get importSettings => 'Importar Configuraciรณn'; @override String get importSettingsDescription => 'Restaurar preferencias desde un archivo'; @override String get importSettingsConfirm => 'Esto reemplazarรก tu configuraciรณn actual. ยฟContinuar?'; @override String get importSettingsSuccess => 'Configuraciรณn importada'; - @override String get importSettingsFailed => 'No se pudo importar la configuraciรณn'; @override String get importSettingsInvalidFile => 'Este archivo no es una exportaciรณn vรกlida de Plezy'; @override String get importSettingsNoUser => 'Inicia sesiรณn antes de importar la configuraciรณn'; @override String get shortcutsReset => 'Atajos restablecidos a los valores predeterminados'; @@ -348,6 +345,7 @@ class _TranslationsSettingsEs extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'La duraciรณn debe estar entre ${min} y ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'El atajo ya estรก asignado a ${action}'; @override String shortcutUpdated({required Object action}) => 'Atajo actualizado para ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Salto automรกtico'; @override String get autoSkipIntro => 'Saltar Intro automรกticamente'; @override String get autoSkipIntroDescription => 'Saltar automรกticamente los marcadores de intro despuรฉs de unos segundos'; @@ -373,7 +371,7 @@ class _TranslationsSettingsEs extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Ubicaciรณn de descarga cambiada'; @override String get downloadLocationReset => 'Ubicaciรณn de descarga restablecida al predeterminado'; @override String get downloadLocationInvalid => 'La carpeta seleccionada no tiene permisos de escritura'; - @override String get downloadLocationSelectError => 'Error al seleccionar la carpeta'; + @override String get downloadLocationPickerUnavailable => 'La selecciรณn de carpetas no estรก disponible en este dispositivo'; @override String get downloadOnWifiOnly => 'Descargar solo con WiFi'; @override String get downloadOnWifiOnlyDescription => 'Evitar descargas cuando se usan datos mรณviles'; @override String get autoRemoveWatchedDownloads => 'Eliminar descargas vistas automรกticamente'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityEs extends TranslationsAccessibilityEn { @override String get hexColor => 'Color hexadecimal'; @override String get expandText => 'Expandir texto'; @override String get collapseText => 'Contraer texto'; + @override String get alphabetNavigation => 'Navegaciรณn alfabรฉtica'; + @override String get alphabetScrollHint => 'Desliza hacia arriba o abajo para avanzar por letra'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Fila ${row} de ${rowCount}, columna ${column} de ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Fila ${row} de ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsEs extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Marcar como no visto'; } +// Path: audioTracks +class _TranslationsAudioTracksEs extends TranslationsAudioTracksEn { + _TranslationsAudioTracksEs._(TranslationsEs root) : this._root = root, super.internal(root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Pista de audio ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsEs extends TranslationsVideoControlsEn { _TranslationsVideoControlsEs._(TranslationsEs root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesEs extends TranslationsMessagesEn { @override String get streamInterrupted => 'La reproducciรณn se interrumpiรณ. Pulsa reproducir o avanza para volver a intentarlo.'; @override String get liveStreamInterrupted => 'La transmisiรณn en vivo se interrumpiรณ. Pulsa reproducir para volver a intentarlo.'; @override String get fileInfoNotAvailable => 'Informaciรณn de archivo no disponible'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Error al cargar info de archivo: ${error}'; @override String get errorLoadingSeries => 'Error al cargar la serie'; @override String get musicNotSupported => 'La reproducciรณn de mรบsica aรบn no estรก soportada'; @@ -867,6 +879,7 @@ class _TranslationsProfilesEs extends TranslationsProfilesEn { @override String get borrowExplain => 'Toma prestada la conexiรณn de otro perfil. Los perfiles protegidos con PIN requieren un PIN.'; @override String get borrowEmpty => 'Nada para tomar prestado todavรญa.'; @override String get borrowEmptySubtitle => 'Conecta Plex o Jellyfin primero a otro perfil.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'De ${displayName}'; @override String get borrowConnectionBorrowed => 'Conexiรณn tomada prestada.'; @override String get borrowFailed => 'No se pudo tomar prestada la conexiรณn.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvEs extends TranslationsLiveTvEn { @override String get favorites => 'Favoritos'; @override String get reorderFavorites => 'Reordenar favoritos'; @override String get favoritesLoadFailed => 'No se pudieron cargar los favoritos. Comprueba tu conexiรณn e intรฉntalo de nuevo.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Unirse a sesiรณn en curso'; @override String watchFromStart({required Object minutes}) => 'Ver desde el inicio (hace ${minutes} min)'; @override String get watchLive => 'Ver en vivo'; @@ -2149,7 +2162,7 @@ extension on TranslationsEs { 'auth.quickConnectWaiting' => 'Esperando aprobaciรณnโ€ฆ', 'auth.quickConnectCancel' => 'Cancelar', 'auth.quickConnectExpired' => 'Quick Connect caducรณ. Intรฉntalo de nuevo.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Cancelar', 'common.save' => 'Guardar', 'common.close' => 'Cerrar', @@ -2233,8 +2246,6 @@ extension on TranslationsEs { 'settings.libraryDensity' => 'Densidad de Biblioteca', 'settings.compact' => 'Compacto', 'settings.comfortable' => 'Cรณmodo', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Modo de Vista', 'settings.gridView' => 'Cuadrรญcula', 'settings.listView' => 'Lista', @@ -2304,7 +2315,7 @@ extension on TranslationsEs { 'settings.watchTogetherRelay' => 'Relay de Ver Juntos', 'settings.watchTogetherRelayDescription' => 'Define un relay personalizado. Todos deben usar el mismo servidor.', 'settings.watchTogetherRelayHint' => 'https://mi-relay.ejemplo.com', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Informes de Errores', 'settings.crashReportingDescription' => 'Enviar informes de errores para mejorar la aplicaciรณn', 'settings.debugLogging' => 'Registro de Depuraciรณn', @@ -2321,12 +2332,10 @@ extension on TranslationsEs { 'settings.exportSettings' => 'Exportar Configuraciรณn', 'settings.exportSettingsDescription' => 'Guardar tus preferencias en un archivo', 'settings.exportSettingsSuccess' => 'Configuraciรณn exportada', - 'settings.exportSettingsFailed' => 'No se pudo exportar la configuraciรณn', 'settings.importSettings' => 'Importar Configuraciรณn', 'settings.importSettingsDescription' => 'Restaurar preferencias desde un archivo', 'settings.importSettingsConfirm' => 'Esto reemplazarรก tu configuraciรณn actual. ยฟContinuar?', 'settings.importSettingsSuccess' => 'Configuraciรณn importada', - 'settings.importSettingsFailed' => 'No se pudo importar la configuraciรณn', 'settings.importSettingsInvalidFile' => 'Este archivo no es una exportaciรณn vรกlida de Plezy', 'settings.importSettingsNoUser' => 'Inicia sesiรณn antes de importar la configuraciรณn', 'settings.shortcutsReset' => 'Atajos restablecidos a los valores predeterminados', @@ -2341,6 +2350,7 @@ extension on TranslationsEs { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'La duraciรณn debe estar entre ${min} y ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'El atajo ya estรก asignado a ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Atajo actualizado para ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Salto automรกtico', 'settings.autoSkipIntro' => 'Saltar Intro automรกticamente', 'settings.autoSkipIntroDescription' => 'Saltar automรกticamente los marcadores de intro despuรฉs de unos segundos', @@ -2366,7 +2376,7 @@ extension on TranslationsEs { 'settings.downloadLocationChanged' => 'Ubicaciรณn de descarga cambiada', 'settings.downloadLocationReset' => 'Ubicaciรณn de descarga restablecida al predeterminado', 'settings.downloadLocationInvalid' => 'La carpeta seleccionada no tiene permisos de escritura', - 'settings.downloadLocationSelectError' => 'Error al seleccionar la carpeta', + 'settings.downloadLocationPickerUnavailable' => 'La selecciรณn de carpetas no estรก disponible en este dispositivo', 'settings.downloadOnWifiOnly' => 'Descargar solo con WiFi', 'settings.downloadOnWifiOnlyDescription' => 'Evitar descargas cuando se usan datos mรณviles', 'settings.autoRemoveWatchedDownloads' => 'Eliminar descargas vistas automรกticamente', @@ -2554,10 +2564,15 @@ extension on TranslationsEs { 'accessibility.hexColor' => 'Color hexadecimal', 'accessibility.expandText' => 'Expandir texto', 'accessibility.collapseText' => 'Contraer texto', + 'accessibility.alphabetNavigation' => 'Navegaciรณn alfabรฉtica', + 'accessibility.alphabetScrollHint' => 'Desliza hacia arriba o abajo para avanzar por letra', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Fila ${row} de ${rowCount}, columna ${column} de ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Fila ${row} de ${rowCount}', 'tooltips.shufflePlay' => 'Reproducciรณn aleatoria', 'tooltips.playTrailer' => 'Reproducir trรกiler', 'tooltips.markAsWatched' => 'Marcar como visto', 'tooltips.markAsUnwatched' => 'Marcar como no visto', + 'audioTracks.track' => ({required Object n}) => 'Pista de audio ${n}', 'videoControls.audioLabel' => 'Audio', 'videoControls.subtitlesLabel' => 'Subtรญtulos', 'videoControls.resetToZero' => 'Restablecer a 0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsEs { 'messages.markedAsUnwatched' => 'Marcado como no visto', 'messages.markedAsWatchedOffline' => 'Marcado como visto (se sincronizarรก al estar en lรญnea)', 'messages.markedAsUnwatchedOffline' => 'Marcado como no visto (se sincronizarรก al estar en lรญnea)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Eliminado automรกticamente: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('es'))(n, one: 'Se eliminรณ automรกticamente ${n} descarga vista', other: 'Se eliminaron automรกticamente ${n} descargas vistas', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Eliminado automรกticamente: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('es'))(n, one: 'Se eliminรณ automรกticamente ${n} descarga vista', other: 'Se eliminaron automรกticamente ${n} descargas vistas', ), 'messages.removedFromContinueWatching' => 'Eliminado de Seguir Viendo', 'messages.errorLoading' => ({required Object error}) => 'Error: ${error}', 'messages.streamInterrupted' => 'La reproducciรณn se interrumpiรณ. Pulsa reproducir o avanza para volver a intentarlo.', 'messages.liveStreamInterrupted' => 'La transmisiรณn en vivo se interrumpiรณ. Pulsa reproducir para volver a intentarlo.', 'messages.fileInfoNotAvailable' => 'Informaciรณn de archivo no disponible', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Error al cargar info de archivo: ${error}', 'messages.errorLoadingSeries' => 'Error al cargar la serie', 'messages.musicNotSupported' => 'La reproducciรณn de mรบsica aรบn no estรก soportada', @@ -2772,6 +2787,7 @@ extension on TranslationsEs { 'profiles.borrowExplain' => 'Toma prestada la conexiรณn de otro perfil. Los perfiles protegidos con PIN requieren un PIN.', 'profiles.borrowEmpty' => 'Nada para tomar prestado todavรญa.', 'profiles.borrowEmptySubtitle' => 'Conecta Plex o Jellyfin primero a otro perfil.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'De ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Conexiรณn tomada prestada.', 'profiles.borrowFailed' => 'No se pudo tomar prestada la conexiรณn.', @@ -2997,7 +3013,7 @@ extension on TranslationsEs { 'liveTv.favorites' => 'Favoritos', 'liveTv.reorderFavorites' => 'Reordenar favoritos', 'liveTv.favoritesLoadFailed' => 'No se pudieron cargar los favoritos. Comprueba tu conexiรณn e intรฉntalo de nuevo.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Unirse a sesiรณn en curso', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Ver desde el inicio (hace ${minutes} min)', 'liveTv.watchLive' => 'Ver en vivo', @@ -3157,11 +3173,11 @@ extension on TranslationsEs { 'watchTogether.participantLeft' => ({required Object name}) => '${name} se fue', 'watchTogether.participantPaused' => ({required Object name}) => '${name} pausรณ', 'watchTogether.participantResumed' => ({required Object name}) => '${name} reanudรณ', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} avanzรณ', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} estรก cargando', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} usa una versiรณn anterior de la app โ€” sincronizaciรณn no disponible', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Reanudando sin ${name}', 'watchTogether.waitingForParticipants' => 'Esperando a que otros carguen...', 'watchTogether.waitingForName' => ({required Object name}) => 'Esperando a ${name}...', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 286fed9e..652d0c9b 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -52,6 +52,7 @@ class TranslationsFr extends Translations with BaseTranslations 'En attente d\'approbationโ€ฆ'; @override String get quickConnectCancel => 'Annuler'; @override String get quickConnectExpired => 'Quick Connect a expirรฉ. Rรฉessayez.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsFr extends TranslationsSettingsEn { @override String get libraryDensity => 'Densitรฉ des bibliothรจques'; @override String get compact => 'Compact'; @override String get comfortable => 'Confortable'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Mode d\'affichage'; @override String get gridView => 'Grille'; @override String get listView => 'Liste'; @@ -311,7 +310,7 @@ class _TranslationsSettingsFr extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Relais Regarder Ensemble'; @override String get watchTogetherRelayDescription => 'Dรฉfinir un relay personnalisรฉ. Tout le monde doit utiliser le mรชme serveur.'; @override String get watchTogetherRelayHint => 'https://mon-relais.exemple.fr'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Rapports de plantage'; @override String get crashReportingDescription => 'Envoyer des rapports de plantage pour amรฉliorer l\'application'; @override String get debugLogging => 'Journalisation de dรฉbogage'; @@ -328,12 +327,10 @@ class _TranslationsSettingsFr extends TranslationsSettingsEn { @override String get exportSettings => 'Exporter les paramรจtres'; @override String get exportSettingsDescription => 'Enregistrer vos prรฉfรฉrences dans un fichier'; @override String get exportSettingsSuccess => 'Paramรจtres exportรฉs'; - @override String get exportSettingsFailed => 'Impossible d\'exporter les paramรจtres'; @override String get importSettings => 'Importer les paramรจtres'; @override String get importSettingsDescription => 'Restaurer les prรฉfรฉrences depuis un fichier'; @override String get importSettingsConfirm => 'Cela remplacera vos paramรจtres actuels. Continuer ?'; @override String get importSettingsSuccess => 'Paramรจtres importรฉs'; - @override String get importSettingsFailed => 'Impossible d\'importer les paramรจtres'; @override String get importSettingsInvalidFile => 'Ce fichier n\'est pas un export Plezy valide'; @override String get importSettingsNoUser => 'Connectez-vous avant d\'importer les paramรจtres'; @override String get shortcutsReset => 'Raccourcis rรฉinitialisรฉs aux valeurs par dรฉfaut'; @@ -348,6 +345,7 @@ class _TranslationsSettingsFr extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'La durรฉe doit รชtre comprise entre ${min} et ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'Raccourci dรฉjร  attribuรฉ ร  ${action}'; @override String shortcutUpdated({required Object action}) => 'Raccourci mis ร  jour pour ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Skip automatique'; @override String get autoSkipIntro => 'Skip automatique de l\'introduction'; @override String get autoSkipIntroDescription => 'Skipper automatiquement l\'introduction aprรจs quelques secondes'; @@ -373,7 +371,7 @@ class _TranslationsSettingsFr extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Emplacement de tรฉlรฉchargement modifiรฉ'; @override String get downloadLocationReset => 'Emplacement de tรฉlรฉchargement rรฉinitialisรฉ ร  la valeur par dรฉfaut'; @override String get downloadLocationInvalid => 'Le dossier sรฉlectionnรฉ n\'est pas accessible en รฉcriture'; - @override String get downloadLocationSelectError => 'ร‰chec de la sรฉlection du dossier'; + @override String get downloadLocationPickerUnavailable => 'La sรฉlection de dossier nโ€™est pas disponible sur cet appareil'; @override String get downloadOnWifiOnly => 'Tรฉlรฉcharger uniquement via WiFi'; @override String get downloadOnWifiOnlyDescription => 'Empรชcher les tรฉlรฉchargements lorsque vous utilisez les donnรฉes cellulaires'; @override String get autoRemoveWatchedDownloads => 'Supprimer automatiquement les tรฉlรฉchargements vus'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityFr extends TranslationsAccessibilityEn { @override String get hexColor => 'Couleur hexadรฉcimale'; @override String get expandText => 'Dรฉvelopper le texte'; @override String get collapseText => 'Replier le texte'; + @override String get alphabetNavigation => 'Navigation alphabรฉtique'; + @override String get alphabetScrollHint => 'Balayez vers le haut ou le bas pour changer de lettre'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Ligne ${row} sur ${rowCount}, colonne ${column} sur ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Ligne ${row} sur ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsFr extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Marquรฉ comme non vu'; } +// Path: audioTracks +class _TranslationsAudioTracksFr extends TranslationsAudioTracksEn { + _TranslationsAudioTracksFr._(TranslationsFr root) : this._root = root, super.internal(root); + + final TranslationsFr _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Piste audio ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsFr extends TranslationsVideoControlsEn { _TranslationsVideoControlsFr._(TranslationsFr root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesFr extends TranslationsMessagesEn { @override String get streamInterrupted => 'La lecture a รฉtรฉ interrompue. Appuyez sur Lecture ou avancez pour rรฉessayer.'; @override String get liveStreamInterrupted => 'Le direct a รฉtรฉ interrompu. Appuyez sur Lecture pour rรฉessayer.'; @override String get fileInfoNotAvailable => 'Informations sur le fichier non disponibles'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Erreur lors du chargement des informations sur le fichier: ${error}'; @override String get errorLoadingSeries => 'Erreur lors du chargement de la sรฉrie'; @override String get musicNotSupported => 'La lecture de musique n\'est pas encore prise en charge'; @@ -867,6 +879,7 @@ class _TranslationsProfilesFr extends TranslationsProfilesEn { @override String get borrowExplain => 'Emprunter la connexion d\'un autre profil. Les profils protรฉgรฉs par PIN exigent un PIN.'; @override String get borrowEmpty => 'Rien ร  emprunter pour le moment.'; @override String get borrowEmptySubtitle => 'Connectez d\'abord Plex ou Jellyfin ร  un autre profil.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'De ${displayName}'; @override String get borrowConnectionBorrowed => 'Connexion empruntรฉe.'; @override String get borrowFailed => 'Impossible d\'emprunter la connexion.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvFr extends TranslationsLiveTvEn { @override String get favorites => 'Favoris'; @override String get reorderFavorites => 'Rรฉorganiser les favoris'; @override String get favoritesLoadFailed => 'Impossible de charger les favoris. Vรฉrifiez votre connexion et rรฉessayez.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Rejoindre la session en cours'; @override String watchFromStart({required Object minutes}) => 'Regarder depuis le dรฉbut (il y a ${minutes} min)'; @override String get watchLive => 'Regarder en direct'; @@ -2149,7 +2162,7 @@ extension on TranslationsFr { 'auth.quickConnectWaiting' => 'En attente d\'approbationโ€ฆ', 'auth.quickConnectCancel' => 'Annuler', 'auth.quickConnectExpired' => 'Quick Connect a expirรฉ. Rรฉessayez.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Annuler', 'common.save' => 'Sauvegarder', 'common.close' => 'Fermer', @@ -2233,8 +2246,6 @@ extension on TranslationsFr { 'settings.libraryDensity' => 'Densitรฉ des bibliothรจques', 'settings.compact' => 'Compact', 'settings.comfortable' => 'Confortable', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Mode d\'affichage', 'settings.gridView' => 'Grille', 'settings.listView' => 'Liste', @@ -2304,7 +2315,7 @@ extension on TranslationsFr { 'settings.watchTogetherRelay' => 'Relais Regarder Ensemble', 'settings.watchTogetherRelayDescription' => 'Dรฉfinir un relay personnalisรฉ. Tout le monde doit utiliser le mรชme serveur.', 'settings.watchTogetherRelayHint' => 'https://mon-relais.exemple.fr', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Rapports de plantage', 'settings.crashReportingDescription' => 'Envoyer des rapports de plantage pour amรฉliorer l\'application', 'settings.debugLogging' => 'Journalisation de dรฉbogage', @@ -2321,12 +2332,10 @@ extension on TranslationsFr { 'settings.exportSettings' => 'Exporter les paramรจtres', 'settings.exportSettingsDescription' => 'Enregistrer vos prรฉfรฉrences dans un fichier', 'settings.exportSettingsSuccess' => 'Paramรจtres exportรฉs', - 'settings.exportSettingsFailed' => 'Impossible d\'exporter les paramรจtres', 'settings.importSettings' => 'Importer les paramรจtres', 'settings.importSettingsDescription' => 'Restaurer les prรฉfรฉrences depuis un fichier', 'settings.importSettingsConfirm' => 'Cela remplacera vos paramรจtres actuels. Continuer ?', 'settings.importSettingsSuccess' => 'Paramรจtres importรฉs', - 'settings.importSettingsFailed' => 'Impossible d\'importer les paramรจtres', 'settings.importSettingsInvalidFile' => 'Ce fichier n\'est pas un export Plezy valide', 'settings.importSettingsNoUser' => 'Connectez-vous avant d\'importer les paramรจtres', 'settings.shortcutsReset' => 'Raccourcis rรฉinitialisรฉs aux valeurs par dรฉfaut', @@ -2341,6 +2350,7 @@ extension on TranslationsFr { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'La durรฉe doit รชtre comprise entre ${min} et ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Raccourci dรฉjร  attribuรฉ ร  ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Raccourci mis ร  jour pour ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Skip automatique', 'settings.autoSkipIntro' => 'Skip automatique de l\'introduction', 'settings.autoSkipIntroDescription' => 'Skipper automatiquement l\'introduction aprรจs quelques secondes', @@ -2366,7 +2376,7 @@ extension on TranslationsFr { 'settings.downloadLocationChanged' => 'Emplacement de tรฉlรฉchargement modifiรฉ', 'settings.downloadLocationReset' => 'Emplacement de tรฉlรฉchargement rรฉinitialisรฉ ร  la valeur par dรฉfaut', 'settings.downloadLocationInvalid' => 'Le dossier sรฉlectionnรฉ n\'est pas accessible en รฉcriture', - 'settings.downloadLocationSelectError' => 'ร‰chec de la sรฉlection du dossier', + 'settings.downloadLocationPickerUnavailable' => 'La sรฉlection de dossier nโ€™est pas disponible sur cet appareil', 'settings.downloadOnWifiOnly' => 'Tรฉlรฉcharger uniquement via WiFi', 'settings.downloadOnWifiOnlyDescription' => 'Empรชcher les tรฉlรฉchargements lorsque vous utilisez les donnรฉes cellulaires', 'settings.autoRemoveWatchedDownloads' => 'Supprimer automatiquement les tรฉlรฉchargements vus', @@ -2554,10 +2564,15 @@ extension on TranslationsFr { 'accessibility.hexColor' => 'Couleur hexadรฉcimale', 'accessibility.expandText' => 'Dรฉvelopper le texte', 'accessibility.collapseText' => 'Replier le texte', + 'accessibility.alphabetNavigation' => 'Navigation alphabรฉtique', + 'accessibility.alphabetScrollHint' => 'Balayez vers le haut ou le bas pour changer de lettre', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Ligne ${row} sur ${rowCount}, colonne ${column} sur ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Ligne ${row} sur ${rowCount}', 'tooltips.shufflePlay' => 'Lecture alรฉatoire', 'tooltips.playTrailer' => 'Lire la bande-annonce', 'tooltips.markAsWatched' => 'Marquรฉ comme vu', 'tooltips.markAsUnwatched' => 'Marquรฉ comme non vu', + 'audioTracks.track' => ({required Object n}) => 'Piste audio ${n}', 'videoControls.audioLabel' => 'Audio', 'videoControls.subtitlesLabel' => 'Sous-titres', 'videoControls.resetToZero' => 'Rรฉinitialiser ร  0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsFr { 'messages.markedAsUnwatched' => 'Marquรฉ comme non vu', 'messages.markedAsWatchedOffline' => 'Marquรฉ comme vu (se synchronisera lorsque vous serez en ligne)', 'messages.markedAsUnwatchedOffline' => 'Marquรฉ comme non vu (sera synchronisรฉ lorsque vous serez en ligne)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Supprimรฉ automatiquement : ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('fr'))(n, one: '${n} tรฉlรฉchargement vu supprimรฉ automatiquement', other: '${n} tรฉlรฉchargements vus supprimรฉs automatiquement', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Supprimรฉ automatiquement : ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('fr'))(n, one: '${n} tรฉlรฉchargement vu supprimรฉ automatiquement', other: '${n} tรฉlรฉchargements vus supprimรฉs automatiquement', ), 'messages.removedFromContinueWatching' => 'Supprimer de "Continuer ร  regarder"', 'messages.errorLoading' => ({required Object error}) => 'Erreur: ${error}', 'messages.streamInterrupted' => 'La lecture a รฉtรฉ interrompue. Appuyez sur Lecture ou avancez pour rรฉessayer.', 'messages.liveStreamInterrupted' => 'Le direct a รฉtรฉ interrompu. Appuyez sur Lecture pour rรฉessayer.', 'messages.fileInfoNotAvailable' => 'Informations sur le fichier non disponibles', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Erreur lors du chargement des informations sur le fichier: ${error}', 'messages.errorLoadingSeries' => 'Erreur lors du chargement de la sรฉrie', 'messages.musicNotSupported' => 'La lecture de musique n\'est pas encore prise en charge', @@ -2772,6 +2787,7 @@ extension on TranslationsFr { 'profiles.borrowExplain' => 'Emprunter la connexion d\'un autre profil. Les profils protรฉgรฉs par PIN exigent un PIN.', 'profiles.borrowEmpty' => 'Rien ร  emprunter pour le moment.', 'profiles.borrowEmptySubtitle' => 'Connectez d\'abord Plex ou Jellyfin ร  un autre profil.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'De ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Connexion empruntรฉe.', 'profiles.borrowFailed' => 'Impossible d\'emprunter la connexion.', @@ -2997,7 +3013,7 @@ extension on TranslationsFr { 'liveTv.favorites' => 'Favoris', 'liveTv.reorderFavorites' => 'Rรฉorganiser les favoris', 'liveTv.favoritesLoadFailed' => 'Impossible de charger les favoris. Vรฉrifiez votre connexion et rรฉessayez.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Rejoindre la session en cours', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Regarder depuis le dรฉbut (il y a ${minutes} min)', 'liveTv.watchLive' => 'Regarder en direct', @@ -3157,11 +3173,11 @@ extension on TranslationsFr { 'watchTogether.participantLeft' => ({required Object name}) => '${name} est parti', 'watchTogether.participantPaused' => ({required Object name}) => '${name} a mis en pause', 'watchTogether.participantResumed' => ({required Object name}) => '${name} a repris', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} a avancรฉ', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} met en mรฉmoire tampon', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} utilise une ancienne version de lโ€™app โ€” synchronisation indisponible', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Reprise sans ${name}', 'watchTogether.waitingForParticipants' => 'En attente du chargement des autres...', 'watchTogether.waitingForName' => ({required Object name}) => 'En attente de ${name}...', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index f6cbcd27..78ed25fe 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -52,6 +52,7 @@ class TranslationsIt extends Translations with BaseTranslations 'In attesa di approvazioneโ€ฆ'; @override String get quickConnectCancel => 'Annulla'; @override String get quickConnectExpired => 'Quick Connect scaduto. Riprova.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsIt extends TranslationsSettingsEn { @override String get libraryDensity => 'Densitร  libreria'; @override String get compact => 'Compatta'; @override String get comfortable => 'Comoda'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Modalitร  di visualizzazione'; @override String get gridView => 'Griglia'; @override String get listView => 'Elenco'; @@ -311,7 +310,7 @@ class _TranslationsSettingsIt extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Relay Guarda Insieme'; @override String get watchTogetherRelayDescription => 'Imposta un relay personalizzato. Tutti devono usare lo stesso server.'; @override String get watchTogetherRelayHint => 'https://mio-relay.esempio.it'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Segnalazione errori'; @override String get crashReportingDescription => 'Invia segnalazioni di errori per migliorare l\'app'; @override String get debugLogging => 'Log di debug'; @@ -328,12 +327,10 @@ class _TranslationsSettingsIt extends TranslationsSettingsEn { @override String get exportSettings => 'Esporta impostazioni'; @override String get exportSettingsDescription => 'Salva le tue preferenze in un file'; @override String get exportSettingsSuccess => 'Impostazioni esportate'; - @override String get exportSettingsFailed => 'Impossibile esportare le impostazioni'; @override String get importSettings => 'Importa impostazioni'; @override String get importSettingsDescription => 'Ripristina le preferenze da un file'; @override String get importSettingsConfirm => 'Questa azione sostituirร  le impostazioni attuali. Continuare?'; @override String get importSettingsSuccess => 'Impostazioni importate'; - @override String get importSettingsFailed => 'Impossibile importare le impostazioni'; @override String get importSettingsInvalidFile => 'Questo file non รจ un\'esportazione Plezy valida'; @override String get importSettingsNoUser => 'Accedi prima di importare le impostazioni'; @override String get shortcutsReset => 'Scorciatoie ripristinate alle impostazioni predefinite'; @@ -348,6 +345,7 @@ class _TranslationsSettingsIt extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'la durata deve essere compresa tra ${min} e ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'Scorciatoia giร  assegnata a ${action}'; @override String shortcutUpdated({required Object action}) => 'Scorciatoia aggiornata per ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Salto Automatico'; @override String get autoSkipIntro => 'Salta Intro Automaticamente'; @override String get autoSkipIntroDescription => 'Salta automaticamente i marcatori dell\'intro dopo alcuni secondi'; @@ -373,7 +371,7 @@ class _TranslationsSettingsIt extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Posizione di download modificata'; @override String get downloadLocationReset => 'Posizione di download ripristinata a predefinita'; @override String get downloadLocationInvalid => 'La cartella selezionata non รจ scrivibile'; - @override String get downloadLocationSelectError => 'Impossibile selezionare la cartella'; + @override String get downloadLocationPickerUnavailable => 'La selezione della cartella non รจ disponibile su questo dispositivo'; @override String get downloadOnWifiOnly => 'Scarica solo con WiFi'; @override String get downloadOnWifiOnlyDescription => 'Impedisci i download quando si utilizza la rete dati cellulare'; @override String get autoRemoveWatchedDownloads => 'Rimuovi automaticamente i download visti'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityIt extends TranslationsAccessibilityEn { @override String get hexColor => 'Colore esadecimale'; @override String get expandText => 'Espandi il testo'; @override String get collapseText => 'Comprimi il testo'; + @override String get alphabetNavigation => 'Navigazione alfabetica'; + @override String get alphabetScrollHint => 'Scorri verso l\'alto o il basso per cambiare lettera'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Riga ${row} di ${rowCount}, colonna ${column} di ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Riga ${row} di ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsIt extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Segna come non visto'; } +// Path: audioTracks +class _TranslationsAudioTracksIt extends TranslationsAudioTracksEn { + _TranslationsAudioTracksIt._(TranslationsIt root) : this._root = root, super.internal(root); + + final TranslationsIt _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Traccia audio ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsIt extends TranslationsVideoControlsEn { _TranslationsVideoControlsIt._(TranslationsIt root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesIt extends TranslationsMessagesEn { @override String get streamInterrupted => 'La riproduzione si รจ interrotta. Premi Riproduci o scorri per riprovare.'; @override String get liveStreamInterrupted => 'La diretta si รจ interrotta. Premi Riproduci per riprovare.'; @override String get fileInfoNotAvailable => 'Informazioni sul file non disponibili'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Errore caricamento informazioni sul file: ${error}'; @override String get errorLoadingSeries => 'Errore caricamento serie'; @override String get musicNotSupported => 'La riproduzione musicale non รจ ancora supportata'; @@ -867,6 +879,7 @@ class _TranslationsProfilesIt extends TranslationsProfilesEn { @override String get borrowExplain => 'Prendi in prestito la connessione di un altro profilo. I profili protetti da PIN richiedono un PIN.'; @override String get borrowEmpty => 'Nulla da prendere in prestito al momento.'; @override String get borrowEmptySubtitle => 'Collega prima Plex o Jellyfin a un altro profilo.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'Da ${displayName}'; @override String get borrowConnectionBorrowed => 'Connessione presa in prestito.'; @override String get borrowFailed => 'Impossibile prendere in prestito la connessione.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvIt extends TranslationsLiveTvEn { @override String get favorites => 'Preferiti'; @override String get reorderFavorites => 'Riordina preferiti'; @override String get favoritesLoadFailed => 'Impossibile caricare i preferiti. Controlla la connessione e riprova.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Partecipa alla sessione in corso'; @override String watchFromStart({required Object minutes}) => 'Guarda dall\'inizio (${minutes} min fa)'; @override String get watchLive => 'Guarda in diretta'; @@ -2149,7 +2162,7 @@ extension on TranslationsIt { 'auth.quickConnectWaiting' => 'In attesa di approvazioneโ€ฆ', 'auth.quickConnectCancel' => 'Annulla', 'auth.quickConnectExpired' => 'Quick Connect scaduto. Riprova.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Cancella', 'common.save' => 'Salva', 'common.close' => 'Chiudi', @@ -2233,8 +2246,6 @@ extension on TranslationsIt { 'settings.libraryDensity' => 'Densitร  libreria', 'settings.compact' => 'Compatta', 'settings.comfortable' => 'Comoda', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Modalitร  di visualizzazione', 'settings.gridView' => 'Griglia', 'settings.listView' => 'Elenco', @@ -2304,7 +2315,7 @@ extension on TranslationsIt { 'settings.watchTogetherRelay' => 'Relay Guarda Insieme', 'settings.watchTogetherRelayDescription' => 'Imposta un relay personalizzato. Tutti devono usare lo stesso server.', 'settings.watchTogetherRelayHint' => 'https://mio-relay.esempio.it', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Segnalazione errori', 'settings.crashReportingDescription' => 'Invia segnalazioni di errori per migliorare l\'app', 'settings.debugLogging' => 'Log di debug', @@ -2321,12 +2332,10 @@ extension on TranslationsIt { 'settings.exportSettings' => 'Esporta impostazioni', 'settings.exportSettingsDescription' => 'Salva le tue preferenze in un file', 'settings.exportSettingsSuccess' => 'Impostazioni esportate', - 'settings.exportSettingsFailed' => 'Impossibile esportare le impostazioni', 'settings.importSettings' => 'Importa impostazioni', 'settings.importSettingsDescription' => 'Ripristina le preferenze da un file', 'settings.importSettingsConfirm' => 'Questa azione sostituirร  le impostazioni attuali. Continuare?', 'settings.importSettingsSuccess' => 'Impostazioni importate', - 'settings.importSettingsFailed' => 'Impossibile importare le impostazioni', 'settings.importSettingsInvalidFile' => 'Questo file non รจ un\'esportazione Plezy valida', 'settings.importSettingsNoUser' => 'Accedi prima di importare le impostazioni', 'settings.shortcutsReset' => 'Scorciatoie ripristinate alle impostazioni predefinite', @@ -2341,6 +2350,7 @@ extension on TranslationsIt { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'la durata deve essere compresa tra ${min} e ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Scorciatoia giร  assegnata a ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Scorciatoia aggiornata per ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Salto Automatico', 'settings.autoSkipIntro' => 'Salta Intro Automaticamente', 'settings.autoSkipIntroDescription' => 'Salta automaticamente i marcatori dell\'intro dopo alcuni secondi', @@ -2366,7 +2376,7 @@ extension on TranslationsIt { 'settings.downloadLocationChanged' => 'Posizione di download modificata', 'settings.downloadLocationReset' => 'Posizione di download ripristinata a predefinita', 'settings.downloadLocationInvalid' => 'La cartella selezionata non รจ scrivibile', - 'settings.downloadLocationSelectError' => 'Impossibile selezionare la cartella', + 'settings.downloadLocationPickerUnavailable' => 'La selezione della cartella non รจ disponibile su questo dispositivo', 'settings.downloadOnWifiOnly' => 'Scarica solo con WiFi', 'settings.downloadOnWifiOnlyDescription' => 'Impedisci i download quando si utilizza la rete dati cellulare', 'settings.autoRemoveWatchedDownloads' => 'Rimuovi automaticamente i download visti', @@ -2554,10 +2564,15 @@ extension on TranslationsIt { 'accessibility.hexColor' => 'Colore esadecimale', 'accessibility.expandText' => 'Espandi il testo', 'accessibility.collapseText' => 'Comprimi il testo', + 'accessibility.alphabetNavigation' => 'Navigazione alfabetica', + 'accessibility.alphabetScrollHint' => 'Scorri verso l\'alto o il basso per cambiare lettera', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Riga ${row} di ${rowCount}, colonna ${column} di ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Riga ${row} di ${rowCount}', 'tooltips.shufflePlay' => 'Riproduzione casuale', 'tooltips.playTrailer' => 'Riproduci trailer', 'tooltips.markAsWatched' => 'Segna come visto', 'tooltips.markAsUnwatched' => 'Segna come non visto', + 'audioTracks.track' => ({required Object n}) => 'Traccia audio ${n}', 'videoControls.audioLabel' => 'Audio', 'videoControls.subtitlesLabel' => 'Sottotitoli', 'videoControls.resetToZero' => 'Riporta a 0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsIt { 'messages.markedAsUnwatched' => 'Segna come non visto', 'messages.markedAsWatchedOffline' => 'Segnato come visto (sincronizzato online)', 'messages.markedAsUnwatchedOffline' => 'Segnato come non visto (sincronizzato online)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Rimosso automaticamente: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('it'))(n, one: 'Rimosso automaticamente ${n} download giร  visto', other: 'Rimossi automaticamente ${n} download giร  visti', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Rimosso automaticamente: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('it'))(n, one: 'Rimosso automaticamente ${n} download giร  visto', other: 'Rimossi automaticamente ${n} download giร  visti', ), 'messages.removedFromContinueWatching' => 'Rimosso da Continua a guardare', 'messages.errorLoading' => ({required Object error}) => 'Errore: ${error}', 'messages.streamInterrupted' => 'La riproduzione si รจ interrotta. Premi Riproduci o scorri per riprovare.', 'messages.liveStreamInterrupted' => 'La diretta si รจ interrotta. Premi Riproduci per riprovare.', 'messages.fileInfoNotAvailable' => 'Informazioni sul file non disponibili', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Errore caricamento informazioni sul file: ${error}', 'messages.errorLoadingSeries' => 'Errore caricamento serie', 'messages.musicNotSupported' => 'La riproduzione musicale non รจ ancora supportata', @@ -2772,6 +2787,7 @@ extension on TranslationsIt { 'profiles.borrowExplain' => 'Prendi in prestito la connessione di un altro profilo. I profili protetti da PIN richiedono un PIN.', 'profiles.borrowEmpty' => 'Nulla da prendere in prestito al momento.', 'profiles.borrowEmptySubtitle' => 'Collega prima Plex o Jellyfin a un altro profilo.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'Da ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Connessione presa in prestito.', 'profiles.borrowFailed' => 'Impossibile prendere in prestito la connessione.', @@ -2997,7 +3013,7 @@ extension on TranslationsIt { 'liveTv.favorites' => 'Preferiti', 'liveTv.reorderFavorites' => 'Riordina preferiti', 'liveTv.favoritesLoadFailed' => 'Impossibile caricare i preferiti. Controlla la connessione e riprova.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Partecipa alla sessione in corso', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Guarda dall\'inizio (${minutes} min fa)', 'liveTv.watchLive' => 'Guarda in diretta', @@ -3157,11 +3173,11 @@ extension on TranslationsIt { 'watchTogether.participantLeft' => ({required Object name}) => '${name} se ne รจ andato', 'watchTogether.participantPaused' => ({required Object name}) => '${name} ha messo in pausa', 'watchTogether.participantResumed' => ({required Object name}) => '${name} ha ripreso', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} ha cercato', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} sta caricando', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} usa una versione precedente dell\'app โ€” sincronizzazione non disponibile', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Ripresa senza ${name}', 'watchTogether.waitingForParticipants' => 'In attesa che gli altri carichino...', 'watchTogether.waitingForName' => ({required Object name}) => 'In attesa di ${name}...', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index b48ce00e..58985922 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -52,6 +52,7 @@ class TranslationsJa extends Translations with BaseTranslations 'ๆ‰ฟ่ชใ‚’ๅพ…ใฃใฆใ„ใพใ™โ€ฆ'; @override String get quickConnectCancel => 'ใ‚ญใƒฃใƒณใ‚ปใƒซ'; @override String get quickConnectExpired => 'Quick Connectใฎๆœ‰ๅŠนๆœŸ้™ใŒๅˆ‡ใ‚Œใพใ—ใŸใ€‚ใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsJa extends TranslationsSettingsEn { @override String get libraryDensity => 'ใƒฉใ‚คใƒ–ใƒฉใƒชใฎๅฏ†ๅบฆ'; @override String get compact => 'ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆ'; @override String get comfortable => 'ใ‚†ใฃใŸใ‚Š'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => '่กจ็คบใƒขใƒผใƒ‰'; @override String get gridView => 'ใ‚ฐใƒชใƒƒใƒ‰'; @override String get listView => 'ใƒชใ‚นใƒˆ'; @@ -311,7 +310,7 @@ class _TranslationsSettingsJa extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'ไธ€็ท’ใซ่ฆ–่ดใƒชใƒฌใƒผใ‚ตใƒผใƒใƒผ'; @override String get watchTogetherRelayDescription => 'ใ‚ซใ‚นใ‚ฟใƒ ใƒชใƒฌใƒผใ‚’่จญๅฎšใ—ใพใ™ใ€‚ๅ…จๅ“กใŒๅŒใ˜ใ‚ตใƒผใƒใƒผใ‚’ไฝฟใ†ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚'; @override String get watchTogetherRelayHint => 'https://my-relay.example.com'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'ใ‚ฏใƒฉใƒƒใ‚ทใƒฅใƒฌใƒใƒผใƒˆ'; @override String get crashReportingDescription => 'ใ‚ขใƒ—ใƒชใฎๆ”นๅ–„ใซๅฝน็ซ‹ใคใ‚ฏใƒฉใƒƒใ‚ทใƒฅใƒฌใƒใƒผใƒˆใ‚’้€ไฟก'; @override String get debugLogging => 'ใƒ‡ใƒใƒƒใ‚ฐใƒญใ‚ฐ'; @@ -328,12 +327,10 @@ class _TranslationsSettingsJa extends TranslationsSettingsEn { @override String get exportSettings => '่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆ'; @override String get exportSettingsDescription => '่จญๅฎšใ‚’ใƒ•ใ‚กใ‚คใƒซใซไฟๅญ˜'; @override String get exportSettingsSuccess => '่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใ—ใพใ—ใŸ'; - @override String get exportSettingsFailed => '่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใงใใพใ›ใ‚“ใงใ—ใŸ'; @override String get importSettings => '่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆ'; @override String get importSettingsDescription => 'ใƒ•ใ‚กใ‚คใƒซใ‹ใ‚‰่จญๅฎšใ‚’ๅพฉๅ…ƒ'; @override String get importSettingsConfirm => '็พๅœจใฎ่จญๅฎšใ‚’็ฝฎใๆ›ใˆใพใ™ใ€‚็ถš่กŒใ—ใพใ™ใ‹๏ผŸ'; @override String get importSettingsSuccess => '่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใพใ—ใŸ'; - @override String get importSettingsFailed => '่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใงใใพใ›ใ‚“ใงใ—ใŸ'; @override String get importSettingsInvalidFile => 'ใ“ใฎใƒ•ใ‚กใ‚คใƒซใฏๆœ‰ๅŠนใชPlezyใฎ่จญๅฎšใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใงใฏใ‚ใ‚Šใพใ›ใ‚“'; @override String get importSettingsNoUser => '่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใ™ใ‚‹ๅ‰ใซใ‚ตใ‚คใƒณใ‚คใƒณใ—ใฆใใ ใ•ใ„'; @override String get shortcutsReset => 'ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚’ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใซใƒชใ‚ปใƒƒใƒˆใ—ใพใ—ใŸ'; @@ -348,6 +345,7 @@ class _TranslationsSettingsJa extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'ๆ™‚้–“ใฏ${min}ใ‹ใ‚‰${max} ${unit}ใฎ้–“ใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™'; @override String shortcutAlreadyAssigned({required Object action}) => 'ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใฏๆ—ขใซ${action}ใซๅ‰ฒใ‚Šๅฝ“ใฆใ‚‰ใ‚Œใฆใ„ใพใ™'; @override String shortcutUpdated({required Object action}) => '${action}ใฎใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚’ๆ›ดๆ–ฐใ—ใพใ—ใŸ'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => '่‡ชๅ‹•ใ‚นใ‚ญใƒƒใƒ—'; @override String get autoSkipIntro => 'ใ‚คใƒณใƒˆใƒญใ‚’่‡ชๅ‹•ใ‚นใ‚ญใƒƒใƒ—'; @override String get autoSkipIntroDescription => 'ๆ•ฐ็ง’ๅพŒใซใ‚คใƒณใƒˆใƒญใƒžใƒผใ‚ซใƒผใ‚’่‡ชๅ‹•็š„ใซใ‚นใ‚ญใƒƒใƒ—'; @@ -373,7 +371,7 @@ class _TranslationsSettingsJa extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅ ดๆ‰€ใ‚’ๅค‰ๆ›ดใ—ใพใ—ใŸ'; @override String get downloadLocationReset => 'ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅ ดๆ‰€ใ‚’ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใซใƒชใ‚ปใƒƒใƒˆใ—ใพใ—ใŸ'; @override String get downloadLocationInvalid => '้ธๆŠžใ—ใŸใƒ•ใ‚ฉใƒซใƒ€ใฏๆ›ธใ่พผใฟใงใใพใ›ใ‚“'; - @override String get downloadLocationSelectError => 'ใƒ•ใ‚ฉใƒซใƒ€ใฎ้ธๆŠžใซๅคฑๆ•—ใ—ใพใ—ใŸ'; + @override String get downloadLocationPickerUnavailable => 'ใ“ใฎใƒ‡ใƒใ‚คใ‚นใงใฏใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠžใงใใพใ›ใ‚“'; @override String get downloadOnWifiOnly => 'WiFiใฎใฟใงใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰'; @override String get downloadOnWifiOnlyDescription => 'ใƒขใƒใ‚คใƒซใƒ‡ใƒผใ‚ฟ้€šไฟกๆ™‚ใฎใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ‚’้˜ฒๆญข'; @override String get autoRemoveWatchedDownloads => '่ฆ–่ดๆธˆใฟใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใฎ่‡ชๅ‹•ๅ‰Š้™ค'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityJa extends TranslationsAccessibilityEn { @override String get hexColor => '16้€ฒใ‚ซใƒฉใƒผ'; @override String get expandText => 'ใƒ†ใ‚ญใ‚นใƒˆใ‚’ๅฑ•้–‹'; @override String get collapseText => 'ใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆŠ˜ใ‚ŠใŸใŸใ‚€'; + @override String get alphabetNavigation => 'ใ‚ขใƒซใƒ•ใ‚กใƒ™ใƒƒใƒˆใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณ'; + @override String get alphabetScrollHint => 'ไธŠไธ‹ใซใ‚นใƒฏใ‚คใƒ—ใ—ใฆๆ–‡ๅญ—ใ”ใจใซ็งปๅ‹•'; + @override String rowColumnPosition({required Object rowCount, required Object row, required Object columnCount, required Object column}) => '${rowCount}่กŒไธญ${row}่กŒใ€${columnCount}ๅˆ—ไธญ${column}ๅˆ—'; + @override String rowPosition({required Object rowCount, required Object row}) => '${rowCount}่กŒไธญ${row}่กŒ'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsJa extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'ๆœช่ฆ–่ดใซใ™ใ‚‹'; } +// Path: audioTracks +class _TranslationsAudioTracksJa extends TranslationsAudioTracksEn { + _TranslationsAudioTracksJa._(TranslationsJa root) : this._root = root, super.internal(root); + + final TranslationsJa _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => '้Ÿณๅฃฐใƒˆใƒฉใƒƒใ‚ฏ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsJa extends TranslationsVideoControlsEn { _TranslationsVideoControlsJa._(TranslationsJa root) : this._root = root, super.internal(root); @@ -711,11 +723,11 @@ class _TranslationsMessagesJa extends TranslationsMessagesEn { @override String get streamInterrupted => 'ใ‚นใƒˆใƒชใƒผใƒ ใŒไธญๆ–ญใ•ใ‚Œใพใ—ใŸใ€‚ๅ†็”Ÿใ‚’ๆŠผใ™ใ‹ใ‚ทใƒผใ‚ฏใ—ใฆๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„ใ€‚'; @override String get liveStreamInterrupted => 'ใƒฉใ‚คใƒ–ใ‚นใƒˆใƒชใƒผใƒ ใŒไธญๆ–ญใ•ใ‚Œใพใ—ใŸใ€‚ๅ†็”Ÿใ‚’ๆŠผใ—ใฆๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„ใ€‚'; @override String get fileInfoNotAvailable => 'ใƒ•ใ‚กใ‚คใƒซๆƒ…ๅ ฑใŒๅˆฉ็”จใงใใพใ›ใ‚“'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'ใƒ•ใ‚กใ‚คใƒซๆƒ…ๅ ฑใฎ่ชญใฟ่พผใฟใ‚จใƒฉใƒผ: ${error}'; @override String get errorLoadingSeries => 'ใ‚ทใƒชใƒผใ‚บใฎ่ชญใฟ่พผใฟใ‚จใƒฉใƒผ'; @override String get musicNotSupported => '้Ÿณๆฅฝใฎๅ†็”Ÿใฏใพใ ใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใพใ›ใ‚“'; @@ -866,6 +878,7 @@ class _TranslationsProfilesJa extends TranslationsProfilesEn { @override String get borrowExplain => 'ๅˆฅใฎใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใฎๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใ—ใพใ™ใ€‚PINไฟ่ญทใ•ใ‚ŒใŸใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใซใฏPINใŒๅฟ…่ฆใงใ™ใ€‚'; @override String get borrowEmpty => 'ใพใ ๅ€Ÿใ‚Šใ‚‹ใ‚‚ใฎใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚'; @override String get borrowEmptySubtitle => 'ใพใšๅˆฅใฎใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใซPlexใพใŸใฏJellyfinใ‚’ๆŽฅ็ถšใ—ใฆใใ ใ•ใ„ใ€‚'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => '${displayName}ใ‹ใ‚‰'; @override String get borrowConnectionBorrowed => 'ๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใ—ใพใ—ใŸใ€‚'; @override String get borrowFailed => 'ๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใงใใพใ›ใ‚“ใงใ—ใŸใ€‚'; @@ -1148,7 +1161,7 @@ class _TranslationsLiveTvJa extends TranslationsLiveTvEn { @override String get favorites => 'ใŠๆฐ—ใซๅ…ฅใ‚Š'; @override String get reorderFavorites => 'ใŠๆฐ—ใซๅ…ฅใ‚Šใ‚’ไธฆในๆ›ฟใˆ'; @override String get favoritesLoadFailed => 'ใŠๆฐ—ใซๅ…ฅใ‚Šใ‚’่ชญใฟ่พผใ‚ใพใ›ใ‚“ใงใ—ใŸใ€‚ๆŽฅ็ถšใ‚’็ขบ่ชใ—ใฆใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => '้€ฒ่กŒไธญใฎใ‚ปใƒƒใ‚ทใƒงใƒณใซๅ‚ๅŠ '; @override String watchFromStart({required Object minutes}) => 'ๆœ€ๅˆใ‹ใ‚‰่ฆ–่ด๏ผˆ${minutes}ๅˆ†ๅ‰ใซ้–‹ๅง‹๏ผ‰'; @override String get watchLive => 'ใƒฉใ‚คใƒ–ใง่ฆ–่ด'; @@ -2146,7 +2159,7 @@ extension on TranslationsJa { 'auth.quickConnectWaiting' => 'ๆ‰ฟ่ชใ‚’ๅพ…ใฃใฆใ„ใพใ™โ€ฆ', 'auth.quickConnectCancel' => 'ใ‚ญใƒฃใƒณใ‚ปใƒซ', 'auth.quickConnectExpired' => 'Quick Connectใฎๆœ‰ๅŠนๆœŸ้™ใŒๅˆ‡ใ‚Œใพใ—ใŸใ€‚ใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'ใ‚ญใƒฃใƒณใ‚ปใƒซ', 'common.save' => 'ไฟๅญ˜', 'common.close' => '้–‰ใ˜ใ‚‹', @@ -2230,8 +2243,6 @@ extension on TranslationsJa { 'settings.libraryDensity' => 'ใƒฉใ‚คใƒ–ใƒฉใƒชใฎๅฏ†ๅบฆ', 'settings.compact' => 'ใ‚ณใƒณใƒ‘ใ‚ฏใƒˆ', 'settings.comfortable' => 'ใ‚†ใฃใŸใ‚Š', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => '่กจ็คบใƒขใƒผใƒ‰', 'settings.gridView' => 'ใ‚ฐใƒชใƒƒใƒ‰', 'settings.listView' => 'ใƒชใ‚นใƒˆ', @@ -2301,7 +2312,7 @@ extension on TranslationsJa { 'settings.watchTogetherRelay' => 'ไธ€็ท’ใซ่ฆ–่ดใƒชใƒฌใƒผใ‚ตใƒผใƒใƒผ', 'settings.watchTogetherRelayDescription' => 'ใ‚ซใ‚นใ‚ฟใƒ ใƒชใƒฌใƒผใ‚’่จญๅฎšใ—ใพใ™ใ€‚ๅ…จๅ“กใŒๅŒใ˜ใ‚ตใƒผใƒใƒผใ‚’ไฝฟใ†ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚', 'settings.watchTogetherRelayHint' => 'https://my-relay.example.com', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'ใ‚ฏใƒฉใƒƒใ‚ทใƒฅใƒฌใƒใƒผใƒˆ', 'settings.crashReportingDescription' => 'ใ‚ขใƒ—ใƒชใฎๆ”นๅ–„ใซๅฝน็ซ‹ใคใ‚ฏใƒฉใƒƒใ‚ทใƒฅใƒฌใƒใƒผใƒˆใ‚’้€ไฟก', 'settings.debugLogging' => 'ใƒ‡ใƒใƒƒใ‚ฐใƒญใ‚ฐ', @@ -2318,12 +2329,10 @@ extension on TranslationsJa { 'settings.exportSettings' => '่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆ', 'settings.exportSettingsDescription' => '่จญๅฎšใ‚’ใƒ•ใ‚กใ‚คใƒซใซไฟๅญ˜', 'settings.exportSettingsSuccess' => '่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใ—ใพใ—ใŸ', - 'settings.exportSettingsFailed' => '่จญๅฎšใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใงใใพใ›ใ‚“ใงใ—ใŸ', 'settings.importSettings' => '่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆ', 'settings.importSettingsDescription' => 'ใƒ•ใ‚กใ‚คใƒซใ‹ใ‚‰่จญๅฎšใ‚’ๅพฉๅ…ƒ', 'settings.importSettingsConfirm' => '็พๅœจใฎ่จญๅฎšใ‚’็ฝฎใๆ›ใˆใพใ™ใ€‚็ถš่กŒใ—ใพใ™ใ‹๏ผŸ', 'settings.importSettingsSuccess' => '่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใพใ—ใŸ', - 'settings.importSettingsFailed' => '่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใงใใพใ›ใ‚“ใงใ—ใŸ', 'settings.importSettingsInvalidFile' => 'ใ“ใฎใƒ•ใ‚กใ‚คใƒซใฏๆœ‰ๅŠนใชPlezyใฎ่จญๅฎšใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใงใฏใ‚ใ‚Šใพใ›ใ‚“', 'settings.importSettingsNoUser' => '่จญๅฎšใ‚’ใ‚คใƒณใƒใƒผใƒˆใ™ใ‚‹ๅ‰ใซใ‚ตใ‚คใƒณใ‚คใƒณใ—ใฆใใ ใ•ใ„', 'settings.shortcutsReset' => 'ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚’ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใซใƒชใ‚ปใƒƒใƒˆใ—ใพใ—ใŸ', @@ -2338,6 +2347,7 @@ extension on TranslationsJa { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'ๆ™‚้–“ใฏ${min}ใ‹ใ‚‰${max} ${unit}ใฎ้–“ใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใฏๆ—ขใซ${action}ใซๅ‰ฒใ‚Šๅฝ“ใฆใ‚‰ใ‚Œใฆใ„ใพใ™', 'settings.shortcutUpdated' => ({required Object action}) => '${action}ใฎใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚’ๆ›ดๆ–ฐใ—ใพใ—ใŸ', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => '่‡ชๅ‹•ใ‚นใ‚ญใƒƒใƒ—', 'settings.autoSkipIntro' => 'ใ‚คใƒณใƒˆใƒญใ‚’่‡ชๅ‹•ใ‚นใ‚ญใƒƒใƒ—', 'settings.autoSkipIntroDescription' => 'ๆ•ฐ็ง’ๅพŒใซใ‚คใƒณใƒˆใƒญใƒžใƒผใ‚ซใƒผใ‚’่‡ชๅ‹•็š„ใซใ‚นใ‚ญใƒƒใƒ—', @@ -2363,7 +2373,7 @@ extension on TranslationsJa { 'settings.downloadLocationChanged' => 'ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅ ดๆ‰€ใ‚’ๅค‰ๆ›ดใ—ใพใ—ใŸ', 'settings.downloadLocationReset' => 'ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ๅ ดๆ‰€ใ‚’ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใซใƒชใ‚ปใƒƒใƒˆใ—ใพใ—ใŸ', 'settings.downloadLocationInvalid' => '้ธๆŠžใ—ใŸใƒ•ใ‚ฉใƒซใƒ€ใฏๆ›ธใ่พผใฟใงใใพใ›ใ‚“', - 'settings.downloadLocationSelectError' => 'ใƒ•ใ‚ฉใƒซใƒ€ใฎ้ธๆŠžใซๅคฑๆ•—ใ—ใพใ—ใŸ', + 'settings.downloadLocationPickerUnavailable' => 'ใ“ใฎใƒ‡ใƒใ‚คใ‚นใงใฏใƒ•ใ‚ฉใƒซใƒ€ใ‚’้ธๆŠžใงใใพใ›ใ‚“', 'settings.downloadOnWifiOnly' => 'WiFiใฎใฟใงใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰', 'settings.downloadOnWifiOnlyDescription' => 'ใƒขใƒใ‚คใƒซใƒ‡ใƒผใ‚ฟ้€šไฟกๆ™‚ใฎใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ‚’้˜ฒๆญข', 'settings.autoRemoveWatchedDownloads' => '่ฆ–่ดๆธˆใฟใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใฎ่‡ชๅ‹•ๅ‰Š้™ค', @@ -2551,10 +2561,15 @@ extension on TranslationsJa { 'accessibility.hexColor' => '16้€ฒใ‚ซใƒฉใƒผ', 'accessibility.expandText' => 'ใƒ†ใ‚ญใ‚นใƒˆใ‚’ๅฑ•้–‹', 'accessibility.collapseText' => 'ใƒ†ใ‚ญใ‚นใƒˆใ‚’ๆŠ˜ใ‚ŠใŸใŸใ‚€', + 'accessibility.alphabetNavigation' => 'ใ‚ขใƒซใƒ•ใ‚กใƒ™ใƒƒใƒˆใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณ', + 'accessibility.alphabetScrollHint' => 'ไธŠไธ‹ใซใ‚นใƒฏใ‚คใƒ—ใ—ใฆๆ–‡ๅญ—ใ”ใจใซ็งปๅ‹•', + 'accessibility.rowColumnPosition' => ({required Object rowCount, required Object row, required Object columnCount, required Object column}) => '${rowCount}่กŒไธญ${row}่กŒใ€${columnCount}ๅˆ—ไธญ${column}ๅˆ—', + 'accessibility.rowPosition' => ({required Object rowCount, required Object row}) => '${rowCount}่กŒไธญ${row}่กŒ', 'tooltips.shufflePlay' => 'ใ‚ทใƒฃใƒƒใƒ•ใƒซๅ†็”Ÿ', 'tooltips.playTrailer' => 'ไบˆๅ‘Š็ทจใ‚’ๅ†็”Ÿ', 'tooltips.markAsWatched' => '่ฆ–่ดๆธˆใฟใซใ™ใ‚‹', 'tooltips.markAsUnwatched' => 'ๆœช่ฆ–่ดใซใ™ใ‚‹', + 'audioTracks.track' => ({required Object n}) => '้Ÿณๅฃฐใƒˆใƒฉใƒƒใ‚ฏ${n}', 'videoControls.audioLabel' => '้Ÿณๅฃฐ', 'videoControls.subtitlesLabel' => 'ๅญ—ๅน•', 'videoControls.resetToZero' => '0msใซใƒชใ‚ปใƒƒใƒˆ', @@ -2641,20 +2656,20 @@ extension on TranslationsJa { 'messages.markedAsUnwatched' => 'ๆœช่ฆ–่ดใซใ—ใพใ—ใŸ', 'messages.markedAsWatchedOffline' => '่ฆ–่ดๆธˆใฟใซใ—ใพใ—ใŸ๏ผˆใ‚ชใƒณใƒฉใ‚คใƒณๆ™‚ใซๅŒๆœŸ๏ผ‰', 'messages.markedAsUnwatchedOffline' => 'ๆœช่ฆ–่ดใซใ—ใพใ—ใŸ๏ผˆใ‚ชใƒณใƒฉใ‚คใƒณๆ™‚ใซๅŒๆœŸ๏ผ‰', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => '่‡ชๅ‹•ๅ‰Š้™ค: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ja'))(n, other: '่ฆ–่ดๆธˆใฟใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ‚’${n}ไปถ่‡ชๅ‹•ๅ‰Š้™คใ—ใพใ—ใŸ', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => '่‡ชๅ‹•ๅ‰Š้™ค: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ja'))(n, other: '่ฆ–่ดๆธˆใฟใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ‚’${n}ไปถ่‡ชๅ‹•ๅ‰Š้™คใ—ใพใ—ใŸ', ), 'messages.removedFromContinueWatching' => '่ฆ–่ดไธญใ‹ใ‚‰ๅ‰Š้™คใ—ใพใ—ใŸ', 'messages.errorLoading' => ({required Object error}) => 'ใ‚จใƒฉใƒผ: ${error}', 'messages.streamInterrupted' => 'ใ‚นใƒˆใƒชใƒผใƒ ใŒไธญๆ–ญใ•ใ‚Œใพใ—ใŸใ€‚ๅ†็”Ÿใ‚’ๆŠผใ™ใ‹ใ‚ทใƒผใ‚ฏใ—ใฆๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„ใ€‚', 'messages.liveStreamInterrupted' => 'ใƒฉใ‚คใƒ–ใ‚นใƒˆใƒชใƒผใƒ ใŒไธญๆ–ญใ•ใ‚Œใพใ—ใŸใ€‚ๅ†็”Ÿใ‚’ๆŠผใ—ใฆๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„ใ€‚', 'messages.fileInfoNotAvailable' => 'ใƒ•ใ‚กใ‚คใƒซๆƒ…ๅ ฑใŒๅˆฉ็”จใงใใพใ›ใ‚“', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'ใƒ•ใ‚กใ‚คใƒซๆƒ…ๅ ฑใฎ่ชญใฟ่พผใฟใ‚จใƒฉใƒผ: ${error}', 'messages.errorLoadingSeries' => 'ใ‚ทใƒชใƒผใ‚บใฎ่ชญใฟ่พผใฟใ‚จใƒฉใƒผ', 'messages.musicNotSupported' => '้Ÿณๆฅฝใฎๅ†็”Ÿใฏใพใ ใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใพใ›ใ‚“', @@ -2769,6 +2784,7 @@ extension on TranslationsJa { 'profiles.borrowExplain' => 'ๅˆฅใฎใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใฎๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใ—ใพใ™ใ€‚PINไฟ่ญทใ•ใ‚ŒใŸใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใซใฏPINใŒๅฟ…่ฆใงใ™ใ€‚', 'profiles.borrowEmpty' => 'ใพใ ๅ€Ÿใ‚Šใ‚‹ใ‚‚ใฎใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚', 'profiles.borrowEmptySubtitle' => 'ใพใšๅˆฅใฎใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใซPlexใพใŸใฏJellyfinใ‚’ๆŽฅ็ถšใ—ใฆใใ ใ•ใ„ใ€‚', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => '${displayName}ใ‹ใ‚‰', 'profiles.borrowConnectionBorrowed' => 'ๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใ—ใพใ—ใŸใ€‚', 'profiles.borrowFailed' => 'ๆŽฅ็ถšใ‚’ๅ€Ÿ็”จใงใใพใ›ใ‚“ใงใ—ใŸใ€‚', @@ -2994,7 +3010,7 @@ extension on TranslationsJa { 'liveTv.favorites' => 'ใŠๆฐ—ใซๅ…ฅใ‚Š', 'liveTv.reorderFavorites' => 'ใŠๆฐ—ใซๅ…ฅใ‚Šใ‚’ไธฆในๆ›ฟใˆ', 'liveTv.favoritesLoadFailed' => 'ใŠๆฐ—ใซๅ…ฅใ‚Šใ‚’่ชญใฟ่พผใ‚ใพใ›ใ‚“ใงใ—ใŸใ€‚ๆŽฅ็ถšใ‚’็ขบ่ชใ—ใฆใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => '้€ฒ่กŒไธญใฎใ‚ปใƒƒใ‚ทใƒงใƒณใซๅ‚ๅŠ ', 'liveTv.watchFromStart' => ({required Object minutes}) => 'ๆœ€ๅˆใ‹ใ‚‰่ฆ–่ด๏ผˆ${minutes}ๅˆ†ๅ‰ใซ้–‹ๅง‹๏ผ‰', 'liveTv.watchLive' => 'ใƒฉใ‚คใƒ–ใง่ฆ–่ด', @@ -3154,11 +3170,11 @@ extension on TranslationsJa { 'watchTogether.participantLeft' => ({required Object name}) => '${name}ใŒ้€€ๅ‡บใ—ใพใ—ใŸ', 'watchTogether.participantPaused' => ({required Object name}) => '${name}ใŒไธ€ๆ™‚ๅœๆญขใ—ใพใ—ใŸ', 'watchTogether.participantResumed' => ({required Object name}) => '${name}ใŒๅ†้–‹ใ—ใพใ—ใŸ', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name}ใŒใ‚ทใƒผใ‚ฏใ—ใพใ—ใŸ', 'watchTogether.participantBuffering' => ({required Object name}) => '${name}ใŒใƒใƒƒใƒ•ใ‚กใƒชใƒณใ‚ฐไธญ', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} ใฏๅคใ„ใƒใƒผใ‚ธใƒงใƒณใฎใ‚ขใƒ—ใƒชใ‚’ไฝฟ็”จใ—ใฆใ„ใ‚‹ใŸใ‚ใ€ๅŒๆœŸใงใใพใ›ใ‚“', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => '${name} ใชใ—ใงๅ†้–‹', 'watchTogether.waitingForParticipants' => 'ไป–ใฎๅ‚ๅŠ ่€…ใฎ่ชญใฟ่พผใฟใ‚’ๅพ…ใฃใฆใ„ใพใ™...', 'watchTogether.waitingForName' => ({required Object name}) => '${name}ใ‚’ๅพ…ใฃใฆใ„ใพใ™...', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 1ad98e9f..3d90175b 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -52,6 +52,7 @@ class TranslationsKo extends Translations with BaseTranslations '์Šน์ธ ๋Œ€๊ธฐ ์ค‘โ€ฆ'; @override String get quickConnectCancel => '์ทจ์†Œ'; @override String get quickConnectExpired => 'Quick Connect๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsKo extends TranslationsSettingsEn { @override String get libraryDensity => '๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ‘œ์‹œ ๋ฐ€๋„'; @override String get compact => '์ข๊ฒŒ'; @override String get comfortable => '๋„“๊ฒŒ'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => '๋ณด๊ธฐ ๋ชจ๋“œ'; @override String get gridView => '๊ทธ๋ฆฌ๋“œ ๋ณด๊ธฐ'; @override String get listView => '๋ชฉ๋ก ๋ณด๊ธฐ'; @@ -311,7 +310,7 @@ class _TranslationsSettingsKo extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'ํ•จ๊ป˜ ๋ณด๊ธฐ ๋ฆด๋ ˆ์ด'; @override String get watchTogetherRelayDescription => '์‚ฌ์šฉ์ž ์ง€์ • ๋ฆด๋ ˆ์ด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋‘ ๊ฐ™์€ ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.'; @override String get watchTogetherRelayHint => 'https://my-relay.example.com'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => '์ถฉ๋Œ ๋ณด๊ณ '; @override String get crashReportingDescription => '์•ฑ ๊ฐœ์„ ์„ ์œ„ํ•ด ์ถฉ๋Œ ๋ณด๊ณ ์„œ ์ „์†ก'; @override String get debugLogging => '๋””๋ฒ„๊ทธ ๋กœ๊น…'; @@ -328,12 +327,10 @@ class _TranslationsSettingsKo extends TranslationsSettingsEn { @override String get exportSettings => '์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ'; @override String get exportSettingsDescription => '๊ธฐ๋ณธ ์„ค์ •์„ ํŒŒ์ผ๋กœ ์ €์žฅ'; @override String get exportSettingsSuccess => '์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ ์™„๋ฃŒ'; - @override String get exportSettingsFailed => '์„ค์ •์„ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค'; @override String get importSettings => '์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ'; @override String get importSettingsDescription => 'ํŒŒ์ผ์—์„œ ๊ธฐ๋ณธ ์„ค์ • ๋ณต์›'; @override String get importSettingsConfirm => 'ํ˜„์žฌ ์„ค์ •์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?'; @override String get importSettingsSuccess => '์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ ์™„๋ฃŒ'; - @override String get importSettingsFailed => '์„ค์ •์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค'; @override String get importSettingsInvalidFile => '์œ ํšจํ•œ Plezy ์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ ํŒŒ์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค'; @override String get importSettingsNoUser => '์„ค์ •์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์ „์— ๋กœ๊ทธ์ธํ•˜์„ธ์š”'; @override String get shortcutsReset => '๋‹จ์ถ•ํ‚ค๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค'; @@ -348,6 +345,7 @@ class _TranslationsSettingsKo extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => '๊ธฐ๊ฐ„์€ ${min}๊ณผ ${max} ${unit} ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค'; @override String shortcutAlreadyAssigned({required Object action}) => '๋‹จ์ถ•ํ‚ค๊ฐ€ ์ด๋ฏธ ${action}์— ํ• ๋‹น ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'; @override String shortcutUpdated({required Object action}) => '๋‹จ์ถ•ํ‚ค๊ฐ€ ${action}์— ๋Œ€ํ•ด ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => '์ž๋™ ๊ฑด๋„ˆ๋›ฐ๊ธฐ'; @override String get autoSkipIntro => '์ž๋™์œผ๋กœ ์˜คํ”„๋‹ ๊ฑด๋„ˆ๋›ฐ๊ธฐ'; @override String get autoSkipIntroDescription => '๋ช‡ ์ดˆ ํ›„ ์˜คํ”„๋‹์„ ์ž๋™์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ'; @@ -373,7 +371,7 @@ class _TranslationsSettingsKo extends TranslationsSettingsEn { @override String get downloadLocationChanged => '๋‹ค์šด๋กœ๋“œ ์œ„์น˜๊ฐ€ ๋ณ€๊ฒฝ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'; @override String get downloadLocationReset => '๋‹ค์šด๋กœ๋“œ ์œ„์น˜๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์„ค์ • ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'; @override String get downloadLocationInvalid => '์„ ํƒํ•œ ํด๋”์— ์“ฐ๊ธฐ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค'; - @override String get downloadLocationSelectError => 'ํด๋” ์„ ํƒ ์‹คํŒจ'; + @override String get downloadLocationPickerUnavailable => '์ด ๊ธฐ๊ธฐ์—์„œ๋Š” ํด๋”๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค'; @override String get downloadOnWifiOnly => 'WiFi ์—ฐ๊ฒฐ ์‹œ์—๋งŒ ๋‹ค์šด๋กœ๋“œ'; @override String get downloadOnWifiOnlyDescription => '์…€๋ฃฐ๋Ÿฌ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ ์‹œ ๋‹ค์šด๋กœ๋“œ ๋ถˆ๊ฐ€'; @override String get autoRemoveWatchedDownloads => '์‹œ์ฒญํ•œ ๋‹ค์šด๋กœ๋“œ ์ž๋™ ์‚ญ์ œ'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityKo extends TranslationsAccessibilityEn { @override String get hexColor => '16์ง„์ˆ˜ ์ƒ‰์ƒ'; @override String get expandText => 'ํ…์ŠคํŠธ ํŽผ์น˜๊ธฐ'; @override String get collapseText => 'ํ…์ŠคํŠธ ์ ‘๊ธฐ'; + @override String get alphabetNavigation => '์•ŒํŒŒ๋ฒณ ํƒ์ƒ‰'; + @override String get alphabetScrollHint => '์œ„์•„๋ž˜๋กœ ์Šค์™€์ดํ”„ํ•˜์—ฌ ๊ธ€์ž๋ณ„๋กœ ์ด๋™'; + @override String rowColumnPosition({required Object rowCount, required Object row, required Object columnCount, required Object column}) => '${rowCount}ํ–‰ ์ค‘ ${row}ํ–‰, ${columnCount}์—ด ์ค‘ ${column}์—ด'; + @override String rowPosition({required Object rowCount, required Object row}) => '${rowCount}ํ–‰ ์ค‘ ${row}ํ–‰'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsKo extends TranslationsTooltipsEn { @override String get markAsUnwatched => '์‹œ์ฒญ ์•ˆ ํ•จ์œผ๋กœ ํ‘œ์‹œ'; } +// Path: audioTracks +class _TranslationsAudioTracksKo extends TranslationsAudioTracksEn { + _TranslationsAudioTracksKo._(TranslationsKo root) : this._root = root, super.internal(root); + + final TranslationsKo _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => '์˜ค๋””์˜ค ํŠธ๋ž™ ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsKo extends TranslationsVideoControlsEn { _TranslationsVideoControlsKo._(TranslationsKo root) : this._root = root, super.internal(root); @@ -711,11 +723,11 @@ class _TranslationsMessagesKo extends TranslationsMessagesEn { @override String get streamInterrupted => '์ŠคํŠธ๋ฆผ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์žฌ์ƒ์„ ๋ˆ„๋ฅด๊ฑฐ๋‚˜ ํƒ์ƒ‰ํ•˜์—ฌ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.'; @override String get liveStreamInterrupted => '๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆผ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์žฌ์ƒ์„ ๋ˆŒ๋Ÿฌ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.'; @override String get fileInfoNotAvailable => 'ํŒŒ์ผ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'ํŒŒ์ผ ์ •๋ณด ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜: ${error}'; @override String get errorLoadingSeries => '์‹œ๋ฆฌ์ฆˆ ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜'; @override String get musicNotSupported => '์Œ์•… ์žฌ์ƒ ๋ฏธ์ง€์›'; @@ -866,6 +878,7 @@ class _TranslationsProfilesKo extends TranslationsProfilesEn { @override String get borrowExplain => '๋‹ค๋ฅธ ํ”„๋กœํ•„์˜ ์—ฐ๊ฒฐ์„ ๋นŒ๋ฆฝ๋‹ˆ๋‹ค. PIN์œผ๋กœ ๋ณดํ˜ธ๋œ ํ”„๋กœํ•„์—๋Š” PIN์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'; @override String get borrowEmpty => '์•„์ง ๋นŒ๋ฆด ๊ฒƒ์ด ์—†์Šต๋‹ˆ๋‹ค.'; @override String get borrowEmptySubtitle => '๋จผ์ € ๋‹ค๋ฅธ ํ”„๋กœํ•„์— Plex ๋˜๋Š” Jellyfin์„ ์—ฐ๊ฒฐํ•˜์„ธ์š”.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => '${displayName}์—์„œ'; @override String get borrowConnectionBorrowed => '์—ฐ๊ฒฐ์„ ๋นŒ๋ ธ์Šต๋‹ˆ๋‹ค.'; @override String get borrowFailed => '์—ฐ๊ฒฐ์„ ๋นŒ๋ฆฌ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.'; @@ -1148,7 +1161,7 @@ class _TranslationsLiveTvKo extends TranslationsLiveTvEn { @override String get favorites => '์ฆ๊ฒจ์ฐพ๊ธฐ'; @override String get reorderFavorites => '์ฆ๊ฒจ์ฐพ๊ธฐ ์ˆœ์„œ ๋ณ€๊ฒฝ'; @override String get favoritesLoadFailed => '์ฆ๊ฒจ์ฐพ๊ธฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => '์ง„ํ–‰ ์ค‘์ธ ์„ธ์…˜ ์ฐธ์—ฌ'; @override String watchFromStart({required Object minutes}) => '์ฒ˜์Œ๋ถ€ํ„ฐ ์‹œ์ฒญ (${minutes}๋ถ„ ์ „ ์‹œ์ž‘)'; @override String get watchLive => '์‹ค์‹œ๊ฐ„ ์‹œ์ฒญ'; @@ -2146,7 +2159,7 @@ extension on TranslationsKo { 'auth.quickConnectWaiting' => '์Šน์ธ ๋Œ€๊ธฐ ์ค‘โ€ฆ', 'auth.quickConnectCancel' => '์ทจ์†Œ', 'auth.quickConnectExpired' => 'Quick Connect๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => '์ทจ์†Œ', 'common.save' => '์ €์žฅ', 'common.close' => '๋‹ซ๊ธฐ', @@ -2230,8 +2243,6 @@ extension on TranslationsKo { 'settings.libraryDensity' => '๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ‘œ์‹œ ๋ฐ€๋„', 'settings.compact' => '์ข๊ฒŒ', 'settings.comfortable' => '๋„“๊ฒŒ', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => '๋ณด๊ธฐ ๋ชจ๋“œ', 'settings.gridView' => '๊ทธ๋ฆฌ๋“œ ๋ณด๊ธฐ', 'settings.listView' => '๋ชฉ๋ก ๋ณด๊ธฐ', @@ -2301,7 +2312,7 @@ extension on TranslationsKo { 'settings.watchTogetherRelay' => 'ํ•จ๊ป˜ ๋ณด๊ธฐ ๋ฆด๋ ˆ์ด', 'settings.watchTogetherRelayDescription' => '์‚ฌ์šฉ์ž ์ง€์ • ๋ฆด๋ ˆ์ด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋‘ ๊ฐ™์€ ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.', 'settings.watchTogetherRelayHint' => 'https://my-relay.example.com', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => '์ถฉ๋Œ ๋ณด๊ณ ', 'settings.crashReportingDescription' => '์•ฑ ๊ฐœ์„ ์„ ์œ„ํ•ด ์ถฉ๋Œ ๋ณด๊ณ ์„œ ์ „์†ก', 'settings.debugLogging' => '๋””๋ฒ„๊ทธ ๋กœ๊น…', @@ -2318,12 +2329,10 @@ extension on TranslationsKo { 'settings.exportSettings' => '์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ', 'settings.exportSettingsDescription' => '๊ธฐ๋ณธ ์„ค์ •์„ ํŒŒ์ผ๋กœ ์ €์žฅ', 'settings.exportSettingsSuccess' => '์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ ์™„๋ฃŒ', - 'settings.exportSettingsFailed' => '์„ค์ •์„ ๋‚ด๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค', 'settings.importSettings' => '์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ', 'settings.importSettingsDescription' => 'ํŒŒ์ผ์—์„œ ๊ธฐ๋ณธ ์„ค์ • ๋ณต์›', 'settings.importSettingsConfirm' => 'ํ˜„์žฌ ์„ค์ •์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?', 'settings.importSettingsSuccess' => '์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ ์™„๋ฃŒ', - 'settings.importSettingsFailed' => '์„ค์ •์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค', 'settings.importSettingsInvalidFile' => '์œ ํšจํ•œ Plezy ์„ค์ • ๋‚ด๋ณด๋‚ด๊ธฐ ํŒŒ์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค', 'settings.importSettingsNoUser' => '์„ค์ •์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์ „์— ๋กœ๊ทธ์ธํ•˜์„ธ์š”', 'settings.shortcutsReset' => '๋‹จ์ถ•ํ‚ค๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค', @@ -2338,6 +2347,7 @@ extension on TranslationsKo { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => '๊ธฐ๊ฐ„์€ ${min}๊ณผ ${max} ${unit} ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => '๋‹จ์ถ•ํ‚ค๊ฐ€ ์ด๋ฏธ ${action}์— ํ• ๋‹น ๋˜์—ˆ์Šต๋‹ˆ๋‹ค', 'settings.shortcutUpdated' => ({required Object action}) => '๋‹จ์ถ•ํ‚ค๊ฐ€ ${action}์— ๋Œ€ํ•ด ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => '์ž๋™ ๊ฑด๋„ˆ๋›ฐ๊ธฐ', 'settings.autoSkipIntro' => '์ž๋™์œผ๋กœ ์˜คํ”„๋‹ ๊ฑด๋„ˆ๋›ฐ๊ธฐ', 'settings.autoSkipIntroDescription' => '๋ช‡ ์ดˆ ํ›„ ์˜คํ”„๋‹์„ ์ž๋™์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ', @@ -2363,7 +2373,7 @@ extension on TranslationsKo { 'settings.downloadLocationChanged' => '๋‹ค์šด๋กœ๋“œ ์œ„์น˜๊ฐ€ ๋ณ€๊ฒฝ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค', 'settings.downloadLocationReset' => '๋‹ค์šด๋กœ๋“œ ์œ„์น˜๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์žฌ์„ค์ • ๋˜์—ˆ์Šต๋‹ˆ๋‹ค', 'settings.downloadLocationInvalid' => '์„ ํƒํ•œ ํด๋”์— ์“ฐ๊ธฐ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค', - 'settings.downloadLocationSelectError' => 'ํด๋” ์„ ํƒ ์‹คํŒจ', + 'settings.downloadLocationPickerUnavailable' => '์ด ๊ธฐ๊ธฐ์—์„œ๋Š” ํด๋”๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค', 'settings.downloadOnWifiOnly' => 'WiFi ์—ฐ๊ฒฐ ์‹œ์—๋งŒ ๋‹ค์šด๋กœ๋“œ', 'settings.downloadOnWifiOnlyDescription' => '์…€๋ฃฐ๋Ÿฌ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ ์‹œ ๋‹ค์šด๋กœ๋“œ ๋ถˆ๊ฐ€', 'settings.autoRemoveWatchedDownloads' => '์‹œ์ฒญํ•œ ๋‹ค์šด๋กœ๋“œ ์ž๋™ ์‚ญ์ œ', @@ -2551,10 +2561,15 @@ extension on TranslationsKo { 'accessibility.hexColor' => '16์ง„์ˆ˜ ์ƒ‰์ƒ', 'accessibility.expandText' => 'ํ…์ŠคํŠธ ํŽผ์น˜๊ธฐ', 'accessibility.collapseText' => 'ํ…์ŠคํŠธ ์ ‘๊ธฐ', + 'accessibility.alphabetNavigation' => '์•ŒํŒŒ๋ฒณ ํƒ์ƒ‰', + 'accessibility.alphabetScrollHint' => '์œ„์•„๋ž˜๋กœ ์Šค์™€์ดํ”„ํ•˜์—ฌ ๊ธ€์ž๋ณ„๋กœ ์ด๋™', + 'accessibility.rowColumnPosition' => ({required Object rowCount, required Object row, required Object columnCount, required Object column}) => '${rowCount}ํ–‰ ์ค‘ ${row}ํ–‰, ${columnCount}์—ด ์ค‘ ${column}์—ด', + 'accessibility.rowPosition' => ({required Object rowCount, required Object row}) => '${rowCount}ํ–‰ ์ค‘ ${row}ํ–‰', 'tooltips.shufflePlay' => '๋ฌด์ž‘์œ„ ์žฌ์ƒ', 'tooltips.playTrailer' => '์˜ˆ๊ณ ํŽธ ์žฌ์ƒ', 'tooltips.markAsWatched' => '์‹œ์ฒญ ์™„๋ฃŒ๋กœ ํ‘œ์‹œ', 'tooltips.markAsUnwatched' => '์‹œ์ฒญ ์•ˆ ํ•จ์œผ๋กœ ํ‘œ์‹œ', + 'audioTracks.track' => ({required Object n}) => '์˜ค๋””์˜ค ํŠธ๋ž™ ${n}', 'videoControls.audioLabel' => '์˜ค๋””์˜ค', 'videoControls.subtitlesLabel' => '์ž๋ง‰', 'videoControls.resetToZero' => '0ms๋กœ ์žฌ์„ค์ •', @@ -2641,20 +2656,20 @@ extension on TranslationsKo { 'messages.markedAsUnwatched' => '์‹œ์ฒญ ์•ˆ ํ•จ์œผ๋กœ ํ‘œ์‹œ๋จ', 'messages.markedAsWatchedOffline' => '์‹œ์ฒญ ์™„๋ฃŒ๋กœ ํ‘œ์‹œ๋จ (์—ฐ๊ฒฐ ์‹œ ๋™๊ธฐํ™”๋จ)', 'messages.markedAsUnwatchedOffline' => '๋ฏธ์‹œ์ฒญ์œผ๋กœ ํ‘œ์‹œ๋จ (์—ฐ๊ฒฐ ์‹œ ๋™๊ธฐํ™”๋จ)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => '์ž๋™ ์‚ญ์ œ๋จ: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ko'))(n, other: '์‹œ์ฒญํ•œ ๋‹ค์šด๋กœ๋“œ ${n}๊ฐœ๋ฅผ ์ž๋™ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => '์ž๋™ ์‚ญ์ œ๋จ: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ko'))(n, other: '์‹œ์ฒญํ•œ ๋‹ค์šด๋กœ๋“œ ${n}๊ฐœ๋ฅผ ์ž๋™ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค', ), 'messages.removedFromContinueWatching' => '๊ณ„์† ์‹œ์ฒญ ๋ชฉ๋ก์—์„œ ์ œ๊ฑฐ๋จ', 'messages.errorLoading' => ({required Object error}) => '์˜ค๋ฅ˜: ${error}', 'messages.streamInterrupted' => '์ŠคํŠธ๋ฆผ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์žฌ์ƒ์„ ๋ˆ„๋ฅด๊ฑฐ๋‚˜ ํƒ์ƒ‰ํ•˜์—ฌ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.', 'messages.liveStreamInterrupted' => '๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆผ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์žฌ์ƒ์„ ๋ˆŒ๋Ÿฌ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.', 'messages.fileInfoNotAvailable' => 'ํŒŒ์ผ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'ํŒŒ์ผ ์ •๋ณด ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜: ${error}', 'messages.errorLoadingSeries' => '์‹œ๋ฆฌ์ฆˆ ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜', 'messages.musicNotSupported' => '์Œ์•… ์žฌ์ƒ ๋ฏธ์ง€์›', @@ -2769,6 +2784,7 @@ extension on TranslationsKo { 'profiles.borrowExplain' => '๋‹ค๋ฅธ ํ”„๋กœํ•„์˜ ์—ฐ๊ฒฐ์„ ๋นŒ๋ฆฝ๋‹ˆ๋‹ค. PIN์œผ๋กœ ๋ณดํ˜ธ๋œ ํ”„๋กœํ•„์—๋Š” PIN์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.', 'profiles.borrowEmpty' => '์•„์ง ๋นŒ๋ฆด ๊ฒƒ์ด ์—†์Šต๋‹ˆ๋‹ค.', 'profiles.borrowEmptySubtitle' => '๋จผ์ € ๋‹ค๋ฅธ ํ”„๋กœํ•„์— Plex ๋˜๋Š” Jellyfin์„ ์—ฐ๊ฒฐํ•˜์„ธ์š”.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => '${displayName}์—์„œ', 'profiles.borrowConnectionBorrowed' => '์—ฐ๊ฒฐ์„ ๋นŒ๋ ธ์Šต๋‹ˆ๋‹ค.', 'profiles.borrowFailed' => '์—ฐ๊ฒฐ์„ ๋นŒ๋ฆฌ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.', @@ -2994,7 +3010,7 @@ extension on TranslationsKo { 'liveTv.favorites' => '์ฆ๊ฒจ์ฐพ๊ธฐ', 'liveTv.reorderFavorites' => '์ฆ๊ฒจ์ฐพ๊ธฐ ์ˆœ์„œ ๋ณ€๊ฒฝ', 'liveTv.favoritesLoadFailed' => '์ฆ๊ฒจ์ฐพ๊ธฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => '์ง„ํ–‰ ์ค‘์ธ ์„ธ์…˜ ์ฐธ์—ฌ', 'liveTv.watchFromStart' => ({required Object minutes}) => '์ฒ˜์Œ๋ถ€ํ„ฐ ์‹œ์ฒญ (${minutes}๋ถ„ ์ „ ์‹œ์ž‘)', 'liveTv.watchLive' => '์‹ค์‹œ๊ฐ„ ์‹œ์ฒญ', @@ -3154,11 +3170,11 @@ extension on TranslationsKo { 'watchTogether.participantLeft' => ({required Object name}) => '${name}๋‹˜์ด ๋‚˜๊ฐ”์Šต๋‹ˆ๋‹ค', 'watchTogether.participantPaused' => ({required Object name}) => '${name}๋‹˜์ด ์ผ์‹œ์ •์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค', 'watchTogether.participantResumed' => ({required Object name}) => '${name}๋‹˜์ด ์žฌ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name}๋‹˜์ด ํƒ์ƒ‰ํ–ˆ์Šต๋‹ˆ๋‹ค', 'watchTogether.participantBuffering' => ({required Object name}) => '${name}๋‹˜์ด ๋ฒ„ํผ๋ง ์ค‘์ž…๋‹ˆ๋‹ค', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name}๋‹˜์ด ์ด์ „ ๋ฒ„์ „์˜ ์•ฑ์„ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค โ€” ๋™๊ธฐํ™”๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => '${name}๋‹˜ ์—†์ด ์žฌ์ƒ์„ ์žฌ๊ฐœํ•ฉ๋‹ˆ๋‹ค', 'watchTogether.waitingForParticipants' => '๋‹ค๋ฅธ ์ฐธ๊ฐ€์ž์˜ ๋กœ๋”ฉ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘...', 'watchTogether.waitingForName' => ({required Object name}) => '${name}๋‹˜์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘...', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index 2681dce7..6dec8ec6 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -52,6 +52,7 @@ class TranslationsNb extends Translations with BaseTranslations 'Venter pรฅ godkjenningโ€ฆ'; @override String get quickConnectCancel => 'Avbryt'; @override String get quickConnectExpired => 'Quick Connect er utlรธpt. Prรธv igjen.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsNb extends TranslationsSettingsEn { @override String get libraryDensity => 'Bibliotekets tetthet'; @override String get compact => 'Kompakt'; @override String get comfortable => 'Komfortabel'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Visningsmodus'; @override String get gridView => 'Rutenett'; @override String get listView => 'Liste'; @@ -311,7 +310,7 @@ class _TranslationsSettingsNb extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Se Sammen-relay'; @override String get watchTogetherRelayDescription => 'Angi en egendefinert relay. Alle mรฅ bruke samme server.'; @override String get watchTogetherRelayHint => 'https://min-relay.eksempel.no'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Krasjrapportering'; @override String get crashReportingDescription => 'Send krasjrapporter for รฅ hjelpe med รฅ forbedre appen'; @override String get debugLogging => 'Feilsรธkingslogging'; @@ -328,12 +327,10 @@ class _TranslationsSettingsNb extends TranslationsSettingsEn { @override String get exportSettings => 'Eksporter innstillinger'; @override String get exportSettingsDescription => 'Lagre innstillingene i en fil'; @override String get exportSettingsSuccess => 'Innstillinger eksportert'; - @override String get exportSettingsFailed => 'Kunne ikke eksportere innstillinger'; @override String get importSettings => 'Importer innstillinger'; @override String get importSettingsDescription => 'Gjenopprett innstillinger fra en fil'; @override String get importSettingsConfirm => 'Dette vil erstatte nรฅvรฆrende innstillinger. Fortsette?'; @override String get importSettingsSuccess => 'Innstillinger importert'; - @override String get importSettingsFailed => 'Kunne ikke importere innstillinger'; @override String get importSettingsInvalidFile => 'Denne filen er ikke en gyldig Plezy-innstillingseksport'; @override String get importSettingsNoUser => 'Logg inn fรธr import av innstillinger'; @override String get shortcutsReset => 'Snarveier tilbakestilt til standard'; @@ -348,6 +345,7 @@ class _TranslationsSettingsNb extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Varigheten mรฅ vรฆre mellom ${min} og ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'Snarvei allerede tilordnet til ${action}'; @override String shortcutUpdated({required Object action}) => 'Snarvei oppdatert for ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Automatisk hopp'; @override String get autoSkipIntro => 'Hopp over intro automatisk'; @override String get autoSkipIntroDescription => 'Hopp automatisk over intromarkรธrer etter noen sekunder'; @@ -373,7 +371,7 @@ class _TranslationsSettingsNb extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Nedlastingsplassering endret'; @override String get downloadLocationReset => 'Nedlastingsplassering tilbakestilt til standard'; @override String get downloadLocationInvalid => 'Valgt mappe er ikke skrivbar'; - @override String get downloadLocationSelectError => 'Kunne ikke velge mappe'; + @override String get downloadLocationPickerUnavailable => 'Mappevalg er ikke tilgjengelig pรฅ denne enheten'; @override String get downloadOnWifiOnly => 'Last ned kun pรฅ WiFi'; @override String get downloadOnWifiOnlyDescription => 'Forhindre nedlastinger pรฅ mobildata'; @override String get autoRemoveWatchedDownloads => 'Fjern sette nedlastinger automatisk'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityNb extends TranslationsAccessibilityEn { @override String get hexColor => 'Heksadesimal farge'; @override String get expandText => 'Utvid tekst'; @override String get collapseText => 'Fold sammen tekst'; + @override String get alphabetNavigation => 'Alfabetisk navigasjon'; + @override String get alphabetScrollHint => 'Sveip opp eller ned for รฅ flytte รฉn bokstav'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Rad ${row} av ${rowCount}, kolonne ${column} av ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Rad ${row} av ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsNb extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Merk som usett'; } +// Path: audioTracks +class _TranslationsAudioTracksNb extends TranslationsAudioTracksEn { + _TranslationsAudioTracksNb._(TranslationsNb root) : this._root = root, super.internal(root); + + final TranslationsNb _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Lydspor ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsNb extends TranslationsVideoControlsEn { _TranslationsVideoControlsNb._(TranslationsNb root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesNb extends TranslationsMessagesEn { @override String get streamInterrupted => 'Avspillingen ble avbrutt. Trykk pรฅ Spill av eller spol for รฅ prรธve pรฅ nytt.'; @override String get liveStreamInterrupted => 'Direktesendingen ble avbrutt. Trykk pรฅ Spill av for รฅ prรธve pรฅ nytt.'; @override String get fileInfoNotAvailable => 'Filinformasjon ikke tilgjengelig'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Feil ved lasting av filinformasjon: ${error}'; @override String get errorLoadingSeries => 'Feil ved lasting av serie'; @override String get musicNotSupported => 'Musikkavspilling stรธttes ikke ennรฅ'; @@ -867,6 +879,7 @@ class _TranslationsProfilesNb extends TranslationsProfilesEn { @override String get borrowExplain => 'Lรฅn en annen profils tilkobling. PIN-beskyttede profiler krever PIN.'; @override String get borrowEmpty => 'Ingenting รฅ lรฅne enda.'; @override String get borrowEmptySubtitle => 'Koble Plex eller Jellyfin til en annen profil fรธrst.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'Fra ${displayName}'; @override String get borrowConnectionBorrowed => 'Tilkobling lรฅnt.'; @override String get borrowFailed => 'Kunne ikke lรฅne tilkoblingen.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvNb extends TranslationsLiveTvEn { @override String get favorites => 'Favoritter'; @override String get reorderFavorites => 'Endre rekkefรธlge pรฅ favoritter'; @override String get favoritesLoadFailed => 'Kunne ikke laste inn favoritter. Kontroller tilkoblingen og prรธv pรฅ nytt.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Bli med i pรฅgรฅende รธkt'; @override String watchFromStart({required Object minutes}) => 'Se fra starten (${minutes} min siden)'; @override String get watchLive => 'Se direkte'; @@ -2149,7 +2162,7 @@ extension on TranslationsNb { 'auth.quickConnectWaiting' => 'Venter pรฅ godkjenningโ€ฆ', 'auth.quickConnectCancel' => 'Avbryt', 'auth.quickConnectExpired' => 'Quick Connect er utlรธpt. Prรธv igjen.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Avbryt', 'common.save' => 'Lagre', 'common.close' => 'Lukk', @@ -2233,8 +2246,6 @@ extension on TranslationsNb { 'settings.libraryDensity' => 'Bibliotekets tetthet', 'settings.compact' => 'Kompakt', 'settings.comfortable' => 'Komfortabel', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Visningsmodus', 'settings.gridView' => 'Rutenett', 'settings.listView' => 'Liste', @@ -2304,7 +2315,7 @@ extension on TranslationsNb { 'settings.watchTogetherRelay' => 'Se Sammen-relay', 'settings.watchTogetherRelayDescription' => 'Angi en egendefinert relay. Alle mรฅ bruke samme server.', 'settings.watchTogetherRelayHint' => 'https://min-relay.eksempel.no', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Krasjrapportering', 'settings.crashReportingDescription' => 'Send krasjrapporter for รฅ hjelpe med รฅ forbedre appen', 'settings.debugLogging' => 'Feilsรธkingslogging', @@ -2321,12 +2332,10 @@ extension on TranslationsNb { 'settings.exportSettings' => 'Eksporter innstillinger', 'settings.exportSettingsDescription' => 'Lagre innstillingene i en fil', 'settings.exportSettingsSuccess' => 'Innstillinger eksportert', - 'settings.exportSettingsFailed' => 'Kunne ikke eksportere innstillinger', 'settings.importSettings' => 'Importer innstillinger', 'settings.importSettingsDescription' => 'Gjenopprett innstillinger fra en fil', 'settings.importSettingsConfirm' => 'Dette vil erstatte nรฅvรฆrende innstillinger. Fortsette?', 'settings.importSettingsSuccess' => 'Innstillinger importert', - 'settings.importSettingsFailed' => 'Kunne ikke importere innstillinger', 'settings.importSettingsInvalidFile' => 'Denne filen er ikke en gyldig Plezy-innstillingseksport', 'settings.importSettingsNoUser' => 'Logg inn fรธr import av innstillinger', 'settings.shortcutsReset' => 'Snarveier tilbakestilt til standard', @@ -2341,6 +2350,7 @@ extension on TranslationsNb { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'Varigheten mรฅ vรฆre mellom ${min} og ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Snarvei allerede tilordnet til ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Snarvei oppdatert for ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Automatisk hopp', 'settings.autoSkipIntro' => 'Hopp over intro automatisk', 'settings.autoSkipIntroDescription' => 'Hopp automatisk over intromarkรธrer etter noen sekunder', @@ -2366,7 +2376,7 @@ extension on TranslationsNb { 'settings.downloadLocationChanged' => 'Nedlastingsplassering endret', 'settings.downloadLocationReset' => 'Nedlastingsplassering tilbakestilt til standard', 'settings.downloadLocationInvalid' => 'Valgt mappe er ikke skrivbar', - 'settings.downloadLocationSelectError' => 'Kunne ikke velge mappe', + 'settings.downloadLocationPickerUnavailable' => 'Mappevalg er ikke tilgjengelig pรฅ denne enheten', 'settings.downloadOnWifiOnly' => 'Last ned kun pรฅ WiFi', 'settings.downloadOnWifiOnlyDescription' => 'Forhindre nedlastinger pรฅ mobildata', 'settings.autoRemoveWatchedDownloads' => 'Fjern sette nedlastinger automatisk', @@ -2554,10 +2564,15 @@ extension on TranslationsNb { 'accessibility.hexColor' => 'Heksadesimal farge', 'accessibility.expandText' => 'Utvid tekst', 'accessibility.collapseText' => 'Fold sammen tekst', + 'accessibility.alphabetNavigation' => 'Alfabetisk navigasjon', + 'accessibility.alphabetScrollHint' => 'Sveip opp eller ned for รฅ flytte รฉn bokstav', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Rad ${row} av ${rowCount}, kolonne ${column} av ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Rad ${row} av ${rowCount}', 'tooltips.shufflePlay' => 'Tilfeldig avspilling', 'tooltips.playTrailer' => 'Spill trailer', 'tooltips.markAsWatched' => 'Merk som sett', 'tooltips.markAsUnwatched' => 'Merk som usett', + 'audioTracks.track' => ({required Object n}) => 'Lydspor ${n}', 'videoControls.audioLabel' => 'Lyd', 'videoControls.subtitlesLabel' => 'Undertekster', 'videoControls.resetToZero' => 'Tilbakestill til 0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsNb { 'messages.markedAsUnwatched' => 'Merket som usett', 'messages.markedAsWatchedOffline' => 'Merket som sett (synkroniseres nรฅr tilkoblet)', 'messages.markedAsUnwatchedOffline' => 'Merket som usett (synkroniseres nรฅr tilkoblet)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatisk fjernet: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('nb'))(n, one: 'Fjernet automatisk ${n} sett nedlasting', other: 'Fjernet automatisk ${n} sette nedlastinger', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatisk fjernet: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('nb'))(n, one: 'Fjernet automatisk ${n} sett nedlasting', other: 'Fjernet automatisk ${n} sette nedlastinger', ), 'messages.removedFromContinueWatching' => 'Fjernet fra Fortsett รฅ se', 'messages.errorLoading' => ({required Object error}) => 'Feil: ${error}', 'messages.streamInterrupted' => 'Avspillingen ble avbrutt. Trykk pรฅ Spill av eller spol for รฅ prรธve pรฅ nytt.', 'messages.liveStreamInterrupted' => 'Direktesendingen ble avbrutt. Trykk pรฅ Spill av for รฅ prรธve pรฅ nytt.', 'messages.fileInfoNotAvailable' => 'Filinformasjon ikke tilgjengelig', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Feil ved lasting av filinformasjon: ${error}', 'messages.errorLoadingSeries' => 'Feil ved lasting av serie', 'messages.musicNotSupported' => 'Musikkavspilling stรธttes ikke ennรฅ', @@ -2772,6 +2787,7 @@ extension on TranslationsNb { 'profiles.borrowExplain' => 'Lรฅn en annen profils tilkobling. PIN-beskyttede profiler krever PIN.', 'profiles.borrowEmpty' => 'Ingenting รฅ lรฅne enda.', 'profiles.borrowEmptySubtitle' => 'Koble Plex eller Jellyfin til en annen profil fรธrst.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'Fra ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Tilkobling lรฅnt.', 'profiles.borrowFailed' => 'Kunne ikke lรฅne tilkoblingen.', @@ -2997,7 +3013,7 @@ extension on TranslationsNb { 'liveTv.favorites' => 'Favoritter', 'liveTv.reorderFavorites' => 'Endre rekkefรธlge pรฅ favoritter', 'liveTv.favoritesLoadFailed' => 'Kunne ikke laste inn favoritter. Kontroller tilkoblingen og prรธv pรฅ nytt.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Bli med i pรฅgรฅende รธkt', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Se fra starten (${minutes} min siden)', 'liveTv.watchLive' => 'Se direkte', @@ -3157,11 +3173,11 @@ extension on TranslationsNb { 'watchTogether.participantLeft' => ({required Object name}) => '${name} forlot', 'watchTogether.participantPaused' => ({required Object name}) => '${name} pauset', 'watchTogether.participantResumed' => ({required Object name}) => '${name} gjenopptok', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} spolet', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} buffrer', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} bruker en eldre appversjon โ€” synkronisering er ikke tilgjengelig', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Fortsetter uten ${name}', 'watchTogether.waitingForParticipants' => 'Venter pรฅ at andre laster inn...', 'watchTogether.waitingForName' => ({required Object name}) => 'Venter pรฅ ${name}...', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index 694031eb..001288fd 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -52,6 +52,7 @@ class TranslationsNl extends Translations with BaseTranslations 'Wachten op goedkeuringโ€ฆ'; @override String get quickConnectCancel => 'Annuleren'; @override String get quickConnectExpired => 'Quick Connect is verlopen. Probeer opnieuw.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsNl extends TranslationsSettingsEn { @override String get libraryDensity => 'Bibliotheek dichtheid'; @override String get compact => 'Compact'; @override String get comfortable => 'Comfortabel'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Weergavemodus'; @override String get gridView => 'Raster'; @override String get listView => 'Lijst'; @@ -311,7 +310,7 @@ class _TranslationsSettingsNl extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Samen Kijken Relay'; @override String get watchTogetherRelayDescription => 'Stel een aangepaste relay in. Iedereen moet dezelfde server gebruiken.'; @override String get watchTogetherRelayHint => 'https://mijn-relay.voorbeeld.nl'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Crashrapportage'; @override String get crashReportingDescription => 'Crashrapporten verzenden om de app te verbeteren'; @override String get debugLogging => 'Debug logging'; @@ -328,12 +327,10 @@ class _TranslationsSettingsNl extends TranslationsSettingsEn { @override String get exportSettings => 'Instellingen exporteren'; @override String get exportSettingsDescription => 'Sla je voorkeuren op in een bestand'; @override String get exportSettingsSuccess => 'Instellingen geรซxporteerd'; - @override String get exportSettingsFailed => 'Kon instellingen niet exporteren'; @override String get importSettings => 'Instellingen importeren'; @override String get importSettingsDescription => 'Voorkeuren herstellen vanuit een bestand'; @override String get importSettingsConfirm => 'Hiermee worden je huidige instellingen vervangen. Doorgaan?'; @override String get importSettingsSuccess => 'Instellingen geรฏmporteerd'; - @override String get importSettingsFailed => 'Kon instellingen niet importeren'; @override String get importSettingsInvalidFile => 'Dit bestand is geen geldige Plezy-export'; @override String get importSettingsNoUser => 'Meld je aan voordat je instellingen importeert'; @override String get shortcutsReset => 'Sneltoetsen gereset naar standaard'; @@ -348,6 +345,7 @@ class _TranslationsSettingsNl extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Duur moet tussen ${min} en ${max} ${unit} zijn'; @override String shortcutAlreadyAssigned({required Object action}) => 'Sneltoets al toegewezen aan ${action}'; @override String shortcutUpdated({required Object action}) => 'Sneltoets bijgewerkt voor ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Automatisch Overslaan'; @override String get autoSkipIntro => 'Intro Automatisch Overslaan'; @override String get autoSkipIntroDescription => 'Intro-markeringen na enkele seconden automatisch overslaan'; @@ -373,7 +371,7 @@ class _TranslationsSettingsNl extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Downloadlocatie gewijzigd'; @override String get downloadLocationReset => 'Downloadlocatie hersteld naar standaard'; @override String get downloadLocationInvalid => 'Geselecteerde map is niet beschrijfbaar'; - @override String get downloadLocationSelectError => 'Kan map niet selecteren'; + @override String get downloadLocationPickerUnavailable => 'Mapselectie is niet beschikbaar op dit apparaat'; @override String get downloadOnWifiOnly => 'Alleen via WiFi downloaden'; @override String get downloadOnWifiOnlyDescription => 'Voorkom downloads bij gebruik van mobiele data'; @override String get autoRemoveWatchedDownloads => 'Bekeken downloads automatisch verwijderen'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityNl extends TranslationsAccessibilityEn { @override String get hexColor => 'Hexkleur'; @override String get expandText => 'Tekst uitvouwen'; @override String get collapseText => 'Tekst samenvouwen'; + @override String get alphabetNavigation => 'Alfabetische navigatie'; + @override String get alphabetScrollHint => 'Veeg omhoog of omlaag om per letter te bewegen'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Rij ${row} van ${rowCount}, kolom ${column} van ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Rij ${row} van ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsNl extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Markeer als ongekeken'; } +// Path: audioTracks +class _TranslationsAudioTracksNl extends TranslationsAudioTracksEn { + _TranslationsAudioTracksNl._(TranslationsNl root) : this._root = root, super.internal(root); + + final TranslationsNl _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Audiospoor ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsNl extends TranslationsVideoControlsEn { _TranslationsVideoControlsNl._(TranslationsNl root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesNl extends TranslationsMessagesEn { @override String get streamInterrupted => 'De stream is onderbroken. Druk op afspelen of spoel om het opnieuw te proberen.'; @override String get liveStreamInterrupted => 'De livestream is onderbroken. Druk op afspelen om het opnieuw te proberen.'; @override String get fileInfoNotAvailable => 'Bestand informatie niet beschikbaar'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Fout bij laden bestand info: ${error}'; @override String get errorLoadingSeries => 'Fout bij laden serie'; @override String get musicNotSupported => 'Muziek afspelen wordt nog niet ondersteund'; @@ -867,6 +879,7 @@ class _TranslationsProfilesNl extends TranslationsProfilesEn { @override String get borrowExplain => 'Leen de verbinding van een ander profiel. PIN-beveiligde profielen vereisen een PIN.'; @override String get borrowEmpty => 'Nog niets te lenen.'; @override String get borrowEmptySubtitle => 'Verbind Plex of Jellyfin eerst met een ander profiel.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'Van ${displayName}'; @override String get borrowConnectionBorrowed => 'Verbinding geleend.'; @override String get borrowFailed => 'Kan verbinding niet lenen.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvNl extends TranslationsLiveTvEn { @override String get favorites => 'Favorieten'; @override String get reorderFavorites => 'Favorieten herordenen'; @override String get favoritesLoadFailed => 'Favorieten konden niet worden geladen. Controleer je verbinding en probeer het opnieuw.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Deelnemen aan lopende sessie'; @override String watchFromStart({required Object minutes}) => 'Kijk vanaf het begin (${minutes} min geleden)'; @override String get watchLive => 'Live kijken'; @@ -2149,7 +2162,7 @@ extension on TranslationsNl { 'auth.quickConnectWaiting' => 'Wachten op goedkeuringโ€ฆ', 'auth.quickConnectCancel' => 'Annuleren', 'auth.quickConnectExpired' => 'Quick Connect is verlopen. Probeer opnieuw.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Annuleren', 'common.save' => 'Opslaan', 'common.close' => 'Sluiten', @@ -2233,8 +2246,6 @@ extension on TranslationsNl { 'settings.libraryDensity' => 'Bibliotheek dichtheid', 'settings.compact' => 'Compact', 'settings.comfortable' => 'Comfortabel', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Weergavemodus', 'settings.gridView' => 'Raster', 'settings.listView' => 'Lijst', @@ -2304,7 +2315,7 @@ extension on TranslationsNl { 'settings.watchTogetherRelay' => 'Samen Kijken Relay', 'settings.watchTogetherRelayDescription' => 'Stel een aangepaste relay in. Iedereen moet dezelfde server gebruiken.', 'settings.watchTogetherRelayHint' => 'https://mijn-relay.voorbeeld.nl', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Crashrapportage', 'settings.crashReportingDescription' => 'Crashrapporten verzenden om de app te verbeteren', 'settings.debugLogging' => 'Debug logging', @@ -2321,12 +2332,10 @@ extension on TranslationsNl { 'settings.exportSettings' => 'Instellingen exporteren', 'settings.exportSettingsDescription' => 'Sla je voorkeuren op in een bestand', 'settings.exportSettingsSuccess' => 'Instellingen geรซxporteerd', - 'settings.exportSettingsFailed' => 'Kon instellingen niet exporteren', 'settings.importSettings' => 'Instellingen importeren', 'settings.importSettingsDescription' => 'Voorkeuren herstellen vanuit een bestand', 'settings.importSettingsConfirm' => 'Hiermee worden je huidige instellingen vervangen. Doorgaan?', 'settings.importSettingsSuccess' => 'Instellingen geรฏmporteerd', - 'settings.importSettingsFailed' => 'Kon instellingen niet importeren', 'settings.importSettingsInvalidFile' => 'Dit bestand is geen geldige Plezy-export', 'settings.importSettingsNoUser' => 'Meld je aan voordat je instellingen importeert', 'settings.shortcutsReset' => 'Sneltoetsen gereset naar standaard', @@ -2341,6 +2350,7 @@ extension on TranslationsNl { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'Duur moet tussen ${min} en ${max} ${unit} zijn', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Sneltoets al toegewezen aan ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Sneltoets bijgewerkt voor ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Automatisch Overslaan', 'settings.autoSkipIntro' => 'Intro Automatisch Overslaan', 'settings.autoSkipIntroDescription' => 'Intro-markeringen na enkele seconden automatisch overslaan', @@ -2366,7 +2376,7 @@ extension on TranslationsNl { 'settings.downloadLocationChanged' => 'Downloadlocatie gewijzigd', 'settings.downloadLocationReset' => 'Downloadlocatie hersteld naar standaard', 'settings.downloadLocationInvalid' => 'Geselecteerde map is niet beschrijfbaar', - 'settings.downloadLocationSelectError' => 'Kan map niet selecteren', + 'settings.downloadLocationPickerUnavailable' => 'Mapselectie is niet beschikbaar op dit apparaat', 'settings.downloadOnWifiOnly' => 'Alleen via WiFi downloaden', 'settings.downloadOnWifiOnlyDescription' => 'Voorkom downloads bij gebruik van mobiele data', 'settings.autoRemoveWatchedDownloads' => 'Bekeken downloads automatisch verwijderen', @@ -2554,10 +2564,15 @@ extension on TranslationsNl { 'accessibility.hexColor' => 'Hexkleur', 'accessibility.expandText' => 'Tekst uitvouwen', 'accessibility.collapseText' => 'Tekst samenvouwen', + 'accessibility.alphabetNavigation' => 'Alfabetische navigatie', + 'accessibility.alphabetScrollHint' => 'Veeg omhoog of omlaag om per letter te bewegen', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Rij ${row} van ${rowCount}, kolom ${column} van ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Rij ${row} van ${rowCount}', 'tooltips.shufflePlay' => 'Willekeurig afspelen', 'tooltips.playTrailer' => 'Trailer afspelen', 'tooltips.markAsWatched' => 'Markeer als gekeken', 'tooltips.markAsUnwatched' => 'Markeer als ongekeken', + 'audioTracks.track' => ({required Object n}) => 'Audiospoor ${n}', 'videoControls.audioLabel' => 'Audio', 'videoControls.subtitlesLabel' => 'Ondertitels', 'videoControls.resetToZero' => 'Reset naar 0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsNl { 'messages.markedAsUnwatched' => 'Gemarkeerd als ongekeken', 'messages.markedAsWatchedOffline' => 'Gemarkeerd als gekeken (sync wanneer online)', 'messages.markedAsUnwatchedOffline' => 'Gemarkeerd als ongekeken (sync wanneer online)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatisch verwijderd: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('nl'))(n, one: 'Automatisch ${n} bekeken download verwijderd', other: 'Automatisch ${n} bekeken downloads verwijderd', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatisch verwijderd: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('nl'))(n, one: 'Automatisch ${n} bekeken download verwijderd', other: 'Automatisch ${n} bekeken downloads verwijderd', ), 'messages.removedFromContinueWatching' => 'Verwijderd uit Doorgaan met kijken', 'messages.errorLoading' => ({required Object error}) => 'Fout: ${error}', 'messages.streamInterrupted' => 'De stream is onderbroken. Druk op afspelen of spoel om het opnieuw te proberen.', 'messages.liveStreamInterrupted' => 'De livestream is onderbroken. Druk op afspelen om het opnieuw te proberen.', 'messages.fileInfoNotAvailable' => 'Bestand informatie niet beschikbaar', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fout bij laden bestand info: ${error}', 'messages.errorLoadingSeries' => 'Fout bij laden serie', 'messages.musicNotSupported' => 'Muziek afspelen wordt nog niet ondersteund', @@ -2772,6 +2787,7 @@ extension on TranslationsNl { 'profiles.borrowExplain' => 'Leen de verbinding van een ander profiel. PIN-beveiligde profielen vereisen een PIN.', 'profiles.borrowEmpty' => 'Nog niets te lenen.', 'profiles.borrowEmptySubtitle' => 'Verbind Plex of Jellyfin eerst met een ander profiel.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'Van ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Verbinding geleend.', 'profiles.borrowFailed' => 'Kan verbinding niet lenen.', @@ -2997,7 +3013,7 @@ extension on TranslationsNl { 'liveTv.favorites' => 'Favorieten', 'liveTv.reorderFavorites' => 'Favorieten herordenen', 'liveTv.favoritesLoadFailed' => 'Favorieten konden niet worden geladen. Controleer je verbinding en probeer het opnieuw.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Deelnemen aan lopende sessie', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Kijk vanaf het begin (${minutes} min geleden)', 'liveTv.watchLive' => 'Live kijken', @@ -3157,11 +3173,11 @@ extension on TranslationsNl { 'watchTogether.participantLeft' => ({required Object name}) => '${name} heeft de sessie verlaten', 'watchTogether.participantPaused' => ({required Object name}) => '${name} heeft gepauzeerd', 'watchTogether.participantResumed' => ({required Object name}) => '${name} heeft hervat', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} heeft gespoeld', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} is aan het bufferen', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} gebruikt een oudere appversie โ€” synchronisatie niet beschikbaar', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Hervatten zonder ${name}', 'watchTogether.waitingForParticipants' => 'Wachten tot anderen geladen zijn...', 'watchTogether.waitingForName' => ({required Object name}) => 'Wachten op ${name}...', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index 5a69c6c3..f05e2267 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -52,6 +52,7 @@ class TranslationsPl extends Translations with BaseTranslations 'Oczekiwanie na zatwierdzenieโ€ฆ'; @override String get quickConnectCancel => 'Anuluj'; @override String get quickConnectExpired => 'Quick Connect wygasล‚. Sprรณbuj ponownie.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsPl extends TranslationsSettingsEn { @override String get libraryDensity => 'Gฤ™stoล›ฤ‡ biblioteki'; @override String get compact => 'Kompaktowy'; @override String get comfortable => 'Wygodny'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Tryb widoku'; @override String get gridView => 'Siatka'; @override String get listView => 'Lista'; @@ -311,7 +310,7 @@ class _TranslationsSettingsPl extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Relay Oglฤ…daj Razem'; @override String get watchTogetherRelayDescription => 'Ustaw wล‚asny relay. Wszyscy muszฤ… uลผywaฤ‡ tego samego serwera.'; @override String get watchTogetherRelayHint => 'https://moj-relay.przyklad.pl'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Raportowanie bล‚ฤ™dรณw'; @override String get crashReportingDescription => 'Wysyล‚aj raporty o bล‚ฤ™dach, aby pomรณc ulepszyฤ‡ aplikacjฤ™'; @override String get debugLogging => 'Logowanie debugowania'; @@ -328,12 +327,10 @@ class _TranslationsSettingsPl extends TranslationsSettingsEn { @override String get exportSettings => 'Eksportuj ustawienia'; @override String get exportSettingsDescription => 'Zapisz swoje preferencje do pliku'; @override String get exportSettingsSuccess => 'Ustawienia wyeksportowane'; - @override String get exportSettingsFailed => 'Nie moลผna wyeksportowaฤ‡ ustawieล„'; @override String get importSettings => 'Importuj ustawienia'; @override String get importSettingsDescription => 'Przywrรณฤ‡ preferencje z pliku'; @override String get importSettingsConfirm => 'Bieลผฤ…ce ustawienia zostanฤ… zastฤ…pione. Kontynuowaฤ‡?'; @override String get importSettingsSuccess => 'Ustawienia zaimportowane'; - @override String get importSettingsFailed => 'Nie moลผna zaimportowaฤ‡ ustawieล„'; @override String get importSettingsInvalidFile => 'Ten plik nie jest prawidล‚owym eksportem Plezy'; @override String get importSettingsNoUser => 'Zaloguj siฤ™ przed importem ustawieล„'; @override String get shortcutsReset => 'Skrรณty przywrรณcone do domyล›lnych'; @@ -348,6 +345,7 @@ class _TranslationsSettingsPl extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Czas musi byฤ‡ miฤ™dzy ${min} a ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'Skrรณt jest juลผ przypisany do ${action}'; @override String shortcutUpdated({required Object action}) => 'Skrรณt zaktualizowany dla ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Automatyczne pomijanie'; @override String get autoSkipIntro => 'Automatyczne pomijanie intro'; @override String get autoSkipIntroDescription => 'Automatycznie pomijaj znaczniki intro po kilku sekundach'; @@ -373,7 +371,7 @@ class _TranslationsSettingsPl extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Lokalizacja pobierania zmieniona'; @override String get downloadLocationReset => 'Lokalizacja pobierania przywrรณcona do domyล›lnej'; @override String get downloadLocationInvalid => 'Wybrany folder nie jest zapisywalny'; - @override String get downloadLocationSelectError => 'Nie udaล‚o siฤ™ wybraฤ‡ folderu'; + @override String get downloadLocationPickerUnavailable => 'Wybรณr folderu nie jest dostฤ™pny na tym urzฤ…dzeniu'; @override String get downloadOnWifiOnly => 'Pobieraj tylko przez WiFi'; @override String get downloadOnWifiOnlyDescription => 'Blokuj pobieranie na danych komรณrkowych'; @override String get autoRemoveWatchedDownloads => 'Automatycznie usuwaj obejrzane pobrania'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityPl extends TranslationsAccessibilityEn { @override String get hexColor => 'Kolor szesnastkowy'; @override String get expandText => 'Rozwiล„ tekst'; @override String get collapseText => 'Zwiล„ tekst'; + @override String get alphabetNavigation => 'Nawigacja alfabetyczna'; + @override String get alphabetScrollHint => 'Przesuล„ w gรณrฤ™ lub w dรณล‚, aby przejล›ฤ‡ o literฤ™'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Wiersz ${row} z ${rowCount}, kolumna ${column} z ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Wiersz ${row} z ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsPl extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Oznacz jako nieobejrzane'; } +// Path: audioTracks +class _TranslationsAudioTracksPl extends TranslationsAudioTracksEn { + _TranslationsAudioTracksPl._(TranslationsPl root) : this._root = root, super.internal(root); + + final TranslationsPl _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'ลšcieลผka audio ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsPl extends TranslationsVideoControlsEn { _TranslationsVideoControlsPl._(TranslationsPl root) : this._root = root, super.internal(root); @@ -714,11 +726,11 @@ class _TranslationsMessagesPl extends TranslationsMessagesEn { @override String get streamInterrupted => 'Strumieล„ zostaล‚ przerwany. Naciล›nij odtwarzanie lub przewiล„, aby sprรณbowaฤ‡ ponownie.'; @override String get liveStreamInterrupted => 'Transmisja na ลผywo zostaล‚a przerwana. Naciล›nij odtwarzanie, aby sprรณbowaฤ‡ ponownie.'; @override String get fileInfoNotAvailable => 'Informacje o pliku niedostฤ™pne'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Bล‚ฤ…d ล‚adowania informacji o pliku: ${error}'; @override String get errorLoadingSeries => 'Bล‚ฤ…d ล‚adowania serialu'; @override String get musicNotSupported => 'Odtwarzanie muzyki nie jest jeszcze obsล‚ugiwane'; @@ -869,6 +881,7 @@ class _TranslationsProfilesPl extends TranslationsProfilesEn { @override String get borrowExplain => 'Poลผycz poล‚ฤ…czenie z innego profilu. Profile chronione PIN wymagajฤ… PIN-u.'; @override String get borrowEmpty => 'Nic do poลผyczenia.'; @override String get borrowEmptySubtitle => 'Najpierw poล‚ฤ…cz Plex lub Jellyfin z innym profilem.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'Od ${displayName}'; @override String get borrowConnectionBorrowed => 'Poล‚ฤ…czenie poลผyczone.'; @override String get borrowFailed => 'Nie udaล‚o siฤ™ poลผyczyฤ‡ poล‚ฤ…czenia.'; @@ -1154,7 +1167,7 @@ class _TranslationsLiveTvPl extends TranslationsLiveTvEn { @override String get favorites => 'Ulubione'; @override String get reorderFavorites => 'Zmieล„ kolejnoล›ฤ‡ ulubionych'; @override String get favoritesLoadFailed => 'Nie udaล‚o siฤ™ wczytaฤ‡ ulubionych. Sprawdลบ poล‚ฤ…czenie i sprรณbuj ponownie.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Doล‚ฤ…cz do trwajฤ…cej sesji'; @override String watchFromStart({required Object minutes}) => 'Oglฤ…daj od poczฤ…tku (${minutes} min temu)'; @override String get watchLive => 'Oglฤ…daj na ลผywo'; @@ -2155,7 +2168,7 @@ extension on TranslationsPl { 'auth.quickConnectWaiting' => 'Oczekiwanie na zatwierdzenieโ€ฆ', 'auth.quickConnectCancel' => 'Anuluj', 'auth.quickConnectExpired' => 'Quick Connect wygasล‚. Sprรณbuj ponownie.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Anuluj', 'common.save' => 'Zapisz', 'common.close' => 'Zamknij', @@ -2239,8 +2252,6 @@ extension on TranslationsPl { 'settings.libraryDensity' => 'Gฤ™stoล›ฤ‡ biblioteki', 'settings.compact' => 'Kompaktowy', 'settings.comfortable' => 'Wygodny', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Tryb widoku', 'settings.gridView' => 'Siatka', 'settings.listView' => 'Lista', @@ -2310,7 +2321,7 @@ extension on TranslationsPl { 'settings.watchTogetherRelay' => 'Relay Oglฤ…daj Razem', 'settings.watchTogetherRelayDescription' => 'Ustaw wล‚asny relay. Wszyscy muszฤ… uลผywaฤ‡ tego samego serwera.', 'settings.watchTogetherRelayHint' => 'https://moj-relay.przyklad.pl', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Raportowanie bล‚ฤ™dรณw', 'settings.crashReportingDescription' => 'Wysyล‚aj raporty o bล‚ฤ™dach, aby pomรณc ulepszyฤ‡ aplikacjฤ™', 'settings.debugLogging' => 'Logowanie debugowania', @@ -2327,12 +2338,10 @@ extension on TranslationsPl { 'settings.exportSettings' => 'Eksportuj ustawienia', 'settings.exportSettingsDescription' => 'Zapisz swoje preferencje do pliku', 'settings.exportSettingsSuccess' => 'Ustawienia wyeksportowane', - 'settings.exportSettingsFailed' => 'Nie moลผna wyeksportowaฤ‡ ustawieล„', 'settings.importSettings' => 'Importuj ustawienia', 'settings.importSettingsDescription' => 'Przywrรณฤ‡ preferencje z pliku', 'settings.importSettingsConfirm' => 'Bieลผฤ…ce ustawienia zostanฤ… zastฤ…pione. Kontynuowaฤ‡?', 'settings.importSettingsSuccess' => 'Ustawienia zaimportowane', - 'settings.importSettingsFailed' => 'Nie moลผna zaimportowaฤ‡ ustawieล„', 'settings.importSettingsInvalidFile' => 'Ten plik nie jest prawidล‚owym eksportem Plezy', 'settings.importSettingsNoUser' => 'Zaloguj siฤ™ przed importem ustawieล„', 'settings.shortcutsReset' => 'Skrรณty przywrรณcone do domyล›lnych', @@ -2347,6 +2356,7 @@ extension on TranslationsPl { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'Czas musi byฤ‡ miฤ™dzy ${min} a ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Skrรณt jest juลผ przypisany do ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Skrรณt zaktualizowany dla ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Automatyczne pomijanie', 'settings.autoSkipIntro' => 'Automatyczne pomijanie intro', 'settings.autoSkipIntroDescription' => 'Automatycznie pomijaj znaczniki intro po kilku sekundach', @@ -2372,7 +2382,7 @@ extension on TranslationsPl { 'settings.downloadLocationChanged' => 'Lokalizacja pobierania zmieniona', 'settings.downloadLocationReset' => 'Lokalizacja pobierania przywrรณcona do domyล›lnej', 'settings.downloadLocationInvalid' => 'Wybrany folder nie jest zapisywalny', - 'settings.downloadLocationSelectError' => 'Nie udaล‚o siฤ™ wybraฤ‡ folderu', + 'settings.downloadLocationPickerUnavailable' => 'Wybรณr folderu nie jest dostฤ™pny na tym urzฤ…dzeniu', 'settings.downloadOnWifiOnly' => 'Pobieraj tylko przez WiFi', 'settings.downloadOnWifiOnlyDescription' => 'Blokuj pobieranie na danych komรณrkowych', 'settings.autoRemoveWatchedDownloads' => 'Automatycznie usuwaj obejrzane pobrania', @@ -2560,10 +2570,15 @@ extension on TranslationsPl { 'accessibility.hexColor' => 'Kolor szesnastkowy', 'accessibility.expandText' => 'Rozwiล„ tekst', 'accessibility.collapseText' => 'Zwiล„ tekst', + 'accessibility.alphabetNavigation' => 'Nawigacja alfabetyczna', + 'accessibility.alphabetScrollHint' => 'Przesuล„ w gรณrฤ™ lub w dรณล‚, aby przejล›ฤ‡ o literฤ™', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Wiersz ${row} z ${rowCount}, kolumna ${column} z ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Wiersz ${row} z ${rowCount}', 'tooltips.shufflePlay' => 'Odtwarzanie losowe', 'tooltips.playTrailer' => 'Odtwรณrz zwiastun', 'tooltips.markAsWatched' => 'Oznacz jako obejrzane', 'tooltips.markAsUnwatched' => 'Oznacz jako nieobejrzane', + 'audioTracks.track' => ({required Object n}) => 'ลšcieลผka audio ${n}', 'videoControls.audioLabel' => 'Audio', 'videoControls.subtitlesLabel' => 'Napisy', 'videoControls.resetToZero' => 'Zresetuj do 0ms', @@ -2650,20 +2665,20 @@ extension on TranslationsPl { 'messages.markedAsUnwatched' => 'Oznaczono jako nieobejrzane', 'messages.markedAsWatchedOffline' => 'Oznaczono jako obejrzane (zsynchronizuje siฤ™ po poล‚ฤ…czeniu)', 'messages.markedAsUnwatchedOffline' => 'Oznaczono jako nieobejrzane (zsynchronizuje siฤ™ po poล‚ฤ…czeniu)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatycznie usuniฤ™to: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(n, one: 'Automatycznie usuniฤ™to ${n} obejrzane pobranie', few: 'Automatycznie usuniฤ™to ${n} obejrzane pobrania', many: 'Automatycznie usuniฤ™to ${n} obejrzanych pobraล„', other: 'Automatycznie usuniฤ™to ${n} obejrzanego pobrania', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatycznie usuniฤ™to: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pl'))(n, one: 'Automatycznie usuniฤ™to ${n} obejrzane pobranie', few: 'Automatycznie usuniฤ™to ${n} obejrzane pobrania', many: 'Automatycznie usuniฤ™to ${n} obejrzanych pobraล„', other: 'Automatycznie usuniฤ™to ${n} obejrzanego pobrania', ), 'messages.removedFromContinueWatching' => 'Usuniฤ™to z kontynuowania oglฤ…dania', 'messages.errorLoading' => ({required Object error}) => 'Bล‚ฤ…d: ${error}', 'messages.streamInterrupted' => 'Strumieล„ zostaล‚ przerwany. Naciล›nij odtwarzanie lub przewiล„, aby sprรณbowaฤ‡ ponownie.', 'messages.liveStreamInterrupted' => 'Transmisja na ลผywo zostaล‚a przerwana. Naciล›nij odtwarzanie, aby sprรณbowaฤ‡ ponownie.', 'messages.fileInfoNotAvailable' => 'Informacje o pliku niedostฤ™pne', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Bล‚ฤ…d ล‚adowania informacji o pliku: ${error}', 'messages.errorLoadingSeries' => 'Bล‚ฤ…d ล‚adowania serialu', 'messages.musicNotSupported' => 'Odtwarzanie muzyki nie jest jeszcze obsล‚ugiwane', @@ -2778,6 +2793,7 @@ extension on TranslationsPl { 'profiles.borrowExplain' => 'Poลผycz poล‚ฤ…czenie z innego profilu. Profile chronione PIN wymagajฤ… PIN-u.', 'profiles.borrowEmpty' => 'Nic do poลผyczenia.', 'profiles.borrowEmptySubtitle' => 'Najpierw poล‚ฤ…cz Plex lub Jellyfin z innym profilem.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'Od ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Poล‚ฤ…czenie poลผyczone.', 'profiles.borrowFailed' => 'Nie udaล‚o siฤ™ poลผyczyฤ‡ poล‚ฤ…czenia.', @@ -3003,7 +3019,7 @@ extension on TranslationsPl { 'liveTv.favorites' => 'Ulubione', 'liveTv.reorderFavorites' => 'Zmieล„ kolejnoล›ฤ‡ ulubionych', 'liveTv.favoritesLoadFailed' => 'Nie udaล‚o siฤ™ wczytaฤ‡ ulubionych. Sprawdลบ poล‚ฤ…czenie i sprรณbuj ponownie.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Doล‚ฤ…cz do trwajฤ…cej sesji', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Oglฤ…daj od poczฤ…tku (${minutes} min temu)', 'liveTv.watchLive' => 'Oglฤ…daj na ลผywo', @@ -3163,11 +3179,11 @@ extension on TranslationsPl { 'watchTogether.participantLeft' => ({required Object name}) => '${name} opuล›ciล‚', 'watchTogether.participantPaused' => ({required Object name}) => '${name} wstrzymaล‚', 'watchTogether.participantResumed' => ({required Object name}) => '${name} wznowiล‚', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} przewinฤ…ล‚', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} buforuje', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} uลผywa starszej wersji aplikacji โ€” synchronizacja jest niedostฤ™pna', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Wznawianie bez ${name}', 'watchTogether.waitingForParticipants' => 'Oczekiwanie na zaล‚adowanie u innych...', 'watchTogether.waitingForName' => ({required Object name}) => 'Oczekiwanie na ${name}...', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index 9c6c030d..27e968ba 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -52,6 +52,7 @@ class TranslationsPt extends Translations with BaseTranslations 'A aguardar aprovaรงรฃoโ€ฆ'; @override String get quickConnectCancel => 'Cancelar'; @override String get quickConnectExpired => 'Quick Connect expirou. Tente novamente.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsPt extends TranslationsSettingsEn { @override String get libraryDensity => 'Densidade da Biblioteca'; @override String get compact => 'Compacto'; @override String get comfortable => 'Confortรกvel'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Modo de Visualizaรงรฃo'; @override String get gridView => 'Grade'; @override String get listView => 'Lista'; @@ -311,7 +310,7 @@ class _TranslationsSettingsPt extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Relay do Assistir Juntos'; @override String get watchTogetherRelayDescription => 'Defina um relay personalizado. Todos devem usar o mesmo servidor.'; @override String get watchTogetherRelayHint => 'https://meu-relay.exemplo.com.br'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Relatรณrio de Erros'; @override String get crashReportingDescription => 'Enviar relatรณrios de erros para ajudar a melhorar o app'; @override String get debugLogging => 'Log de Depuraรงรฃo'; @@ -328,12 +327,10 @@ class _TranslationsSettingsPt extends TranslationsSettingsEn { @override String get exportSettings => 'Exportar Configuraรงรตes'; @override String get exportSettingsDescription => 'Salve suas preferรชncias em um arquivo'; @override String get exportSettingsSuccess => 'Configuraรงรตes exportadas'; - @override String get exportSettingsFailed => 'Nรฃo foi possรญvel exportar as configuraรงรตes'; @override String get importSettings => 'Importar Configuraรงรตes'; @override String get importSettingsDescription => 'Restaurar preferรชncias a partir de um arquivo'; @override String get importSettingsConfirm => 'Isso substituirรก suas configuraรงรตes atuais. Continuar?'; @override String get importSettingsSuccess => 'Configuraรงรตes importadas'; - @override String get importSettingsFailed => 'Nรฃo foi possรญvel importar as configuraรงรตes'; @override String get importSettingsInvalidFile => 'Este arquivo nรฃo รฉ uma exportaรงรฃo vรกlida do Plezy'; @override String get importSettingsNoUser => 'Entre na conta antes de importar as configuraรงรตes'; @override String get shortcutsReset => 'Atalhos redefinidos para o padrรฃo'; @@ -348,6 +345,7 @@ class _TranslationsSettingsPt extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'A duraรงรฃo deve ser entre ${min} e ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'Atalho jรก atribuรญdo a ${action}'; @override String shortcutUpdated({required Object action}) => 'Atalho atualizado para ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Pular Automaticamente'; @override String get autoSkipIntro => 'Pular Intro Automaticamente'; @override String get autoSkipIntroDescription => 'Pular marcadores de intro automaticamente apรณs alguns segundos'; @@ -373,7 +371,7 @@ class _TranslationsSettingsPt extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Local de download alterado'; @override String get downloadLocationReset => 'Local de download redefinido para padrรฃo'; @override String get downloadLocationInvalid => 'A pasta selecionada nรฃo permite gravaรงรฃo'; - @override String get downloadLocationSelectError => 'Falha ao selecionar pasta'; + @override String get downloadLocationPickerUnavailable => 'A seleรงรฃo de pasta nรฃo estรก disponรญvel neste dispositivo'; @override String get downloadOnWifiOnly => 'Baixar apenas no WiFi'; @override String get downloadOnWifiOnlyDescription => 'Impedir downloads quando em dados mรณveis'; @override String get autoRemoveWatchedDownloads => 'Remover downloads assistidos automaticamente'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityPt extends TranslationsAccessibilityEn { @override String get hexColor => 'Cor hexadecimal'; @override String get expandText => 'Expandir texto'; @override String get collapseText => 'Recolher texto'; + @override String get alphabetNavigation => 'Navegaรงรฃo alfabรฉtica'; + @override String get alphabetScrollHint => 'Deslize para cima ou para baixo para avanรงar por letra'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Linha ${row} de ${rowCount}, coluna ${column} de ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Linha ${row} de ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsPt extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Marcar como nรฃo assistido'; } +// Path: audioTracks +class _TranslationsAudioTracksPt extends TranslationsAudioTracksEn { + _TranslationsAudioTracksPt._(TranslationsPt root) : this._root = root, super.internal(root); + + final TranslationsPt _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Faixa de รกudio ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsPt extends TranslationsVideoControlsEn { _TranslationsVideoControlsPt._(TranslationsPt root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesPt extends TranslationsMessagesEn { @override String get streamInterrupted => 'A transmissรฃo foi interrompida. Toque em reproduzir ou avance para tentar novamente.'; @override String get liveStreamInterrupted => 'A transmissรฃo ao vivo foi interrompida. Toque em reproduzir para tentar novamente.'; @override String get fileInfoNotAvailable => 'Informaรงรตes do arquivo nรฃo disponรญveis'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Erro ao carregar info do arquivo: ${error}'; @override String get errorLoadingSeries => 'Erro ao carregar sรฉrie'; @override String get musicNotSupported => 'Reproduรงรฃo de mรบsica ainda nรฃo รฉ suportada'; @@ -867,6 +879,7 @@ class _TranslationsProfilesPt extends TranslationsProfilesEn { @override String get borrowExplain => 'Use a conexรฃo de outro perfil. Perfis protegidos por PIN exigem PIN.'; @override String get borrowEmpty => 'Nada para emprestar ainda.'; @override String get borrowEmptySubtitle => 'Conecte Plex ou Jellyfin a outro perfil primeiro.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'De ${displayName}'; @override String get borrowConnectionBorrowed => 'Conexรฃo tomada emprestada.'; @override String get borrowFailed => 'Nรฃo foi possรญvel tomar a conexรฃo emprestada.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvPt extends TranslationsLiveTvEn { @override String get favorites => 'Favoritos'; @override String get reorderFavorites => 'Reordenar favoritos'; @override String get favoritesLoadFailed => 'Nรฃo foi possรญvel carregar os favoritos. Verifique sua conexรฃo e tente novamente.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Entrar na sessรฃo em andamento'; @override String watchFromStart({required Object minutes}) => 'Assistir do inรญcio (${minutes} min atrรกs)'; @override String get watchLive => 'Assistir ao vivo'; @@ -2149,7 +2162,7 @@ extension on TranslationsPt { 'auth.quickConnectWaiting' => 'A aguardar aprovaรงรฃoโ€ฆ', 'auth.quickConnectCancel' => 'Cancelar', 'auth.quickConnectExpired' => 'Quick Connect expirou. Tente novamente.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Cancelar', 'common.save' => 'Salvar', 'common.close' => 'Fechar', @@ -2233,8 +2246,6 @@ extension on TranslationsPt { 'settings.libraryDensity' => 'Densidade da Biblioteca', 'settings.compact' => 'Compacto', 'settings.comfortable' => 'Confortรกvel', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Modo de Visualizaรงรฃo', 'settings.gridView' => 'Grade', 'settings.listView' => 'Lista', @@ -2304,7 +2315,7 @@ extension on TranslationsPt { 'settings.watchTogetherRelay' => 'Relay do Assistir Juntos', 'settings.watchTogetherRelayDescription' => 'Defina um relay personalizado. Todos devem usar o mesmo servidor.', 'settings.watchTogetherRelayHint' => 'https://meu-relay.exemplo.com.br', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Relatรณrio de Erros', 'settings.crashReportingDescription' => 'Enviar relatรณrios de erros para ajudar a melhorar o app', 'settings.debugLogging' => 'Log de Depuraรงรฃo', @@ -2321,12 +2332,10 @@ extension on TranslationsPt { 'settings.exportSettings' => 'Exportar Configuraรงรตes', 'settings.exportSettingsDescription' => 'Salve suas preferรชncias em um arquivo', 'settings.exportSettingsSuccess' => 'Configuraรงรตes exportadas', - 'settings.exportSettingsFailed' => 'Nรฃo foi possรญvel exportar as configuraรงรตes', 'settings.importSettings' => 'Importar Configuraรงรตes', 'settings.importSettingsDescription' => 'Restaurar preferรชncias a partir de um arquivo', 'settings.importSettingsConfirm' => 'Isso substituirรก suas configuraรงรตes atuais. Continuar?', 'settings.importSettingsSuccess' => 'Configuraรงรตes importadas', - 'settings.importSettingsFailed' => 'Nรฃo foi possรญvel importar as configuraรงรตes', 'settings.importSettingsInvalidFile' => 'Este arquivo nรฃo รฉ uma exportaรงรฃo vรกlida do Plezy', 'settings.importSettingsNoUser' => 'Entre na conta antes de importar as configuraรงรตes', 'settings.shortcutsReset' => 'Atalhos redefinidos para o padrรฃo', @@ -2341,6 +2350,7 @@ extension on TranslationsPt { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'A duraรงรฃo deve ser entre ${min} e ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Atalho jรก atribuรญdo a ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Atalho atualizado para ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Pular Automaticamente', 'settings.autoSkipIntro' => 'Pular Intro Automaticamente', 'settings.autoSkipIntroDescription' => 'Pular marcadores de intro automaticamente apรณs alguns segundos', @@ -2366,7 +2376,7 @@ extension on TranslationsPt { 'settings.downloadLocationChanged' => 'Local de download alterado', 'settings.downloadLocationReset' => 'Local de download redefinido para padrรฃo', 'settings.downloadLocationInvalid' => 'A pasta selecionada nรฃo permite gravaรงรฃo', - 'settings.downloadLocationSelectError' => 'Falha ao selecionar pasta', + 'settings.downloadLocationPickerUnavailable' => 'A seleรงรฃo de pasta nรฃo estรก disponรญvel neste dispositivo', 'settings.downloadOnWifiOnly' => 'Baixar apenas no WiFi', 'settings.downloadOnWifiOnlyDescription' => 'Impedir downloads quando em dados mรณveis', 'settings.autoRemoveWatchedDownloads' => 'Remover downloads assistidos automaticamente', @@ -2554,10 +2564,15 @@ extension on TranslationsPt { 'accessibility.hexColor' => 'Cor hexadecimal', 'accessibility.expandText' => 'Expandir texto', 'accessibility.collapseText' => 'Recolher texto', + 'accessibility.alphabetNavigation' => 'Navegaรงรฃo alfabรฉtica', + 'accessibility.alphabetScrollHint' => 'Deslize para cima ou para baixo para avanรงar por letra', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Linha ${row} de ${rowCount}, coluna ${column} de ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Linha ${row} de ${rowCount}', 'tooltips.shufflePlay' => 'Reproduรงรฃo aleatรณria', 'tooltips.playTrailer' => 'Reproduzir trailer', 'tooltips.markAsWatched' => 'Marcar como assistido', 'tooltips.markAsUnwatched' => 'Marcar como nรฃo assistido', + 'audioTracks.track' => ({required Object n}) => 'Faixa de รกudio ${n}', 'videoControls.audioLabel' => 'รudio', 'videoControls.subtitlesLabel' => 'Legendas', 'videoControls.resetToZero' => 'Redefinir para 0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsPt { 'messages.markedAsUnwatched' => 'Marcado como nรฃo assistido', 'messages.markedAsWatchedOffline' => 'Marcado como assistido (serรก sincronizado quando online)', 'messages.markedAsUnwatchedOffline' => 'Marcado como nรฃo assistido (serรก sincronizado quando online)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Removido automaticamente: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pt'))(n, one: 'Removido automaticamente ${n} download assistido', other: 'Removidos automaticamente ${n} downloads assistidos', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Removido automaticamente: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('pt'))(n, one: 'Removido automaticamente ${n} download assistido', other: 'Removidos automaticamente ${n} downloads assistidos', ), 'messages.removedFromContinueWatching' => 'Removido de Continuar Assistindo', 'messages.errorLoading' => ({required Object error}) => 'Erro: ${error}', 'messages.streamInterrupted' => 'A transmissรฃo foi interrompida. Toque em reproduzir ou avance para tentar novamente.', 'messages.liveStreamInterrupted' => 'A transmissรฃo ao vivo foi interrompida. Toque em reproduzir para tentar novamente.', 'messages.fileInfoNotAvailable' => 'Informaรงรตes do arquivo nรฃo disponรญveis', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Erro ao carregar info do arquivo: ${error}', 'messages.errorLoadingSeries' => 'Erro ao carregar sรฉrie', 'messages.musicNotSupported' => 'Reproduรงรฃo de mรบsica ainda nรฃo รฉ suportada', @@ -2772,6 +2787,7 @@ extension on TranslationsPt { 'profiles.borrowExplain' => 'Use a conexรฃo de outro perfil. Perfis protegidos por PIN exigem PIN.', 'profiles.borrowEmpty' => 'Nada para emprestar ainda.', 'profiles.borrowEmptySubtitle' => 'Conecte Plex ou Jellyfin a outro perfil primeiro.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'De ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Conexรฃo tomada emprestada.', 'profiles.borrowFailed' => 'Nรฃo foi possรญvel tomar a conexรฃo emprestada.', @@ -2997,7 +3013,7 @@ extension on TranslationsPt { 'liveTv.favorites' => 'Favoritos', 'liveTv.reorderFavorites' => 'Reordenar favoritos', 'liveTv.favoritesLoadFailed' => 'Nรฃo foi possรญvel carregar os favoritos. Verifique sua conexรฃo e tente novamente.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Entrar na sessรฃo em andamento', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Assistir do inรญcio (${minutes} min atrรกs)', 'liveTv.watchLive' => 'Assistir ao vivo', @@ -3157,11 +3173,11 @@ extension on TranslationsPt { 'watchTogether.participantLeft' => ({required Object name}) => '${name} saiu', 'watchTogether.participantPaused' => ({required Object name}) => '${name} pausou', 'watchTogether.participantResumed' => ({required Object name}) => '${name} retomou', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} avanรงou', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} estรก carregando', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} estรก em uma versรฃo mais antiga do aplicativo โ€” sincronizaรงรฃo indisponรญvel', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'Retomando sem ${name}', 'watchTogether.waitingForParticipants' => 'Aguardando outros carregarem...', 'watchTogether.waitingForName' => ({required Object name}) => 'Aguardando ${name}...', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index b783415b..3a2cb641 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -52,6 +52,7 @@ class TranslationsRu extends Translations with BaseTranslations 'ะžะถะธะดะฐะฝะธะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธัโ€ฆ'; @override String get quickConnectCancel => 'ะžั‚ะผะตะฝะฐ'; @override String get quickConnectExpired => 'ะกั€ะพะบ Quick Connect ะธัั‚ะตะบ. ะŸะพะฟั€ะพะฑัƒะนั‚ะต ัะฝะพะฒะฐ.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsRu extends TranslationsSettingsEn { @override String get libraryDensity => 'ะŸะปะพั‚ะฝะพัั‚ัŒ ะฑะธะฑะปะธะพั‚ะตะบะธ'; @override String get compact => 'ะšะพะผะฟะฐะบั‚ะฝั‹ะน'; @override String get comfortable => 'ะšะพะผั„ะพั€ั‚ะฝั‹ะน'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'ะ ะตะถะธะผ ะฟั€ะพัะผะพั‚ั€ะฐ'; @override String get gridView => 'ะกะตั‚ะบะฐ'; @override String get listView => 'ะกะฟะธัะพะบ'; @@ -311,7 +310,7 @@ class _TranslationsSettingsRu extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Relay ัะพะฒะผะตัั‚ะฝะพะณะพ ะฟั€ะพัะผะพั‚ั€ะฐ'; @override String get watchTogetherRelayDescription => 'ะ—ะฐะดะฐะนั‚ะต ัะฒะพะน relay. ะ’ัะต ะดะพะปะถะฝั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะดะธะฝ ัะตั€ะฒะตั€.'; @override String get watchTogetherRelayHint => 'https://my-relay.example.com'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'ะžั‚ั‡ั‘ั‚ั‹ ะพะฑ ะพัˆะธะฑะบะฐั…'; @override String get crashReportingDescription => 'ะžั‚ะฟั€ะฐะฒะปัั‚ัŒ ะพั‚ั‡ั‘ั‚ั‹ ะพะฑ ะพัˆะธะฑะบะฐั… ะดะปั ัƒะปัƒั‡ัˆะตะฝะธั ะฟั€ะธะปะพะถะตะฝะธั'; @override String get debugLogging => 'ะ–ัƒั€ะฝะฐะป ะพั‚ะปะฐะดะบะธ'; @@ -328,12 +327,10 @@ class _TranslationsSettingsRu extends TranslationsSettingsEn { @override String get exportSettings => 'ะญะบัะฟะพั€ั‚ ะฝะฐัั‚ั€ะพะตะบ'; @override String get exportSettingsDescription => 'ะกะพั…ั€ะฐะฝะธั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ ะฒ ั„ะฐะนะป'; @override String get exportSettingsSuccess => 'ะะฐัั‚ั€ะพะนะบะธ ัะบัะฟะพั€ั‚ะธั€ะพะฒะฐะฝั‹'; - @override String get exportSettingsFailed => 'ะะต ัƒะดะฐะปะพััŒ ัะบัะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ'; @override String get importSettings => 'ะ˜ะผะฟะพั€ั‚ ะฝะฐัั‚ั€ะพะตะบ'; @override String get importSettingsDescription => 'ะ’ะพััั‚ะฐะฝะพะฒะธั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ ะธะท ั„ะฐะนะปะฐ'; @override String get importSettingsConfirm => 'ะญั‚ะพ ะทะฐะผะตะฝะธั‚ ะฒะฐัˆะธ ั‚ะตะบัƒั‰ะธะต ะฝะฐัั‚ั€ะพะนะบะธ. ะŸั€ะพะดะพะปะถะธั‚ัŒ?'; @override String get importSettingsSuccess => 'ะะฐัั‚ั€ะพะนะบะธ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะฝั‹'; - @override String get importSettingsFailed => 'ะะต ัƒะดะฐะปะพััŒ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ'; @override String get importSettingsInvalidFile => 'ะญั‚ะพั‚ ั„ะฐะนะป ะฝะต ัะฒะปัะตั‚ัั ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะผ ัะบัะฟะพั€ั‚ะพะผ ะฝะฐัั‚ั€ะพะตะบ Plezy'; @override String get importSettingsNoUser => 'ะ’ะพะนะดะธั‚ะต ะฒ ัะธัั‚ะตะผัƒ ะฟะตั€ะตะด ะธะผะฟะพั€ั‚ะพะผ ะฝะฐัั‚ั€ะพะตะบ'; @override String get shortcutsReset => 'ะ“ะพั€ัั‡ะธะต ะบะปะฐะฒะธัˆะธ ัะฑั€ะพัˆะตะฝั‹ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ'; @@ -348,6 +345,7 @@ class _TranslationsSettingsRu extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'ะ”ะปะธั‚ะตะปัŒะฝะพัั‚ัŒ ะดะพะปะถะฝะฐ ะฑั‹ั‚ัŒ ะพั‚ ${min} ะดะพ ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'ะšะปะฐะฒะธัˆะฐ ัƒะถะต ะฝะฐะทะฝะฐั‡ะตะฝะฐ ะดะปั ${action}'; @override String shortcutUpdated({required Object action}) => 'ะšะปะฐะฒะธัˆะฐ ะพะฑะฝะพะฒะปะตะฝะฐ ะดะปั ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'ะะฒั‚ะพะฟั€ะพะฟัƒัะบ'; @override String get autoSkipIntro => 'ะะฒั‚ะพะฟั€ะพะฟัƒัะบ ะฒัั‚ัƒะฟะปะตะฝะธั'; @override String get autoSkipIntroDescription => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟั€ะพะฟัƒัะบะฐั‚ัŒ ะผะฐั€ะบะตั€ั‹ ะฒัั‚ัƒะฟะปะตะฝะธั ั‡ะตั€ะตะท ะฝะตัะบะพะปัŒะบะพ ัะตะบัƒะฝะด'; @@ -373,7 +371,7 @@ class _TranslationsSettingsRu extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'ะœะตัั‚ะพ ะทะฐะณั€ัƒะทะบะธ ะธะทะผะตะฝะตะฝะพ'; @override String get downloadLocationReset => 'ะœะตัั‚ะพ ะทะฐะณั€ัƒะทะบะธ ัะฑั€ะพัˆะตะฝะพ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ'; @override String get downloadLocationInvalid => 'ะ’ั‹ะฑั€ะฐะฝะฝะฐั ะฟะฐะฟะบะฐ ะฝะตะดะพัั‚ัƒะฟะฝะฐ ะดะปั ะทะฐะฟะธัะธ'; - @override String get downloadLocationSelectError => 'ะะต ัƒะดะฐะปะพััŒ ะฒั‹ะฑั€ะฐั‚ัŒ ะฟะฐะฟะบัƒ'; + @override String get downloadLocationPickerUnavailable => 'ะ’ั‹ะฑะพั€ ะฟะฐะฟะบะธ ะฝะตะดะพัั‚ัƒะฟะตะฝ ะฝะฐ ัั‚ะพะผ ัƒัั‚ั€ะพะนัั‚ะฒะต'; @override String get downloadOnWifiOnly => 'ะ—ะฐะณั€ัƒะถะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟะพ WiFi'; @override String get downloadOnWifiOnlyDescription => 'ะ—ะฐะฟั€ะตั‚ะธั‚ัŒ ะทะฐะณั€ัƒะทะบัƒ ะฟะพ ะผะพะฑะธะปัŒะฝั‹ะผ ะดะฐะฝะฝั‹ะผ'; @override String get autoRemoveWatchedDownloads => 'ะะฒั‚ะพัƒะดะฐะปะตะฝะธะต ะฟั€ะพัะผะพั‚ั€ะตะฝะฝั‹ั… ะทะฐะณั€ัƒะทะพะบ'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityRu extends TranslationsAccessibilityEn { @override String get hexColor => 'ะจะตัั‚ะฝะฐะดั†ะฐั‚ะตั€ะธั‡ะฝั‹ะน ั†ะฒะตั‚'; @override String get expandText => 'ะ ะฐะทะฒะตั€ะฝัƒั‚ัŒ ั‚ะตะบัั‚'; @override String get collapseText => 'ะกะฒะตั€ะฝัƒั‚ัŒ ั‚ะตะบัั‚'; + @override String get alphabetNavigation => 'ะะฐะฒะธะณะฐั†ะธั ะฟะพ ะฐะปั„ะฐะฒะธั‚ัƒ'; + @override String get alphabetScrollHint => 'ะŸั€ะพะฒะตะดะธั‚ะต ะฒะฒะตั€ั… ะธะปะธ ะฒะฝะธะท ะดะปั ะฟะตั€ะตั…ะพะดะฐ ะฟะพ ะฑัƒะบะฒะฐะผ'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'ะกั‚ั€ะพะบะฐ ${row} ะธะท ${rowCount}, ัั‚ะพะปะฑะตั† ${column} ะธะท ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'ะกั‚ั€ะพะบะฐ ${row} ะธะท ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsRu extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'ะžั‚ะผะตั‚ะธั‚ัŒ ะบะฐะบ ะฝะตะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะต'; } +// Path: audioTracks +class _TranslationsAudioTracksRu extends TranslationsAudioTracksEn { + _TranslationsAudioTracksRu._(TranslationsRu root) : this._root = root, super.internal(root); + + final TranslationsRu _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'ะัƒะดะธะพะดะพั€ะพะถะบะฐ ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsRu extends TranslationsVideoControlsEn { _TranslationsVideoControlsRu._(TranslationsRu root) : this._root = root, super.internal(root); @@ -714,11 +726,11 @@ class _TranslationsMessagesRu extends TranslationsMessagesEn { @override String get streamInterrupted => 'ะŸะพั‚ะพะบ ะฟั€ะตั€ะฒะฐะปัั. ะะฐะถะผะธั‚ะต ยซะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธยป ะธะปะธ ะฟะตั€ะตะผะพั‚ะฐะนั‚ะต, ั‡ั‚ะพะฑั‹ ะฟะพะฒั‚ะพั€ะธั‚ัŒ ะฟะพะฟั‹ั‚ะบัƒ.'; @override String get liveStreamInterrupted => 'ะŸั€ัะผะฐั ั‚ั€ะฐะฝัะปัั†ะธั ะฟั€ะตั€ะฒะฐะปะฐััŒ. ะะฐะถะผะธั‚ะต ยซะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธยป, ั‡ั‚ะพะฑั‹ ะฟะพะฒั‚ะพั€ะธั‚ัŒ ะฟะพะฟั‹ั‚ะบัƒ.'; @override String get fileInfoNotAvailable => 'ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ั„ะฐะนะปะต ะฝะตะดะพัั‚ัƒะฟะฝะฐ'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'ะžัˆะธะฑะบะฐ ะทะฐะณั€ัƒะทะบะธ ะธะฝั„ะพั€ะผะฐั†ะธะธ ะพ ั„ะฐะนะปะต: ${error}'; @override String get errorLoadingSeries => 'ะžัˆะธะฑะบะฐ ะทะฐะณั€ัƒะทะบะธ ัะตั€ะธะฐะปะฐ'; @override String get musicNotSupported => 'ะ’ะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต ะผัƒะทั‹ะบะธ ะฟะพะบะฐ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั'; @@ -869,6 +881,7 @@ class _TranslationsProfilesRu extends TranslationsProfilesEn { @override String get borrowExplain => 'ะ—ะฐะธะผัั‚ะฒัƒะนั‚ะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะดั€ัƒะณะพะณะพ ะฟั€ะพั„ะธะปั. ะ”ะปั ะฟั€ะพั„ะธะปะตะน ั PIN ะฝัƒะถะตะฝ PIN.'; @override String get borrowEmpty => 'ะŸะพะบะฐ ะฝะตั‡ะตะณะพ ะทะฐะธะผัั‚ะฒะพะฒะฐั‚ัŒ.'; @override String get borrowEmptySubtitle => 'ะกะฝะฐั‡ะฐะปะฐ ะฟะพะดะบะปัŽั‡ะธั‚ะต Plex ะธะปะธ Jellyfin ะบ ะดั€ัƒะณะพะผัƒ ะฟั€ะพั„ะธะปัŽ.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'ะ˜ะท ${displayName}'; @override String get borrowConnectionBorrowed => 'ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะทะฐะธะผัั‚ะฒะพะฒะฐะฝะพ.'; @override String get borrowFailed => 'ะะต ัƒะดะฐะปะพััŒ ะทะฐะธะผัั‚ะฒะพะฒะฐั‚ัŒ ะฟะพะดะบะปัŽั‡ะตะฝะธะต.'; @@ -1154,7 +1167,7 @@ class _TranslationsLiveTvRu extends TranslationsLiveTvEn { @override String get favorites => 'ะ˜ะทะฑั€ะฐะฝะฝะพะต'; @override String get reorderFavorites => 'ะ˜ะทะผะตะฝะธั‚ัŒ ะฟะพั€ัะดะพะบ ะธะทะฑั€ะฐะฝะฝะพะณะพ'; @override String get favoritesLoadFailed => 'ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ ะธะทะฑั€ะฐะฝะฝะพะต. ะŸั€ะพะฒะตั€ัŒั‚ะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะธ ะฟะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'ะŸั€ะธัะพะตะดะธะฝะธั‚ัŒัั ะบ ั‚ะตะบัƒั‰ะตะผัƒ ัะตะฐะฝััƒ'; @override String watchFromStart({required Object minutes}) => 'ะกะผะพั‚ั€ะตั‚ัŒ ัะฝะฐั‡ะฐะปะฐ (${minutes} ะผะธะฝ. ะฝะฐะทะฐะด)'; @override String get watchLive => 'ะกะผะพั‚ั€ะตั‚ัŒ ะฒ ะฟั€ัะผะพะผ ัั„ะธั€ะต'; @@ -2155,7 +2168,7 @@ extension on TranslationsRu { 'auth.quickConnectWaiting' => 'ะžะถะธะดะฐะฝะธะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธัโ€ฆ', 'auth.quickConnectCancel' => 'ะžั‚ะผะตะฝะฐ', 'auth.quickConnectExpired' => 'ะกั€ะพะบ Quick Connect ะธัั‚ะตะบ. ะŸะพะฟั€ะพะฑัƒะนั‚ะต ัะฝะพะฒะฐ.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'ะžั‚ะผะตะฝะฐ', 'common.save' => 'ะกะพั…ั€ะฐะฝะธั‚ัŒ', 'common.close' => 'ะ—ะฐะบั€ั‹ั‚ัŒ', @@ -2239,8 +2252,6 @@ extension on TranslationsRu { 'settings.libraryDensity' => 'ะŸะปะพั‚ะฝะพัั‚ัŒ ะฑะธะฑะปะธะพั‚ะตะบะธ', 'settings.compact' => 'ะšะพะผะฟะฐะบั‚ะฝั‹ะน', 'settings.comfortable' => 'ะšะพะผั„ะพั€ั‚ะฝั‹ะน', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'ะ ะตะถะธะผ ะฟั€ะพัะผะพั‚ั€ะฐ', 'settings.gridView' => 'ะกะตั‚ะบะฐ', 'settings.listView' => 'ะกะฟะธัะพะบ', @@ -2310,7 +2321,7 @@ extension on TranslationsRu { 'settings.watchTogetherRelay' => 'Relay ัะพะฒะผะตัั‚ะฝะพะณะพ ะฟั€ะพัะผะพั‚ั€ะฐ', 'settings.watchTogetherRelayDescription' => 'ะ—ะฐะดะฐะนั‚ะต ัะฒะพะน relay. ะ’ัะต ะดะพะปะถะฝั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะดะธะฝ ัะตั€ะฒะตั€.', 'settings.watchTogetherRelayHint' => 'https://my-relay.example.com', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'ะžั‚ั‡ั‘ั‚ั‹ ะพะฑ ะพัˆะธะฑะบะฐั…', 'settings.crashReportingDescription' => 'ะžั‚ะฟั€ะฐะฒะปัั‚ัŒ ะพั‚ั‡ั‘ั‚ั‹ ะพะฑ ะพัˆะธะฑะบะฐั… ะดะปั ัƒะปัƒั‡ัˆะตะฝะธั ะฟั€ะธะปะพะถะตะฝะธั', 'settings.debugLogging' => 'ะ–ัƒั€ะฝะฐะป ะพั‚ะปะฐะดะบะธ', @@ -2327,12 +2338,10 @@ extension on TranslationsRu { 'settings.exportSettings' => 'ะญะบัะฟะพั€ั‚ ะฝะฐัั‚ั€ะพะตะบ', 'settings.exportSettingsDescription' => 'ะกะพั…ั€ะฐะฝะธั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ ะฒ ั„ะฐะนะป', 'settings.exportSettingsSuccess' => 'ะะฐัั‚ั€ะพะนะบะธ ัะบัะฟะพั€ั‚ะธั€ะพะฒะฐะฝั‹', - 'settings.exportSettingsFailed' => 'ะะต ัƒะดะฐะปะพััŒ ัะบัะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ', 'settings.importSettings' => 'ะ˜ะผะฟะพั€ั‚ ะฝะฐัั‚ั€ะพะตะบ', 'settings.importSettingsDescription' => 'ะ’ะพััั‚ะฐะฝะพะฒะธั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ ะธะท ั„ะฐะนะปะฐ', 'settings.importSettingsConfirm' => 'ะญั‚ะพ ะทะฐะผะตะฝะธั‚ ะฒะฐัˆะธ ั‚ะตะบัƒั‰ะธะต ะฝะฐัั‚ั€ะพะนะบะธ. ะŸั€ะพะดะพะปะถะธั‚ัŒ?', 'settings.importSettingsSuccess' => 'ะะฐัั‚ั€ะพะนะบะธ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะฝั‹', - 'settings.importSettingsFailed' => 'ะะต ัƒะดะฐะปะพััŒ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฝะฐัั‚ั€ะพะนะบะธ', 'settings.importSettingsInvalidFile' => 'ะญั‚ะพั‚ ั„ะฐะนะป ะฝะต ัะฒะปัะตั‚ัั ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะผ ัะบัะฟะพั€ั‚ะพะผ ะฝะฐัั‚ั€ะพะตะบ Plezy', 'settings.importSettingsNoUser' => 'ะ’ะพะนะดะธั‚ะต ะฒ ัะธัั‚ะตะผัƒ ะฟะตั€ะตะด ะธะผะฟะพั€ั‚ะพะผ ะฝะฐัั‚ั€ะพะตะบ', 'settings.shortcutsReset' => 'ะ“ะพั€ัั‡ะธะต ะบะปะฐะฒะธัˆะธ ัะฑั€ะพัˆะตะฝั‹ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ', @@ -2347,6 +2356,7 @@ extension on TranslationsRu { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'ะ”ะปะธั‚ะตะปัŒะฝะพัั‚ัŒ ะดะพะปะถะฝะฐ ะฑั‹ั‚ัŒ ะพั‚ ${min} ะดะพ ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'ะšะปะฐะฒะธัˆะฐ ัƒะถะต ะฝะฐะทะฝะฐั‡ะตะฝะฐ ะดะปั ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'ะšะปะฐะฒะธัˆะฐ ะพะฑะฝะพะฒะปะตะฝะฐ ะดะปั ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'ะะฒั‚ะพะฟั€ะพะฟัƒัะบ', 'settings.autoSkipIntro' => 'ะะฒั‚ะพะฟั€ะพะฟัƒัะบ ะฒัั‚ัƒะฟะปะตะฝะธั', 'settings.autoSkipIntroDescription' => 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟั€ะพะฟัƒัะบะฐั‚ัŒ ะผะฐั€ะบะตั€ั‹ ะฒัั‚ัƒะฟะปะตะฝะธั ั‡ะตั€ะตะท ะฝะตัะบะพะปัŒะบะพ ัะตะบัƒะฝะด', @@ -2372,7 +2382,7 @@ extension on TranslationsRu { 'settings.downloadLocationChanged' => 'ะœะตัั‚ะพ ะทะฐะณั€ัƒะทะบะธ ะธะทะผะตะฝะตะฝะพ', 'settings.downloadLocationReset' => 'ะœะตัั‚ะพ ะทะฐะณั€ัƒะทะบะธ ัะฑั€ะพัˆะตะฝะพ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ', 'settings.downloadLocationInvalid' => 'ะ’ั‹ะฑั€ะฐะฝะฝะฐั ะฟะฐะฟะบะฐ ะฝะตะดะพัั‚ัƒะฟะฝะฐ ะดะปั ะทะฐะฟะธัะธ', - 'settings.downloadLocationSelectError' => 'ะะต ัƒะดะฐะปะพััŒ ะฒั‹ะฑั€ะฐั‚ัŒ ะฟะฐะฟะบัƒ', + 'settings.downloadLocationPickerUnavailable' => 'ะ’ั‹ะฑะพั€ ะฟะฐะฟะบะธ ะฝะตะดะพัั‚ัƒะฟะตะฝ ะฝะฐ ัั‚ะพะผ ัƒัั‚ั€ะพะนัั‚ะฒะต', 'settings.downloadOnWifiOnly' => 'ะ—ะฐะณั€ัƒะถะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟะพ WiFi', 'settings.downloadOnWifiOnlyDescription' => 'ะ—ะฐะฟั€ะตั‚ะธั‚ัŒ ะทะฐะณั€ัƒะทะบัƒ ะฟะพ ะผะพะฑะธะปัŒะฝั‹ะผ ะดะฐะฝะฝั‹ะผ', 'settings.autoRemoveWatchedDownloads' => 'ะะฒั‚ะพัƒะดะฐะปะตะฝะธะต ะฟั€ะพัะผะพั‚ั€ะตะฝะฝั‹ั… ะทะฐะณั€ัƒะทะพะบ', @@ -2560,10 +2570,15 @@ extension on TranslationsRu { 'accessibility.hexColor' => 'ะจะตัั‚ะฝะฐะดั†ะฐั‚ะตั€ะธั‡ะฝั‹ะน ั†ะฒะตั‚', 'accessibility.expandText' => 'ะ ะฐะทะฒะตั€ะฝัƒั‚ัŒ ั‚ะตะบัั‚', 'accessibility.collapseText' => 'ะกะฒะตั€ะฝัƒั‚ัŒ ั‚ะตะบัั‚', + 'accessibility.alphabetNavigation' => 'ะะฐะฒะธะณะฐั†ะธั ะฟะพ ะฐะปั„ะฐะฒะธั‚ัƒ', + 'accessibility.alphabetScrollHint' => 'ะŸั€ะพะฒะตะดะธั‚ะต ะฒะฒะตั€ั… ะธะปะธ ะฒะฝะธะท ะดะปั ะฟะตั€ะตั…ะพะดะฐ ะฟะพ ะฑัƒะบะฒะฐะผ', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'ะกั‚ั€ะพะบะฐ ${row} ะธะท ${rowCount}, ัั‚ะพะปะฑะตั† ${column} ะธะท ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'ะกั‚ั€ะพะบะฐ ${row} ะธะท ${rowCount}', 'tooltips.shufflePlay' => 'ะกะปัƒั‡ะฐะนะฝะพะต ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต', 'tooltips.playTrailer' => 'ะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธ ั‚ั€ะตะนะปะตั€', 'tooltips.markAsWatched' => 'ะžั‚ะผะตั‚ะธั‚ัŒ ะบะฐะบ ะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะต', 'tooltips.markAsUnwatched' => 'ะžั‚ะผะตั‚ะธั‚ัŒ ะบะฐะบ ะฝะตะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะต', + 'audioTracks.track' => ({required Object n}) => 'ะัƒะดะธะพะดะพั€ะพะถะบะฐ ${n}', 'videoControls.audioLabel' => 'ะัƒะดะธะพ', 'videoControls.subtitlesLabel' => 'ะกัƒะฑั‚ะธั‚ั€ั‹', 'videoControls.resetToZero' => 'ะกะฑั€ะพัะธั‚ัŒ ะดะพ 0ะผั', @@ -2650,20 +2665,20 @@ extension on TranslationsRu { 'messages.markedAsUnwatched' => 'ะžั‚ะผะตั‡ะตะฝะพ ะบะฐะบ ะฝะตะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะต', 'messages.markedAsWatchedOffline' => 'ะžั‚ะผะตั‡ะตะฝะพ ะบะฐะบ ะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะต (ัะธะฝั…ั€ะพะฝะธะทะธั€ัƒะตั‚ัั ะฟั€ะธ ะฟะพะดะบะปัŽั‡ะตะฝะธะธ)', 'messages.markedAsUnwatchedOffline' => 'ะžั‚ะผะตั‡ะตะฝะพ ะบะฐะบ ะฝะตะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะต (ัะธะฝั…ั€ะพะฝะธะทะธั€ัƒะตั‚ัั ะฟั€ะธ ะฟะพะดะบะปัŽั‡ะตะฝะธะธ)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'ะะฒั‚ะพัƒะดะฐะปะตะฝะพ: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n, one: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปะตะฝะฐ ${n} ะฟั€ะพัะผะพั‚ั€ะตะฝะฝะฐั ะทะฐะณั€ัƒะทะบะฐ', few: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปะตะฝั‹ ${n} ะฟั€ะพัะผะพั‚ั€ะตะฝะฝั‹ะต ะทะฐะณั€ัƒะทะบะธ', many: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปะตะฝะพ ${n} ะฟั€ะพัะผะพั‚ั€ะตะฝะฝั‹ั… ะทะฐะณั€ัƒะทะพะบ', other: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปะตะฝะพ ${n} ะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะน ะทะฐะณั€ัƒะทะบะธ', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'ะะฒั‚ะพัƒะดะฐะปะตะฝะพ: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n, one: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปะตะฝะฐ ${n} ะฟั€ะพัะผะพั‚ั€ะตะฝะฝะฐั ะทะฐะณั€ัƒะทะบะฐ', few: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปะตะฝั‹ ${n} ะฟั€ะพัะผะพั‚ั€ะตะฝะฝั‹ะต ะทะฐะณั€ัƒะทะบะธ', many: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปะตะฝะพ ${n} ะฟั€ะพัะผะพั‚ั€ะตะฝะฝั‹ั… ะทะฐะณั€ัƒะทะพะบ', other: 'ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัƒะดะฐะปะตะฝะพ ${n} ะฟั€ะพัะผะพั‚ั€ะตะฝะฝะพะน ะทะฐะณั€ัƒะทะบะธ', ), 'messages.removedFromContinueWatching' => 'ะฃะดะฐะปะตะฝะพ ะธะท ยซะŸั€ะพะดะพะปะถะธั‚ัŒ ะฟั€ะพัะผะพั‚ั€ยป', 'messages.errorLoading' => ({required Object error}) => 'ะžัˆะธะฑะบะฐ: ${error}', 'messages.streamInterrupted' => 'ะŸะพั‚ะพะบ ะฟั€ะตั€ะฒะฐะปัั. ะะฐะถะผะธั‚ะต ยซะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธยป ะธะปะธ ะฟะตั€ะตะผะพั‚ะฐะนั‚ะต, ั‡ั‚ะพะฑั‹ ะฟะพะฒั‚ะพั€ะธั‚ัŒ ะฟะพะฟั‹ั‚ะบัƒ.', 'messages.liveStreamInterrupted' => 'ะŸั€ัะผะฐั ั‚ั€ะฐะฝัะปัั†ะธั ะฟั€ะตั€ะฒะฐะปะฐััŒ. ะะฐะถะผะธั‚ะต ยซะ’ะพัะฟั€ะพะธะทะฒะตัั‚ะธยป, ั‡ั‚ะพะฑั‹ ะฟะพะฒั‚ะพั€ะธั‚ัŒ ะฟะพะฟั‹ั‚ะบัƒ.', 'messages.fileInfoNotAvailable' => 'ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ั„ะฐะนะปะต ะฝะตะดะพัั‚ัƒะฟะฝะฐ', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'ะžัˆะธะฑะบะฐ ะทะฐะณั€ัƒะทะบะธ ะธะฝั„ะพั€ะผะฐั†ะธะธ ะพ ั„ะฐะนะปะต: ${error}', 'messages.errorLoadingSeries' => 'ะžัˆะธะฑะบะฐ ะทะฐะณั€ัƒะทะบะธ ัะตั€ะธะฐะปะฐ', 'messages.musicNotSupported' => 'ะ’ะพัะฟั€ะพะธะทะฒะตะดะตะฝะธะต ะผัƒะทั‹ะบะธ ะฟะพะบะฐ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ัั', @@ -2778,6 +2793,7 @@ extension on TranslationsRu { 'profiles.borrowExplain' => 'ะ—ะฐะธะผัั‚ะฒัƒะนั‚ะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะดั€ัƒะณะพะณะพ ะฟั€ะพั„ะธะปั. ะ”ะปั ะฟั€ะพั„ะธะปะตะน ั PIN ะฝัƒะถะตะฝ PIN.', 'profiles.borrowEmpty' => 'ะŸะพะบะฐ ะฝะตั‡ะตะณะพ ะทะฐะธะผัั‚ะฒะพะฒะฐั‚ัŒ.', 'profiles.borrowEmptySubtitle' => 'ะกะฝะฐั‡ะฐะปะฐ ะฟะพะดะบะปัŽั‡ะธั‚ะต Plex ะธะปะธ Jellyfin ะบ ะดั€ัƒะณะพะผัƒ ะฟั€ะพั„ะธะปัŽ.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'ะ˜ะท ${displayName}', 'profiles.borrowConnectionBorrowed' => 'ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะทะฐะธะผัั‚ะฒะพะฒะฐะฝะพ.', 'profiles.borrowFailed' => 'ะะต ัƒะดะฐะปะพััŒ ะทะฐะธะผัั‚ะฒะพะฒะฐั‚ัŒ ะฟะพะดะบะปัŽั‡ะตะฝะธะต.', @@ -3003,7 +3019,7 @@ extension on TranslationsRu { 'liveTv.favorites' => 'ะ˜ะทะฑั€ะฐะฝะฝะพะต', 'liveTv.reorderFavorites' => 'ะ˜ะทะผะตะฝะธั‚ัŒ ะฟะพั€ัะดะพะบ ะธะทะฑั€ะฐะฝะฝะพะณะพ', 'liveTv.favoritesLoadFailed' => 'ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ ะธะทะฑั€ะฐะฝะฝะพะต. ะŸั€ะพะฒะตั€ัŒั‚ะต ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะธ ะฟะพะฒั‚ะพั€ะธั‚ะต ะฟะพะฟั‹ั‚ะบัƒ.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'ะŸั€ะธัะพะตะดะธะฝะธั‚ัŒัั ะบ ั‚ะตะบัƒั‰ะตะผัƒ ัะตะฐะฝััƒ', 'liveTv.watchFromStart' => ({required Object minutes}) => 'ะกะผะพั‚ั€ะตั‚ัŒ ัะฝะฐั‡ะฐะปะฐ (${minutes} ะผะธะฝ. ะฝะฐะทะฐะด)', 'liveTv.watchLive' => 'ะกะผะพั‚ั€ะตั‚ัŒ ะฒ ะฟั€ัะผะพะผ ัั„ะธั€ะต', @@ -3163,11 +3179,11 @@ extension on TranslationsRu { 'watchTogether.participantLeft' => ({required Object name}) => '${name} ะฒั‹ัˆะตะป', 'watchTogether.participantPaused' => ({required Object name}) => '${name} ะฟะพัั‚ะฐะฒะธะป ะฝะฐ ะฟะฐัƒะทัƒ', 'watchTogether.participantResumed' => ({required Object name}) => '${name} ะฒะพะทะพะฑะฝะพะฒะธะป', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} ะฟะตั€ะตะผะพั‚ะฐะป', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} ะฑัƒั„ะตั€ะธะทัƒะตั‚', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} ะธัะฟะพะปัŒะทัƒะตั‚ ัั‚ะฐั€ัƒัŽ ะฒะตั€ัะธัŽ ะฟั€ะธะปะพะถะตะฝะธั โ€” ัะธะฝั…ั€ะพะฝะธะทะฐั†ะธั ะฝะตะดะพัั‚ัƒะฟะฝะฐ', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'ะ’ะพะทะพะฑะฝะพะฒะปะตะฝะธะต ะฑะตะท ${name}', 'watchTogether.waitingForParticipants' => 'ะžะถะธะดะฐะฝะธะต ะทะฐะณั€ัƒะทะบะธ ัƒ ะดั€ัƒะณะธั…...', 'watchTogether.waitingForName' => ({required Object name}) => 'ะžะถะธะดะฐะฝะธะต ${name}...', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index ccab5580..e338da05 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -52,6 +52,7 @@ class TranslationsSv extends Translations with BaseTranslations 'Vรคntar pรฅ godkรคnnandeโ€ฆ'; @override String get quickConnectCancel => 'Avbryt'; @override String get quickConnectExpired => 'Quick Connect har gรฅtt ut. Fรถrsรถk igen.'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsSv extends TranslationsSettingsEn { @override String get libraryDensity => 'Biblioteksdensitet'; @override String get compact => 'Kompakt'; @override String get comfortable => 'Bekvรคm'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => 'Visningslรคge'; @override String get gridView => 'Rutnรคt'; @override String get listView => 'Lista'; @@ -311,7 +310,7 @@ class _TranslationsSettingsSv extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'Titta Tillsammans-relay'; @override String get watchTogetherRelayDescription => 'Ange en anpassad relay. Alla mรฅste anvรคnda samma server.'; @override String get watchTogetherRelayHint => 'https://min-relay.exempel.se'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'Kraschrapportering'; @override String get crashReportingDescription => 'Skicka kraschrapporter fรถr att fรถrbรคttra appen'; @override String get debugLogging => 'Felsรถkningsloggning'; @@ -328,12 +327,10 @@ class _TranslationsSettingsSv extends TranslationsSettingsEn { @override String get exportSettings => 'Exportera instรคllningar'; @override String get exportSettingsDescription => 'Spara dina instรคllningar till en fil'; @override String get exportSettingsSuccess => 'Instรคllningar exporterade'; - @override String get exportSettingsFailed => 'Kunde inte exportera instรคllningar'; @override String get importSettings => 'Importera instรคllningar'; @override String get importSettingsDescription => 'ร…terstรคll instรคllningar frรฅn en fil'; @override String get importSettingsConfirm => 'Detta ersรคtter dina nuvarande instรคllningar. Fortsรคtta?'; @override String get importSettingsSuccess => 'Instรคllningar importerade'; - @override String get importSettingsFailed => 'Kunde inte importera instรคllningar'; @override String get importSettingsInvalidFile => 'Denna fil รคr inte en giltig Plezy-export'; @override String get importSettingsNoUser => 'Logga in innan du importerar instรคllningar'; @override String get shortcutsReset => 'Genvรคgar รฅterstรคllda till standard'; @@ -348,6 +345,7 @@ class _TranslationsSettingsSv extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Tiden mรฅste vara mellan ${min} och ${max} ${unit}'; @override String shortcutAlreadyAssigned({required Object action}) => 'Genvรคg redan tilldelad ${action}'; @override String shortcutUpdated({required Object action}) => 'Genvรคg uppdaterad fรถr ${action}'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => 'Auto Hoppa ร–ver'; @override String get autoSkipIntro => 'Hoppa ร–ver Intro Automatiskt'; @override String get autoSkipIntroDescription => 'Hoppa automatiskt รถver intro-markรถrer efter nรฅgra sekunder'; @@ -373,7 +371,7 @@ class _TranslationsSettingsSv extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'Nedladdningsplats รคndrad'; @override String get downloadLocationReset => 'Nedladdningsplats รฅterstรคlld till standard'; @override String get downloadLocationInvalid => 'Vald mapp รคr inte skrivbar'; - @override String get downloadLocationSelectError => 'Kunde inte vรคlja mapp'; + @override String get downloadLocationPickerUnavailable => 'Mappval รคr inte tillgรคngligt pรฅ den hรคr enheten'; @override String get downloadOnWifiOnly => 'Ladda ner endast pรฅ WiFi'; @override String get downloadOnWifiOnlyDescription => 'Fรถrhindra nedladdningar vid anvรคndning av mobildata'; @override String get autoRemoveWatchedDownloads => 'Ta bort sedda nedladdningar automatiskt'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilitySv extends TranslationsAccessibilityEn { @override String get hexColor => 'Hexfรคrg'; @override String get expandText => 'Expandera text'; @override String get collapseText => 'Fรคll ihop text'; + @override String get alphabetNavigation => 'Alfabetisk navigering'; + @override String get alphabetScrollHint => 'Svep upp eller ned fรถr att flytta en bokstav'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Rad ${row} av ${rowCount}, kolumn ${column} av ${columnCount}'; + @override String rowPosition({required Object row, required Object rowCount}) => 'Rad ${row} av ${rowCount}'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsSv extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'Markera som osedd'; } +// Path: audioTracks +class _TranslationsAudioTracksSv extends TranslationsAudioTracksEn { + _TranslationsAudioTracksSv._(TranslationsSv root) : this._root = root, super.internal(root); + + final TranslationsSv _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => 'Ljudspรฅr ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsSv extends TranslationsVideoControlsEn { _TranslationsVideoControlsSv._(TranslationsSv root) : this._root = root, super.internal(root); @@ -712,11 +724,11 @@ class _TranslationsMessagesSv extends TranslationsMessagesEn { @override String get streamInterrupted => 'Uppspelningen avbrรถts. Tryck pรฅ play eller spola fรถr att fรถrsรถka igen.'; @override String get liveStreamInterrupted => 'Livestreamen avbrรถts. Tryck pรฅ play fรถr att fรถrsรถka igen.'; @override String get fileInfoNotAvailable => 'Filinformation inte tillgรคnglig'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'Fel vid laddning av filinformation: ${error}'; @override String get errorLoadingSeries => 'Fel vid laddning av serie'; @override String get musicNotSupported => 'Musikuppspelning stรถds inte รคnnu'; @@ -867,6 +879,7 @@ class _TranslationsProfilesSv extends TranslationsProfilesEn { @override String get borrowExplain => 'Lรฅna en annan profils anslutning. PIN-skyddade profiler krรคver en PIN.'; @override String get borrowEmpty => 'Inget att lรฅna รคnnu.'; @override String get borrowEmptySubtitle => 'Anslut Plex eller Jellyfin till en annan profil fรถrst.'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'Frรฅn ${displayName}'; @override String get borrowConnectionBorrowed => 'Anslutning lรฅnad.'; @override String get borrowFailed => 'Kunde inte lรฅna anslutningen.'; @@ -1150,7 +1163,7 @@ class _TranslationsLiveTvSv extends TranslationsLiveTvEn { @override String get favorites => 'Favoriter'; @override String get reorderFavorites => 'Ordna om favoriter'; @override String get favoritesLoadFailed => 'Det gick inte att lรคsa in favoriter. Kontrollera anslutningen och fรถrsรถk igen.'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'Gรฅ med i pรฅgรฅende session'; @override String watchFromStart({required Object minutes}) => 'Titta frรฅn bรถrjan (${minutes} min sedan)'; @override String get watchLive => 'Titta live'; @@ -2149,7 +2162,7 @@ extension on TranslationsSv { 'auth.quickConnectWaiting' => 'Vรคntar pรฅ godkรคnnandeโ€ฆ', 'auth.quickConnectCancel' => 'Avbryt', 'auth.quickConnectExpired' => 'Quick Connect har gรฅtt ut. Fรถrsรถk igen.', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'Avbryt', 'common.save' => 'Spara', 'common.close' => 'Stรคng', @@ -2233,8 +2246,6 @@ extension on TranslationsSv { 'settings.libraryDensity' => 'Biblioteksdensitet', 'settings.compact' => 'Kompakt', 'settings.comfortable' => 'Bekvรคm', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => 'Visningslรคge', 'settings.gridView' => 'Rutnรคt', 'settings.listView' => 'Lista', @@ -2304,7 +2315,7 @@ extension on TranslationsSv { 'settings.watchTogetherRelay' => 'Titta Tillsammans-relay', 'settings.watchTogetherRelayDescription' => 'Ange en anpassad relay. Alla mรฅste anvรคnda samma server.', 'settings.watchTogetherRelayHint' => 'https://min-relay.exempel.se', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'Kraschrapportering', 'settings.crashReportingDescription' => 'Skicka kraschrapporter fรถr att fรถrbรคttra appen', 'settings.debugLogging' => 'Felsรถkningsloggning', @@ -2321,12 +2332,10 @@ extension on TranslationsSv { 'settings.exportSettings' => 'Exportera instรคllningar', 'settings.exportSettingsDescription' => 'Spara dina instรคllningar till en fil', 'settings.exportSettingsSuccess' => 'Instรคllningar exporterade', - 'settings.exportSettingsFailed' => 'Kunde inte exportera instรคllningar', 'settings.importSettings' => 'Importera instรคllningar', 'settings.importSettingsDescription' => 'ร…terstรคll instรคllningar frรฅn en fil', 'settings.importSettingsConfirm' => 'Detta ersรคtter dina nuvarande instรคllningar. Fortsรคtta?', 'settings.importSettingsSuccess' => 'Instรคllningar importerade', - 'settings.importSettingsFailed' => 'Kunde inte importera instรคllningar', 'settings.importSettingsInvalidFile' => 'Denna fil รคr inte en giltig Plezy-export', 'settings.importSettingsNoUser' => 'Logga in innan du importerar instรคllningar', 'settings.shortcutsReset' => 'Genvรคgar รฅterstรคllda till standard', @@ -2341,6 +2350,7 @@ extension on TranslationsSv { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'Tiden mรฅste vara mellan ${min} och ${max} ${unit}', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'Genvรคg redan tilldelad ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'Genvรคg uppdaterad fรถr ${action}', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => 'Auto Hoppa ร–ver', 'settings.autoSkipIntro' => 'Hoppa ร–ver Intro Automatiskt', 'settings.autoSkipIntroDescription' => 'Hoppa automatiskt รถver intro-markรถrer efter nรฅgra sekunder', @@ -2366,7 +2376,7 @@ extension on TranslationsSv { 'settings.downloadLocationChanged' => 'Nedladdningsplats รคndrad', 'settings.downloadLocationReset' => 'Nedladdningsplats รฅterstรคlld till standard', 'settings.downloadLocationInvalid' => 'Vald mapp รคr inte skrivbar', - 'settings.downloadLocationSelectError' => 'Kunde inte vรคlja mapp', + 'settings.downloadLocationPickerUnavailable' => 'Mappval รคr inte tillgรคngligt pรฅ den hรคr enheten', 'settings.downloadOnWifiOnly' => 'Ladda ner endast pรฅ WiFi', 'settings.downloadOnWifiOnlyDescription' => 'Fรถrhindra nedladdningar vid anvรคndning av mobildata', 'settings.autoRemoveWatchedDownloads' => 'Ta bort sedda nedladdningar automatiskt', @@ -2554,10 +2564,15 @@ extension on TranslationsSv { 'accessibility.hexColor' => 'Hexfรคrg', 'accessibility.expandText' => 'Expandera text', 'accessibility.collapseText' => 'Fรคll ihop text', + 'accessibility.alphabetNavigation' => 'Alfabetisk navigering', + 'accessibility.alphabetScrollHint' => 'Svep upp eller ned fรถr att flytta en bokstav', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => 'Rad ${row} av ${rowCount}, kolumn ${column} av ${columnCount}', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => 'Rad ${row} av ${rowCount}', 'tooltips.shufflePlay' => 'Blanda uppspelning', 'tooltips.playTrailer' => 'Spela trailer', 'tooltips.markAsWatched' => 'Markera som sedd', 'tooltips.markAsUnwatched' => 'Markera som osedd', + 'audioTracks.track' => ({required Object n}) => 'Ljudspรฅr ${n}', 'videoControls.audioLabel' => 'Ljud', 'videoControls.subtitlesLabel' => 'Undertexter', 'videoControls.resetToZero' => 'ร…terstรคll till 0ms', @@ -2644,20 +2659,20 @@ extension on TranslationsSv { 'messages.markedAsUnwatched' => 'Markerad som osedd', 'messages.markedAsWatchedOffline' => 'Markerad som sedd (synkroniseras nรคr online)', 'messages.markedAsUnwatchedOffline' => 'Markerad som osedd (synkroniseras nรคr online)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatiskt borttagen: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('sv'))(n, one: 'Tog automatiskt bort ${n} sedd nedladdning', other: 'Tog automatiskt bort ${n} sedda nedladdningar', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'Automatiskt borttagen: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('sv'))(n, one: 'Tog automatiskt bort ${n} sedd nedladdning', other: 'Tog automatiskt bort ${n} sedda nedladdningar', ), 'messages.removedFromContinueWatching' => 'Borttagen frรฅn Fortsรคtt titta', 'messages.errorLoading' => ({required Object error}) => 'Fel: ${error}', 'messages.streamInterrupted' => 'Uppspelningen avbrรถts. Tryck pรฅ play eller spola fรถr att fรถrsรถka igen.', 'messages.liveStreamInterrupted' => 'Livestreamen avbrรถts. Tryck pรฅ play fรถr att fรถrsรถka igen.', 'messages.fileInfoNotAvailable' => 'Filinformation inte tillgรคnglig', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fel vid laddning av filinformation: ${error}', 'messages.errorLoadingSeries' => 'Fel vid laddning av serie', 'messages.musicNotSupported' => 'Musikuppspelning stรถds inte รคnnu', @@ -2772,6 +2787,7 @@ extension on TranslationsSv { 'profiles.borrowExplain' => 'Lรฅna en annan profils anslutning. PIN-skyddade profiler krรคver en PIN.', 'profiles.borrowEmpty' => 'Inget att lรฅna รคnnu.', 'profiles.borrowEmptySubtitle' => 'Anslut Plex eller Jellyfin till en annan profil fรถrst.', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'Frรฅn ${displayName}', 'profiles.borrowConnectionBorrowed' => 'Anslutning lรฅnad.', 'profiles.borrowFailed' => 'Kunde inte lรฅna anslutningen.', @@ -2997,7 +3013,7 @@ extension on TranslationsSv { 'liveTv.favorites' => 'Favoriter', 'liveTv.reorderFavorites' => 'Ordna om favoriter', 'liveTv.favoritesLoadFailed' => 'Det gick inte att lรคsa in favoriter. Kontrollera anslutningen och fรถrsรถk igen.', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'Gรฅ med i pรฅgรฅende session', 'liveTv.watchFromStart' => ({required Object minutes}) => 'Titta frรฅn bรถrjan (${minutes} min sedan)', 'liveTv.watchLive' => 'Titta live', @@ -3157,11 +3173,11 @@ extension on TranslationsSv { 'watchTogether.participantLeft' => ({required Object name}) => '${name} lรคmnade', 'watchTogether.participantPaused' => ({required Object name}) => '${name} pausade', 'watchTogether.participantResumed' => ({required Object name}) => '${name} รฅterupptog', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} spolade', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} buffrar', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} anvรคnder en รคldre appversion โ€” synkronisering รคr inte tillgรคnglig', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'ร…terupptar utan ${name}', 'watchTogether.waitingForParticipants' => 'Vรคntar pรฅ att andra laddar...', 'watchTogether.waitingForName' => ({required Object name}) => 'Vรคntar pรฅ ${name}...', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index b7a3b098..95354c9b 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -52,6 +52,7 @@ class TranslationsZh extends Translations with BaseTranslations '็ญ‰ๅพ…ๆ‰นๅ‡†โ€ฆ'; @override String get quickConnectCancel => 'ๅ–ๆถˆ'; @override String get quickConnectExpired => 'Quick Connect ๅทฒ่ฟ‡ๆœŸใ€‚่ฏท้‡่ฏ•ใ€‚'; - @override String get localDataRecoveryRequired => ''; + @override String get localDataRecoveryRequired => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.'; } // Path: common @@ -240,8 +241,6 @@ class _TranslationsSettingsZh extends TranslationsSettingsEn { @override String get libraryDensity => 'ๅช’ไฝ“ๅบ“ๅฏ†ๅบฆ'; @override String get compact => '็ดงๅ‡‘'; @override String get comfortable => '่ˆ’้€‚'; - @override String get tvCornerSpotlightBackdrop => ''; - @override String get tvCornerSpotlightBackdropDescription => ''; @override String get viewMode => '่ง†ๅ›พๆจกๅผ'; @override String get gridView => '็ฝ‘ๆ ผ่ง†ๅ›พ'; @override String get listView => 'ๅˆ—่กจ่ง†ๅ›พ'; @@ -311,7 +310,7 @@ class _TranslationsSettingsZh extends TranslationsSettingsEn { @override String get watchTogetherRelay => 'ไธ€่ตท็œ‹ไธญ็ปงๆœๅŠกๅ™จ'; @override String get watchTogetherRelayDescription => '่ฎพ็ฝฎ่‡ชๅฎšไน‰ไธญ็ปงใ€‚ๆ‰€ๆœ‰ไบบๅฟ…้กปไฝฟ็”จๅŒไธ€ๆœๅŠกๅ™จใ€‚'; @override String get watchTogetherRelayHint => 'https://my-relay.example.com'; - @override String get watchTogetherRelayInvalid => ''; + @override String get watchTogetherRelayInvalid => 'Enter a valid HTTP or HTTPS relay base URL.'; @override String get crashReporting => 'ๅดฉๆบƒๆŠฅๅ‘Š'; @override String get crashReportingDescription => 'ๅ‘้€ๅดฉๆบƒๆŠฅๅ‘ŠไปฅๅธฎๅŠฉๆ”น่ฟ›ๅบ”็”จ'; @override String get debugLogging => '่ฐƒ่ฏ•ๆ—ฅๅฟ—'; @@ -328,12 +327,10 @@ class _TranslationsSettingsZh extends TranslationsSettingsEn { @override String get exportSettings => 'ๅฏผๅ‡บ่ฎพ็ฝฎ'; @override String get exportSettingsDescription => 'ๅฐ†ๆ‚จ็š„ๅๅฅฝไฟๅญ˜ๅˆฐๆ–‡ไปถ'; @override String get exportSettingsSuccess => '่ฎพ็ฝฎๅทฒๅฏผๅ‡บ'; - @override String get exportSettingsFailed => 'ๆ— ๆณ•ๅฏผๅ‡บ่ฎพ็ฝฎ'; @override String get importSettings => 'ๅฏผๅ…ฅ่ฎพ็ฝฎ'; @override String get importSettingsDescription => 'ไปŽๆ–‡ไปถๆขๅคๅๅฅฝ'; @override String get importSettingsConfirm => '่ฟ™ๅฐ†ๆ›ฟๆขๆ‚จๅฝ“ๅ‰็š„่ฎพ็ฝฎใ€‚็ปง็ปญๅ—๏ผŸ'; @override String get importSettingsSuccess => '่ฎพ็ฝฎๅทฒๅฏผๅ…ฅ'; - @override String get importSettingsFailed => 'ๆ— ๆณ•ๅฏผๅ…ฅ่ฎพ็ฝฎ'; @override String get importSettingsInvalidFile => 'ๆญคๆ–‡ไปถไธๆ˜ฏๆœ‰ๆ•ˆ็š„ Plezy ่ฎพ็ฝฎๅฏผๅ‡บ'; @override String get importSettingsNoUser => 'ๅฏผๅ…ฅ่ฎพ็ฝฎๅ‰่ฏทๅ…ˆ็™ปๅฝ•'; @override String get shortcutsReset => 'ๅฟซๆท้”ฎๅทฒ้‡็ฝฎไธบ้ป˜่ฎคๅ€ผ'; @@ -348,6 +345,7 @@ class _TranslationsSettingsZh extends TranslationsSettingsEn { @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'ๆ—ถ้•ฟๅฟ…้กปไป‹ไบŽ ${min} ๅ’Œ ${max} ${unit} ไน‹้—ด'; @override String shortcutAlreadyAssigned({required Object action}) => 'ๅฟซๆท้”ฎๅทฒ่ขซๅˆ†้…็ป™ ${action}'; @override String shortcutUpdated({required Object action}) => 'ๅฟซๆท้”ฎๅทฒไธบ ${action} ๆ›ดๆ–ฐ'; + @override String get saveFailed => 'Could not save changes. Try again.'; @override String get autoSkip => '่‡ชๅŠจ่ทณ่ฟ‡'; @override String get autoSkipIntro => '่‡ชๅŠจ่ทณ่ฟ‡็‰‡ๅคด'; @override String get autoSkipIntroDescription => 'ๅ‡ ็ง’้’ŸๅŽ่‡ชๅŠจ่ทณ่ฟ‡็‰‡ๅคดๆ ‡่ฎฐ'; @@ -373,7 +371,7 @@ class _TranslationsSettingsZh extends TranslationsSettingsEn { @override String get downloadLocationChanged => 'ไธ‹่ฝฝไฝ็ฝฎๅทฒๆ›ดๆ”น'; @override String get downloadLocationReset => 'ไธ‹่ฝฝไฝ็ฝฎๅทฒ้‡็ฝฎไธบ้ป˜่ฎค'; @override String get downloadLocationInvalid => 'ๆ‰€้€‰ๆ–‡ไปถๅคนไธๅฏๅ†™ๅ…ฅ'; - @override String get downloadLocationSelectError => '้€‰ๆ‹ฉๆ–‡ไปถๅคนๅคฑ่ดฅ'; + @override String get downloadLocationPickerUnavailable => 'ๆญค่ฎพๅค‡ไธๆ”ฏๆŒ้€‰ๆ‹ฉๆ–‡ไปถๅคน'; @override String get downloadOnWifiOnly => 'ไป…ๅœจ WiFi ๆ—ถไธ‹่ฝฝ'; @override String get downloadOnWifiOnlyDescription => 'ไฝฟ็”จ่œ‚็ชๆ•ฐๆฎๆ—ถ็ฆๆญขไธ‹่ฝฝ'; @override String get autoRemoveWatchedDownloads => '่‡ชๅŠจ็งป้™คๅทฒ่ง‚็œ‹็š„ไธ‹่ฝฝ'; @@ -591,6 +589,10 @@ class _TranslationsAccessibilityZh extends TranslationsAccessibilityEn { @override String get hexColor => 'ๅๅ…ญ่ฟ›ๅˆถ้ขœ่‰ฒ'; @override String get expandText => 'ๅฑ•ๅผ€ๆ–‡ๆœฌ'; @override String get collapseText => 'ๆŠ˜ๅ ๆ–‡ๆœฌ'; + @override String get alphabetNavigation => 'ๅญ—ๆฏๅฏผ่ˆช'; + @override String get alphabetScrollHint => 'ไธŠไธ‹ๆป‘ๅŠจไปฅๆŒ‰ๅญ—ๆฏ็งปๅŠจ'; + @override String rowColumnPosition({required Object row, required Object rowCount, required Object column, required Object columnCount}) => '็ฌฌ ${row} ่กŒ๏ผŒๅ…ฑ ${rowCount} ่กŒ๏ผ›็ฌฌ ${column} ๅˆ—๏ผŒๅ…ฑ ${columnCount} ๅˆ—'; + @override String rowPosition({required Object row, required Object rowCount}) => '็ฌฌ ${row} ่กŒ๏ผŒๅ…ฑ ${rowCount} ่กŒ'; } // Path: tooltips @@ -606,6 +608,16 @@ class _TranslationsTooltipsZh extends TranslationsTooltipsEn { @override String get markAsUnwatched => 'ๆ ‡่ฎฐไธบๆœช่ง‚็œ‹'; } +// Path: audioTracks +class _TranslationsAudioTracksZh extends TranslationsAudioTracksEn { + _TranslationsAudioTracksZh._(TranslationsZh root) : this._root = root, super.internal(root); + + final TranslationsZh _root; // ignore: unused_field + + // Translations + @override String track({required Object n}) => '้Ÿณ่ฝจ ${n}'; +} + // Path: videoControls class _TranslationsVideoControlsZh extends TranslationsVideoControlsEn { _TranslationsVideoControlsZh._(TranslationsZh root) : this._root = root, super.internal(root); @@ -711,11 +723,11 @@ class _TranslationsMessagesZh extends TranslationsMessagesEn { @override String get streamInterrupted => '่ง†้ข‘ๆตๅทฒไธญๆ–ญใ€‚ๆŒ‰ๆ’ญๆ”พ้”ฎๆˆ–ๆ‹–ๅŠจ่ฟ›ๅบฆๆก้‡่ฏ•ใ€‚'; @override String get liveStreamInterrupted => '็›ดๆ’ญๆตๅทฒไธญๆ–ญใ€‚ๆŒ‰ๆ’ญๆ”พ้”ฎ้‡่ฏ•ใ€‚'; @override String get fileInfoNotAvailable => 'ๆ–‡ไปถไฟกๆฏไธๅฏ็”จ'; - @override String get playbackAuthenticationRequired => ''; - @override String get playbackServerUnavailable => ''; - @override String get playbackDataInvalid => ''; - @override String get playbackCancelled => ''; - @override String get playbackFailed => ''; + @override String get playbackAuthenticationRequired => 'Sign in to the media server again to play this item.'; + @override String get playbackServerUnavailable => 'The media server is unavailable. Try again later.'; + @override String get playbackDataInvalid => 'The server returned invalid playback information.'; + @override String get playbackCancelled => 'Playback was cancelled.'; + @override String get playbackFailed => 'Playback could not be started.'; @override String errorLoadingFileInfo({required Object error}) => 'ๅŠ ่ฝฝๆ–‡ไปถไฟกๆฏๆ—ถๅ‡บ้”™: ${error}'; @override String get errorLoadingSeries => 'ๅŠ ่ฝฝ็ณปๅˆ—ๆ—ถๅ‡บ้”™'; @override String get musicNotSupported => 'ๅฐšไธๆ”ฏๆŒๆ’ญๆ”พ้Ÿณไน'; @@ -866,6 +878,7 @@ class _TranslationsProfilesZh extends TranslationsProfilesEn { @override String get borrowExplain => 'ๅ€Ÿ็”จๅฆไธ€ไธชไธชไบบ่ต„ๆ–™็š„่ฟžๆŽฅใ€‚ๅ— PIN ไฟๆŠค็š„ไธชไบบ่ต„ๆ–™้œ€่ฆ PINใ€‚'; @override String get borrowEmpty => 'ๆš‚ๆ— ๅฏๅ€Ÿ็”จ็š„ๅ†…ๅฎนใ€‚'; @override String get borrowEmptySubtitle => '่ฏทๅ…ˆๅฐ† Plex ๆˆ– Jellyfin ่ฟžๆŽฅๅˆฐๅฆไธ€ไธชไธชไบบ่ต„ๆ–™ใ€‚'; + @override String get borrowLoadFailed => 'Available connections could not be loaded. Try again.'; @override String borrowFromProfile({required Object displayName}) => 'ๆฅ่‡ช ${displayName}'; @override String get borrowConnectionBorrowed => 'ๅทฒๅ€Ÿ็”จ่ฟžๆŽฅใ€‚'; @override String get borrowFailed => 'ๆ— ๆณ•ๅ€Ÿ็”จ่ฟžๆŽฅใ€‚'; @@ -1148,7 +1161,7 @@ class _TranslationsLiveTvZh extends TranslationsLiveTvEn { @override String get favorites => 'ๆ”ถ่—'; @override String get reorderFavorites => '้‡ๆ–ฐๆŽ’ๅบๆ”ถ่—'; @override String get favoritesLoadFailed => 'ๆ— ๆณ•ๅŠ ่ฝฝๆ”ถ่—ใ€‚่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœ่ฟžๆŽฅๅŽ้‡่ฏ•ใ€‚'; - @override String get favoritesUpdateFailed => ''; + @override String get favoritesUpdateFailed => 'Could not update favorites. Check your connection and try again.'; @override String get joinSession => 'ๅŠ ๅ…ฅๆญฃๅœจ่ฟ›่กŒ็š„ไผš่ฏ'; @override String watchFromStart({required Object minutes}) => 'ไปŽๅคด่ง‚็œ‹๏ผˆ${minutes}ๅˆ†้’Ÿๅ‰ๅผ€ๅง‹๏ผ‰'; @override String get watchLive => '่ง‚็œ‹็›ดๆ’ญ'; @@ -2146,7 +2159,7 @@ extension on TranslationsZh { 'auth.quickConnectWaiting' => '็ญ‰ๅพ…ๆ‰นๅ‡†โ€ฆ', 'auth.quickConnectCancel' => 'ๅ–ๆถˆ', 'auth.quickConnectExpired' => 'Quick Connect ๅทฒ่ฟ‡ๆœŸใ€‚่ฏท้‡่ฏ•ใ€‚', - 'auth.localDataRecoveryRequired' => '', + 'auth.localDataRecoveryRequired' => 'Plezy could not safely recover local sign-in and pending playback data. Please sign in again.', 'common.cancel' => 'ๅ–ๆถˆ', 'common.save' => 'ไฟๅญ˜', 'common.close' => 'ๅ…ณ้—ญ', @@ -2230,8 +2243,6 @@ extension on TranslationsZh { 'settings.libraryDensity' => 'ๅช’ไฝ“ๅบ“ๅฏ†ๅบฆ', 'settings.compact' => '็ดงๅ‡‘', 'settings.comfortable' => '่ˆ’้€‚', - 'settings.tvCornerSpotlightBackdrop' => '', - 'settings.tvCornerSpotlightBackdropDescription' => '', 'settings.viewMode' => '่ง†ๅ›พๆจกๅผ', 'settings.gridView' => '็ฝ‘ๆ ผ่ง†ๅ›พ', 'settings.listView' => 'ๅˆ—่กจ่ง†ๅ›พ', @@ -2301,7 +2312,7 @@ extension on TranslationsZh { 'settings.watchTogetherRelay' => 'ไธ€่ตท็œ‹ไธญ็ปงๆœๅŠกๅ™จ', 'settings.watchTogetherRelayDescription' => '่ฎพ็ฝฎ่‡ชๅฎšไน‰ไธญ็ปงใ€‚ๆ‰€ๆœ‰ไบบๅฟ…้กปไฝฟ็”จๅŒไธ€ๆœๅŠกๅ™จใ€‚', 'settings.watchTogetherRelayHint' => 'https://my-relay.example.com', - 'settings.watchTogetherRelayInvalid' => '', + 'settings.watchTogetherRelayInvalid' => 'Enter a valid HTTP or HTTPS relay base URL.', 'settings.crashReporting' => 'ๅดฉๆบƒๆŠฅๅ‘Š', 'settings.crashReportingDescription' => 'ๅ‘้€ๅดฉๆบƒๆŠฅๅ‘ŠไปฅๅธฎๅŠฉๆ”น่ฟ›ๅบ”็”จ', 'settings.debugLogging' => '่ฐƒ่ฏ•ๆ—ฅๅฟ—', @@ -2318,12 +2329,10 @@ extension on TranslationsZh { 'settings.exportSettings' => 'ๅฏผๅ‡บ่ฎพ็ฝฎ', 'settings.exportSettingsDescription' => 'ๅฐ†ๆ‚จ็š„ๅๅฅฝไฟๅญ˜ๅˆฐๆ–‡ไปถ', 'settings.exportSettingsSuccess' => '่ฎพ็ฝฎๅทฒๅฏผๅ‡บ', - 'settings.exportSettingsFailed' => 'ๆ— ๆณ•ๅฏผๅ‡บ่ฎพ็ฝฎ', 'settings.importSettings' => 'ๅฏผๅ…ฅ่ฎพ็ฝฎ', 'settings.importSettingsDescription' => 'ไปŽๆ–‡ไปถๆขๅคๅๅฅฝ', 'settings.importSettingsConfirm' => '่ฟ™ๅฐ†ๆ›ฟๆขๆ‚จๅฝ“ๅ‰็š„่ฎพ็ฝฎใ€‚็ปง็ปญๅ—๏ผŸ', 'settings.importSettingsSuccess' => '่ฎพ็ฝฎๅทฒๅฏผๅ…ฅ', - 'settings.importSettingsFailed' => 'ๆ— ๆณ•ๅฏผๅ…ฅ่ฎพ็ฝฎ', 'settings.importSettingsInvalidFile' => 'ๆญคๆ–‡ไปถไธๆ˜ฏๆœ‰ๆ•ˆ็š„ Plezy ่ฎพ็ฝฎๅฏผๅ‡บ', 'settings.importSettingsNoUser' => 'ๅฏผๅ…ฅ่ฎพ็ฝฎๅ‰่ฏทๅ…ˆ็™ปๅฝ•', 'settings.shortcutsReset' => 'ๅฟซๆท้”ฎๅทฒ้‡็ฝฎไธบ้ป˜่ฎคๅ€ผ', @@ -2338,6 +2347,7 @@ extension on TranslationsZh { 'settings.validationErrorDuration' => ({required Object min, required Object max, required Object unit}) => 'ๆ—ถ้•ฟๅฟ…้กปไป‹ไบŽ ${min} ๅ’Œ ${max} ${unit} ไน‹้—ด', 'settings.shortcutAlreadyAssigned' => ({required Object action}) => 'ๅฟซๆท้”ฎๅทฒ่ขซๅˆ†้…็ป™ ${action}', 'settings.shortcutUpdated' => ({required Object action}) => 'ๅฟซๆท้”ฎๅทฒไธบ ${action} ๆ›ดๆ–ฐ', + 'settings.saveFailed' => 'Could not save changes. Try again.', 'settings.autoSkip' => '่‡ชๅŠจ่ทณ่ฟ‡', 'settings.autoSkipIntro' => '่‡ชๅŠจ่ทณ่ฟ‡็‰‡ๅคด', 'settings.autoSkipIntroDescription' => 'ๅ‡ ็ง’้’ŸๅŽ่‡ชๅŠจ่ทณ่ฟ‡็‰‡ๅคดๆ ‡่ฎฐ', @@ -2363,7 +2373,7 @@ extension on TranslationsZh { 'settings.downloadLocationChanged' => 'ไธ‹่ฝฝไฝ็ฝฎๅทฒๆ›ดๆ”น', 'settings.downloadLocationReset' => 'ไธ‹่ฝฝไฝ็ฝฎๅทฒ้‡็ฝฎไธบ้ป˜่ฎค', 'settings.downloadLocationInvalid' => 'ๆ‰€้€‰ๆ–‡ไปถๅคนไธๅฏๅ†™ๅ…ฅ', - 'settings.downloadLocationSelectError' => '้€‰ๆ‹ฉๆ–‡ไปถๅคนๅคฑ่ดฅ', + 'settings.downloadLocationPickerUnavailable' => 'ๆญค่ฎพๅค‡ไธๆ”ฏๆŒ้€‰ๆ‹ฉๆ–‡ไปถๅคน', 'settings.downloadOnWifiOnly' => 'ไป…ๅœจ WiFi ๆ—ถไธ‹่ฝฝ', 'settings.downloadOnWifiOnlyDescription' => 'ไฝฟ็”จ่œ‚็ชๆ•ฐๆฎๆ—ถ็ฆๆญขไธ‹่ฝฝ', 'settings.autoRemoveWatchedDownloads' => '่‡ชๅŠจ็งป้™คๅทฒ่ง‚็œ‹็š„ไธ‹่ฝฝ', @@ -2551,10 +2561,15 @@ extension on TranslationsZh { 'accessibility.hexColor' => 'ๅๅ…ญ่ฟ›ๅˆถ้ขœ่‰ฒ', 'accessibility.expandText' => 'ๅฑ•ๅผ€ๆ–‡ๆœฌ', 'accessibility.collapseText' => 'ๆŠ˜ๅ ๆ–‡ๆœฌ', + 'accessibility.alphabetNavigation' => 'ๅญ—ๆฏๅฏผ่ˆช', + 'accessibility.alphabetScrollHint' => 'ไธŠไธ‹ๆป‘ๅŠจไปฅๆŒ‰ๅญ—ๆฏ็งปๅŠจ', + 'accessibility.rowColumnPosition' => ({required Object row, required Object rowCount, required Object column, required Object columnCount}) => '็ฌฌ ${row} ่กŒ๏ผŒๅ…ฑ ${rowCount} ่กŒ๏ผ›็ฌฌ ${column} ๅˆ—๏ผŒๅ…ฑ ${columnCount} ๅˆ—', + 'accessibility.rowPosition' => ({required Object row, required Object rowCount}) => '็ฌฌ ${row} ่กŒ๏ผŒๅ…ฑ ${rowCount} ่กŒ', 'tooltips.shufflePlay' => '้šๆœบๆ’ญๆ”พ', 'tooltips.playTrailer' => 'ๆ’ญๆ”พ้ข„ๅ‘Š็‰‡', 'tooltips.markAsWatched' => 'ๆ ‡่ฎฐไธบๅทฒ่ง‚็œ‹', 'tooltips.markAsUnwatched' => 'ๆ ‡่ฎฐไธบๆœช่ง‚็œ‹', + 'audioTracks.track' => ({required Object n}) => '้Ÿณ่ฝจ ${n}', 'videoControls.audioLabel' => '้Ÿณ้ข‘', 'videoControls.subtitlesLabel' => 'ๅญ—ๅน•', 'videoControls.resetToZero' => '้‡็ฝฎไธบ 0ms', @@ -2641,20 +2656,20 @@ extension on TranslationsZh { 'messages.markedAsUnwatched' => 'ๅทฒๆ ‡่ฎฐไธบๆœช่ง‚็œ‹', 'messages.markedAsWatchedOffline' => 'ๅทฒๆ ‡่ฎฐไธบๅทฒ่ง‚็œ‹ (ๅฐ†ๅœจ่”็ฝ‘ๆ—ถๅŒๆญฅ)', 'messages.markedAsUnwatchedOffline' => 'ๅทฒๆ ‡่ฎฐไธบๆœช่ง‚็œ‹ (ๅฐ†ๅœจ่”็ฝ‘ๆ—ถๅŒๆญฅ)', - 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'ๅทฒ่‡ชๅŠจ็งป้™ค: ${title}', - 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('zh'))(n, other: 'ๅทฒ่‡ชๅŠจ็งป้™ค ${n} ไธช็œ‹่ฟ‡็š„ไธ‹่ฝฝ', ), _ => null, } ?? switch (path) { + 'messages.autoRemovedWatchedDownload' => ({required Object title}) => 'ๅทฒ่‡ชๅŠจ็งป้™ค: ${title}', + 'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('zh'))(n, other: 'ๅทฒ่‡ชๅŠจ็งป้™ค ${n} ไธช็œ‹่ฟ‡็š„ไธ‹่ฝฝ', ), 'messages.removedFromContinueWatching' => 'ๅทฒไปŽ็ปง็ปญ่ง‚็œ‹ไธญ็งป้™ค', 'messages.errorLoading' => ({required Object error}) => '้”™่ฏฏ: ${error}', 'messages.streamInterrupted' => '่ง†้ข‘ๆตๅทฒไธญๆ–ญใ€‚ๆŒ‰ๆ’ญๆ”พ้”ฎๆˆ–ๆ‹–ๅŠจ่ฟ›ๅบฆๆก้‡่ฏ•ใ€‚', 'messages.liveStreamInterrupted' => '็›ดๆ’ญๆตๅทฒไธญๆ–ญใ€‚ๆŒ‰ๆ’ญๆ”พ้”ฎ้‡่ฏ•ใ€‚', 'messages.fileInfoNotAvailable' => 'ๆ–‡ไปถไฟกๆฏไธๅฏ็”จ', - 'messages.playbackAuthenticationRequired' => '', - 'messages.playbackServerUnavailable' => '', - 'messages.playbackDataInvalid' => '', - 'messages.playbackCancelled' => '', - 'messages.playbackFailed' => '', + 'messages.playbackAuthenticationRequired' => 'Sign in to the media server again to play this item.', + 'messages.playbackServerUnavailable' => 'The media server is unavailable. Try again later.', + 'messages.playbackDataInvalid' => 'The server returned invalid playback information.', + 'messages.playbackCancelled' => 'Playback was cancelled.', + 'messages.playbackFailed' => 'Playback could not be started.', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'ๅŠ ่ฝฝๆ–‡ไปถไฟกๆฏๆ—ถๅ‡บ้”™: ${error}', 'messages.errorLoadingSeries' => 'ๅŠ ่ฝฝ็ณปๅˆ—ๆ—ถๅ‡บ้”™', 'messages.musicNotSupported' => 'ๅฐšไธๆ”ฏๆŒๆ’ญๆ”พ้Ÿณไน', @@ -2769,6 +2784,7 @@ extension on TranslationsZh { 'profiles.borrowExplain' => 'ๅ€Ÿ็”จๅฆไธ€ไธชไธชไบบ่ต„ๆ–™็š„่ฟžๆŽฅใ€‚ๅ— PIN ไฟๆŠค็š„ไธชไบบ่ต„ๆ–™้œ€่ฆ PINใ€‚', 'profiles.borrowEmpty' => 'ๆš‚ๆ— ๅฏๅ€Ÿ็”จ็š„ๅ†…ๅฎนใ€‚', 'profiles.borrowEmptySubtitle' => '่ฏทๅ…ˆๅฐ† Plex ๆˆ– Jellyfin ่ฟžๆŽฅๅˆฐๅฆไธ€ไธชไธชไบบ่ต„ๆ–™ใ€‚', + 'profiles.borrowLoadFailed' => 'Available connections could not be loaded. Try again.', 'profiles.borrowFromProfile' => ({required Object displayName}) => 'ๆฅ่‡ช ${displayName}', 'profiles.borrowConnectionBorrowed' => 'ๅทฒๅ€Ÿ็”จ่ฟžๆŽฅใ€‚', 'profiles.borrowFailed' => 'ๆ— ๆณ•ๅ€Ÿ็”จ่ฟžๆŽฅใ€‚', @@ -2994,7 +3010,7 @@ extension on TranslationsZh { 'liveTv.favorites' => 'ๆ”ถ่—', 'liveTv.reorderFavorites' => '้‡ๆ–ฐๆŽ’ๅบๆ”ถ่—', 'liveTv.favoritesLoadFailed' => 'ๆ— ๆณ•ๅŠ ่ฝฝๆ”ถ่—ใ€‚่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœ่ฟžๆŽฅๅŽ้‡่ฏ•ใ€‚', - 'liveTv.favoritesUpdateFailed' => '', + 'liveTv.favoritesUpdateFailed' => 'Could not update favorites. Check your connection and try again.', 'liveTv.joinSession' => 'ๅŠ ๅ…ฅๆญฃๅœจ่ฟ›่กŒ็š„ไผš่ฏ', 'liveTv.watchFromStart' => ({required Object minutes}) => 'ไปŽๅคด่ง‚็œ‹๏ผˆ${minutes}ๅˆ†้’Ÿๅ‰ๅผ€ๅง‹๏ผ‰', 'liveTv.watchLive' => '่ง‚็œ‹็›ดๆ’ญ', @@ -3154,11 +3170,11 @@ extension on TranslationsZh { 'watchTogether.participantLeft' => ({required Object name}) => '${name} ็ฆปๅผ€ไบ†', 'watchTogether.participantPaused' => ({required Object name}) => '${name} ๆš‚ๅœไบ†', 'watchTogether.participantResumed' => ({required Object name}) => '${name} ็ปง็ปญๆ’ญๆ”พไบ†', + _ => null, + } ?? switch (path) { 'watchTogether.participantSeeked' => ({required Object name}) => '${name} ่ทณ่ฝฌไบ†', 'watchTogether.participantBuffering' => ({required Object name}) => '${name} ๆญฃๅœจ็ผ“ๅ†ฒ', 'watchTogether.participantNeedsUpdate' => ({required Object name}) => '${name} ๆญฃๅœจไฝฟ็”จ่พƒๆ—ง็‰ˆๆœฌ็š„ๅบ”็”จ๏ผŒๆ— ๆณ•ๅŒๆญฅ', - _ => null, - } ?? switch (path) { 'watchTogether.resumingWithout' => ({required Object name}) => 'ไธ็ญ‰ๅพ… ${name}๏ผŒ็ปง็ปญๆ’ญๆ”พ', 'watchTogether.waitingForParticipants' => '็ญ‰ๅพ…ๅ…ถไป–ไบบๅŠ ่ฝฝ...', 'watchTogether.waitingForName' => ({required Object name}) => 'ๆญฃๅœจ็ญ‰ๅพ… ${name}...', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index dd20a8db..6489f48c 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "Vรคntar pรฅ godkรคnnandeโ€ฆ", "quickConnectCancel": "Avbryt", "quickConnectExpired": "Quick Connect har gรฅtt ut. Fรถrsรถk igen.", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "Avbryt", @@ -180,7 +180,7 @@ "watchTogetherRelay": "Titta Tillsammans-relay", "watchTogetherRelayDescription": "Ange en anpassad relay. Alla mรฅste anvรคnda samma server.", "watchTogetherRelayHint": "https://min-relay.exempel.se", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "Kraschrapportering", "crashReportingDescription": "Skicka kraschrapporter fรถr att fรถrbรคttra appen", "debugLogging": "Felsรถkningsloggning", @@ -197,12 +197,10 @@ "exportSettings": "Exportera instรคllningar", "exportSettingsDescription": "Spara dina instรคllningar till en fil", "exportSettingsSuccess": "Instรคllningar exporterade", - "exportSettingsFailed": "Kunde inte exportera instรคllningar", "importSettings": "Importera instรคllningar", "importSettingsDescription": "ร…terstรคll instรคllningar frรฅn en fil", "importSettingsConfirm": "Detta ersรคtter dina nuvarande instรคllningar. Fortsรคtta?", "importSettingsSuccess": "Instรคllningar importerade", - "importSettingsFailed": "Kunde inte importera instรคllningar", "importSettingsInvalidFile": "Denna fil รคr inte en giltig Plezy-export", "importSettingsNoUser": "Logga in innan du importerar instรคllningar", "shortcutsReset": "Genvรคgar รฅterstรคllda till standard", @@ -217,6 +215,7 @@ "validationErrorDuration": "Tiden mรฅste vara mellan ${min} och ${max} ${unit}", "shortcutAlreadyAssigned": "Genvรคg redan tilldelad ${action}", "shortcutUpdated": "Genvรคg uppdaterad fรถr ${action}", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "Auto Hoppa ร–ver", "autoSkipIntro": "Hoppa ร–ver Intro Automatiskt", "autoSkipIntroDescription": "Hoppa automatiskt รถver intro-markรถrer efter nรฅgra sekunder", @@ -242,7 +241,7 @@ "downloadLocationChanged": "Nedladdningsplats รคndrad", "downloadLocationReset": "Nedladdningsplats รฅterstรคlld till standard", "downloadLocationInvalid": "Vald mapp รคr inte skrivbar", - "downloadLocationSelectError": "Kunde inte vรคlja mapp", + "downloadLocationPickerUnavailable": "Mappval รคr inte tillgรคngligt pรฅ den hรคr enheten", "downloadOnWifiOnly": "Ladda ner endast pรฅ WiFi", "downloadOnWifiOnlyDescription": "Fรถrhindra nedladdningar vid anvรคndning av mobildata", "autoRemoveWatchedDownloads": "Ta bort sedda nedladdningar automatiskt", @@ -443,7 +442,11 @@ "brightness": "Ljusstyrka", "hexColor": "Hexfรคrg", "expandText": "Expandera text", - "collapseText": "Fรคll ihop text" + "collapseText": "Fรคll ihop text", + "alphabetNavigation": "Alfabetisk navigering", + "alphabetScrollHint": "Svep upp eller ned fรถr att flytta en bokstav", + "rowColumnPosition": "Rad ${row} av ${rowCount}, kolumn ${column} av ${columnCount}", + "rowPosition": "Rad ${row} av ${rowCount}" }, "tooltips": { "shufflePlay": "Blanda uppspelning", @@ -451,6 +454,9 @@ "markAsWatched": "Markera som sedd", "markAsUnwatched": "Markera som osedd" }, + "audioTracks": { + "track": "Ljudspรฅr ${n}" + }, "videoControls": { "audioLabel": "Ljud", "subtitlesLabel": "Undertexter", @@ -478,6 +484,8 @@ "playNext": "Spela nรคsta", "playButton": "Spela", "pauseButton": "Pausa", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "Spola bakรฅt ${seconds} sekunder", "seekForwardButton": "Spola framรฅt ${seconds} sekunder", "previousButton": "Fรถregรฅende avsnitt", @@ -553,11 +561,11 @@ "streamInterrupted": "Uppspelningen avbrรถts. Tryck pรฅ play eller spola fรถr att fรถrsรถka igen.", "liveStreamInterrupted": "Livestreamen avbrรถts. Tryck pรฅ play fรถr att fรถrsรถka igen.", "fileInfoNotAvailable": "Filinformation inte tillgรคnglig", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "Fel vid laddning av filinformation: ${error}", "errorLoadingSeries": "Fel vid laddning av serie", "musicNotSupported": "Musikuppspelning stรถds inte รคnnu", @@ -680,6 +688,7 @@ "borrowExplain": "Lรฅna en annan profils anslutning. PIN-skyddade profiler krรคver en PIN.", "borrowEmpty": "Inget att lรฅna รคnnu.", "borrowEmptySubtitle": "Anslut Plex eller Jellyfin till en annan profil fรถrst.", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "Frรฅn ${displayName}", "borrowConnectionBorrowed": "Anslutning lรฅnad.", "borrowFailed": "Kunde inte lรฅna anslutningen.", @@ -944,7 +953,7 @@ "favorites": "Favoriter", "reorderFavorites": "Ordna om favoriter", "favoritesLoadFailed": "Det gick inte att lรคsa in favoriter. Kontrollera anslutningen och fรถrsรถk igen.", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "Gรฅ med i pรฅgรฅende session", "watchFromStart": "Titta frรฅn bรถrjan (${minutes} min sedan)", "watchLive": "Titta live", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 8e8da55e..69bf8431 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -17,7 +17,7 @@ "quickConnectWaiting": "็ญ‰ๅพ…ๆ‰นๅ‡†โ€ฆ", "quickConnectCancel": "ๅ–ๆถˆ", "quickConnectExpired": "Quick Connect ๅทฒ่ฟ‡ๆœŸใ€‚่ฏท้‡่ฏ•ใ€‚", - "localDataRecoveryRequired": "" + "localDataRecoveryRequired": "Plezy could not safely recover local sign-in and pending playback data. Please sign in again." }, "common": { "cancel": "ๅ–ๆถˆ", @@ -180,7 +180,7 @@ "watchTogetherRelay": "ไธ€่ตท็œ‹ไธญ็ปงๆœๅŠกๅ™จ", "watchTogetherRelayDescription": "่ฎพ็ฝฎ่‡ชๅฎšไน‰ไธญ็ปงใ€‚ๆ‰€ๆœ‰ไบบๅฟ…้กปไฝฟ็”จๅŒไธ€ๆœๅŠกๅ™จใ€‚", "watchTogetherRelayHint": "https://my-relay.example.com", - "watchTogetherRelayInvalid": "", + "watchTogetherRelayInvalid": "Enter a valid HTTP or HTTPS relay base URL.", "crashReporting": "ๅดฉๆบƒๆŠฅๅ‘Š", "crashReportingDescription": "ๅ‘้€ๅดฉๆบƒๆŠฅๅ‘ŠไปฅๅธฎๅŠฉๆ”น่ฟ›ๅบ”็”จ", "debugLogging": "่ฐƒ่ฏ•ๆ—ฅๅฟ—", @@ -197,12 +197,10 @@ "exportSettings": "ๅฏผๅ‡บ่ฎพ็ฝฎ", "exportSettingsDescription": "ๅฐ†ๆ‚จ็š„ๅๅฅฝไฟๅญ˜ๅˆฐๆ–‡ไปถ", "exportSettingsSuccess": "่ฎพ็ฝฎๅทฒๅฏผๅ‡บ", - "exportSettingsFailed": "ๆ— ๆณ•ๅฏผๅ‡บ่ฎพ็ฝฎ", "importSettings": "ๅฏผๅ…ฅ่ฎพ็ฝฎ", "importSettingsDescription": "ไปŽๆ–‡ไปถๆขๅคๅๅฅฝ", "importSettingsConfirm": "่ฟ™ๅฐ†ๆ›ฟๆขๆ‚จๅฝ“ๅ‰็š„่ฎพ็ฝฎใ€‚็ปง็ปญๅ—๏ผŸ", "importSettingsSuccess": "่ฎพ็ฝฎๅทฒๅฏผๅ…ฅ", - "importSettingsFailed": "ๆ— ๆณ•ๅฏผๅ…ฅ่ฎพ็ฝฎ", "importSettingsInvalidFile": "ๆญคๆ–‡ไปถไธๆ˜ฏๆœ‰ๆ•ˆ็š„ Plezy ่ฎพ็ฝฎๅฏผๅ‡บ", "importSettingsNoUser": "ๅฏผๅ…ฅ่ฎพ็ฝฎๅ‰่ฏทๅ…ˆ็™ปๅฝ•", "shortcutsReset": "ๅฟซๆท้”ฎๅทฒ้‡็ฝฎไธบ้ป˜่ฎคๅ€ผ", @@ -217,6 +215,7 @@ "validationErrorDuration": "ๆ—ถ้•ฟๅฟ…้กปไป‹ไบŽ ${min} ๅ’Œ ${max} ${unit} ไน‹้—ด", "shortcutAlreadyAssigned": "ๅฟซๆท้”ฎๅทฒ่ขซๅˆ†้…็ป™ ${action}", "shortcutUpdated": "ๅฟซๆท้”ฎๅทฒไธบ ${action} ๆ›ดๆ–ฐ", + "saveFailed": "Could not save changes. Try again.", "autoSkip": "่‡ชๅŠจ่ทณ่ฟ‡", "autoSkipIntro": "่‡ชๅŠจ่ทณ่ฟ‡็‰‡ๅคด", "autoSkipIntroDescription": "ๅ‡ ็ง’้’ŸๅŽ่‡ชๅŠจ่ทณ่ฟ‡็‰‡ๅคดๆ ‡่ฎฐ", @@ -242,7 +241,7 @@ "downloadLocationChanged": "ไธ‹่ฝฝไฝ็ฝฎๅทฒๆ›ดๆ”น", "downloadLocationReset": "ไธ‹่ฝฝไฝ็ฝฎๅทฒ้‡็ฝฎไธบ้ป˜่ฎค", "downloadLocationInvalid": "ๆ‰€้€‰ๆ–‡ไปถๅคนไธๅฏๅ†™ๅ…ฅ", - "downloadLocationSelectError": "้€‰ๆ‹ฉๆ–‡ไปถๅคนๅคฑ่ดฅ", + "downloadLocationPickerUnavailable": "ๆญค่ฎพๅค‡ไธๆ”ฏๆŒ้€‰ๆ‹ฉๆ–‡ไปถๅคน", "downloadOnWifiOnly": "ไป…ๅœจ WiFi ๆ—ถไธ‹่ฝฝ", "downloadOnWifiOnlyDescription": "ไฝฟ็”จ่œ‚็ชๆ•ฐๆฎๆ—ถ็ฆๆญขไธ‹่ฝฝ", "autoRemoveWatchedDownloads": "่‡ชๅŠจ็งป้™คๅทฒ่ง‚็œ‹็š„ไธ‹่ฝฝ", @@ -443,7 +442,11 @@ "brightness": "ไบฎๅบฆ", "hexColor": "ๅๅ…ญ่ฟ›ๅˆถ้ขœ่‰ฒ", "expandText": "ๅฑ•ๅผ€ๆ–‡ๆœฌ", - "collapseText": "ๆŠ˜ๅ ๆ–‡ๆœฌ" + "collapseText": "ๆŠ˜ๅ ๆ–‡ๆœฌ", + "alphabetNavigation": "ๅญ—ๆฏๅฏผ่ˆช", + "alphabetScrollHint": "ไธŠไธ‹ๆป‘ๅŠจไปฅๆŒ‰ๅญ—ๆฏ็งปๅŠจ", + "rowColumnPosition": "็ฌฌ ${row} ่กŒ๏ผŒๅ…ฑ ${rowCount} ่กŒ๏ผ›็ฌฌ ${column} ๅˆ—๏ผŒๅ…ฑ ${columnCount} ๅˆ—", + "rowPosition": "็ฌฌ ${row} ่กŒ๏ผŒๅ…ฑ ${rowCount} ่กŒ" }, "tooltips": { "shufflePlay": "้šๆœบๆ’ญๆ”พ", @@ -451,6 +454,9 @@ "markAsWatched": "ๆ ‡่ฎฐไธบๅทฒ่ง‚็œ‹", "markAsUnwatched": "ๆ ‡่ฎฐไธบๆœช่ง‚็œ‹" }, + "audioTracks": { + "track": "้Ÿณ่ฝจ ${n}" + }, "videoControls": { "audioLabel": "้Ÿณ้ข‘", "subtitlesLabel": "ๅญ—ๅน•", @@ -478,6 +484,8 @@ "playNext": "ๆ’ญๆ”พไธ‹ไธ€้›†", "playButton": "ๆ’ญๆ”พ", "pauseButton": "ๆš‚ๅœ", + "showPlaybackControls": "", + "hidePlaybackControls": "", "seekBackwardButton": "ๅŽ้€€ ${seconds} ็ง’", "seekForwardButton": "ๅ‰่ฟ› ${seconds} ็ง’", "previousButton": "ไธŠไธ€้›†", @@ -552,11 +560,11 @@ "streamInterrupted": "่ง†้ข‘ๆตๅทฒไธญๆ–ญใ€‚ๆŒ‰ๆ’ญๆ”พ้”ฎๆˆ–ๆ‹–ๅŠจ่ฟ›ๅบฆๆก้‡่ฏ•ใ€‚", "liveStreamInterrupted": "็›ดๆ’ญๆตๅทฒไธญๆ–ญใ€‚ๆŒ‰ๆ’ญๆ”พ้”ฎ้‡่ฏ•ใ€‚", "fileInfoNotAvailable": "ๆ–‡ไปถไฟกๆฏไธๅฏ็”จ", - "playbackAuthenticationRequired": "", - "playbackServerUnavailable": "", - "playbackDataInvalid": "", - "playbackCancelled": "", - "playbackFailed": "", + "playbackAuthenticationRequired": "Sign in to the media server again to play this item.", + "playbackServerUnavailable": "The media server is unavailable. Try again later.", + "playbackDataInvalid": "The server returned invalid playback information.", + "playbackCancelled": "Playback was cancelled.", + "playbackFailed": "Playback could not be started.", "errorLoadingFileInfo": "ๅŠ ่ฝฝๆ–‡ไปถไฟกๆฏๆ—ถๅ‡บ้”™: ${error}", "errorLoadingSeries": "ๅŠ ่ฝฝ็ณปๅˆ—ๆ—ถๅ‡บ้”™", "musicNotSupported": "ๅฐšไธๆ”ฏๆŒๆ’ญๆ”พ้Ÿณไน", @@ -679,6 +687,7 @@ "borrowExplain": "ๅ€Ÿ็”จๅฆไธ€ไธชไธชไบบ่ต„ๆ–™็š„่ฟžๆŽฅใ€‚ๅ— PIN ไฟๆŠค็š„ไธชไบบ่ต„ๆ–™้œ€่ฆ PINใ€‚", "borrowEmpty": "ๆš‚ๆ— ๅฏๅ€Ÿ็”จ็š„ๅ†…ๅฎนใ€‚", "borrowEmptySubtitle": "่ฏทๅ…ˆๅฐ† Plex ๆˆ– Jellyfin ่ฟžๆŽฅๅˆฐๅฆไธ€ไธชไธชไบบ่ต„ๆ–™ใ€‚", + "borrowLoadFailed": "Available connections could not be loaded. Try again.", "borrowFromProfile": "ๆฅ่‡ช ${displayName}", "borrowConnectionBorrowed": "ๅทฒๅ€Ÿ็”จ่ฟžๆŽฅใ€‚", "borrowFailed": "ๆ— ๆณ•ๅ€Ÿ็”จ่ฟžๆŽฅใ€‚", @@ -942,7 +951,7 @@ "favorites": "ๆ”ถ่—", "reorderFavorites": "้‡ๆ–ฐๆŽ’ๅบๆ”ถ่—", "favoritesLoadFailed": "ๆ— ๆณ•ๅŠ ่ฝฝๆ”ถ่—ใ€‚่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœ่ฟžๆŽฅๅŽ้‡่ฏ•ใ€‚", - "favoritesUpdateFailed": "", + "favoritesUpdateFailed": "Could not update favorites. Check your connection and try again.", "joinSession": "ๅŠ ๅ…ฅๆญฃๅœจ่ฟ›่กŒ็š„ไผš่ฏ", "watchFromStart": "ไปŽๅคด่ง‚็œ‹๏ผˆ${minutes}ๅˆ†้’Ÿๅ‰ๅผ€ๅง‹๏ผ‰", "watchLive": "่ง‚็œ‹็›ดๆ’ญ", diff --git a/lib/mixins/grid_focus_node_mixin.dart b/lib/mixins/grid_focus_node_mixin.dart index ae5e334a..9bc58530 100644 --- a/lib/mixins/grid_focus_node_mixin.dart +++ b/lib/mixins/grid_focus_node_mixin.dart @@ -59,10 +59,12 @@ mixin GridFocusNodeMixin on State { } } for (final key in keysToRemove) { - final node = gridItemFocusNodes.remove(key); - if (node != null && !node.hasFocus) { - node.dispose(); - } + final node = gridItemFocusNodes[key]; + // A focused node is still borrowed by its mounted card. Keep ownership + // and indexed identity until a later eviction or final teardown. + if (node == null || node.hasFocus) continue; + gridItemFocusNodes.remove(key); + node.dispose(); } } diff --git a/lib/models/shader_preset.dart b/lib/models/shader_preset.dart index daa80132..e55f9e88 100644 --- a/lib/models/shader_preset.dart +++ b/lib/models/shader_preset.dart @@ -208,38 +208,36 @@ class ShaderPreset { return ''; } - static List get allPresets { - return [ - none, - nvscalerDefault, - artcnnPreset(ArtCNNModel.c4f16, ArtCNNVariant.neutral), - artcnnPreset(ArtCNNModel.c4f16, ArtCNNVariant.denoise), - artcnnPreset(ArtCNNModel.c4f16, ArtCNNVariant.denoiseSharpen), - artcnnPreset(ArtCNNModel.c4f32, ArtCNNVariant.neutral), - artcnnPreset(ArtCNNModel.c4f32, ArtCNNVariant.denoise), - artcnnPreset(ArtCNNModel.c4f32, ArtCNNVariant.denoiseSharpen), - anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeA), - anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeB), - anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeC), - anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeAA), - anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeBB), - anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeCA), - anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeA), - anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeB), - anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeC), - anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeAA), - anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeBB), - anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeCA), - ]; - } + static final List _builtInPresets = List.unmodifiable([ + none, + nvscalerDefault, + artcnnPreset(ArtCNNModel.c4f16, ArtCNNVariant.neutral), + artcnnPreset(ArtCNNModel.c4f16, ArtCNNVariant.denoise), + artcnnPreset(ArtCNNModel.c4f16, ArtCNNVariant.denoiseSharpen), + artcnnPreset(ArtCNNModel.c4f32, ArtCNNVariant.neutral), + artcnnPreset(ArtCNNModel.c4f32, ArtCNNVariant.denoise), + artcnnPreset(ArtCNNModel.c4f32, ArtCNNVariant.denoiseSharpen), + anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeA), + anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeB), + anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeC), + anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeAA), + anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeBB), + anime4kPreset(Anime4KQuality.fast, Anime4KMode.modeCA), + anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeA), + anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeB), + anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeC), + anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeAA), + anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeBB), + anime4kPreset(Anime4KQuality.hq, Anime4KMode.modeCA), + ]); - static ShaderPreset? fromId(String id) { - try { - return allPresets.firstWhere((p) => p.id == id); - } catch (_) { - return null; - } - } + static final Map _builtInPresetsById = Map.unmodifiable({ + for (final preset in _builtInPresets) preset.id: preset, + }); + + static List get allPresets => _builtInPresets; + + static ShaderPreset? fromId(String id) => _builtInPresetsById[id]; bool get isEnabled => type != ShaderPresetType.none; diff --git a/lib/providers/shader_provider.dart b/lib/providers/shader_provider.dart index ae9e12da..38c33dc7 100644 --- a/lib/providers/shader_provider.dart +++ b/lib/providers/shader_provider.dart @@ -15,6 +15,7 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin { ShaderPreset _savedPreset = ShaderPreset.none; ShaderPreset _currentPreset = ShaderPreset.none; List _customPresets = []; + List _allPresets = ShaderPreset.allPresets; bool _initialized = false; ShaderProvider() { @@ -26,9 +27,22 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin { } void _syncFromSettings(SettingsService service) { - final customData = service.read(SettingsService.customShaderPresets); - final customPresets = customData.map((json) => ShaderPreset.fromJson(json)).toList(); + final customPresets = []; + for (final json in service.read(SettingsService.customShaderPresets)) { + try { + final preset = ShaderPreset.fromJson(json); + final fileName = preset.fileName; + if (preset.type == ShaderPresetType.custom && + fileName != null && + ShaderAssetLoader.isValidCustomShaderFileName(fileName)) { + customPresets.add(preset); + } + } on Object { + // Imported settings can contain structurally invalid custom rows. + } + } _customPresets = customPresets; + _refreshAllPresets(); final presetId = service.read(SettingsService.globalShaderPreset); _savedPreset = findPresetById(presetId) ?? ShaderPreset.none; @@ -47,7 +61,7 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin { bool get initialized => _initialized; ShaderPreset get savedPreset => _savedPreset; ShaderPreset get currentPreset => _currentPreset; - List get allPresets => [...ShaderPreset.allPresets, ..._customPresets]; + List get allPresets => _allPresets; List get customPresets => _customPresets; bool get isShaderEnabled => _currentPreset.type != ShaderPresetType.none; @@ -83,6 +97,7 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin { final preset = ShaderPreset(id: id, name: displayName, type: ShaderPresetType.custom, fileName: storedFileName); _customPresets.add(preset); + _refreshAllPresets(); await _saveCustomPresets(); return preset; } @@ -94,6 +109,7 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin { await ShaderAssetLoader.deleteCustomShader(preset.fileName!); } _customPresets.removeWhere((p) => p.id == preset.id); + _refreshAllPresets(); await _saveCustomPresets(); // Reset to none if the deleted preset was active @@ -102,6 +118,12 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin { } } + void _refreshAllPresets() { + _allPresets = _customPresets.isEmpty + ? ShaderPreset.allPresets + : List.unmodifiable([...ShaderPreset.allPresets, ..._customPresets]); + } + Future _saveCustomPresets() async { final service = _settingsBinding.settings ?? await SettingsService.getInstance(); final data = _customPresets.map((p) => p.toJson()).toList(); diff --git a/lib/screens/catalog_item_detail_screen.dart b/lib/screens/catalog_item_detail_screen.dart index eb3d9b6e..143a5104 100644 --- a/lib/screens/catalog_item_detail_screen.dart +++ b/lib/screens/catalog_item_detail_screen.dart @@ -10,6 +10,7 @@ import '../focus/focusable_action_bar.dart'; import '../focus/dpad_navigator.dart'; import '../focus/input_mode_tracker.dart'; import '../focus/key_event_utils.dart'; +import '../focus/locked_hub_controller.dart'; import '../i18n/strings.g.dart'; import '../media/media_hub.dart'; import '../media/media_item.dart'; @@ -52,9 +53,11 @@ class CatalogItemDetailScreen extends StatefulWidget { class _CatalogItemDetailScreenState extends State { final _actionBarKey = GlobalKey(); + final _backButtonFocusNode = FocusNode(debugLabel: 'catalog_detail_back'); final _castSectionKey = GlobalKey(); final _castStripKey = GlobalKey(); final _relatedSectionKey = GlobalKey(); + final _hubFocusMemory = HubFocusMemory(); final ScrollController _scrollController = ScrollController(); List _libraryMatchFocusNodes = const []; CatalogSource? _watchlistSource; @@ -101,6 +104,7 @@ class _CatalogItemDetailScreenState extends State { @override void dispose() { + _backButtonFocusNode.dispose(); _watchlistSource?.watchlistChanges.removeListener(_onWatchlistChanged); for (final node in _libraryMatchFocusNodes) { node.dispose(); @@ -441,6 +445,7 @@ class _CatalogItemDetailScreenState extends State { items: [for (final item in related) item.toMediaItem()], size: related.length, ), + focusMemory: _hubFocusMemory, icon: Symbols.recommend_rounded, inset: true, onNavigateUp: _focusSectionAboveRelated, @@ -561,7 +566,7 @@ class _CatalogItemDetailScreenState extends State { ? t.explore.removeFromWatchlist : t.explore.addToWatchlist, onPressed: onWatchlist == null - ? () {} + ? null : () => unawaited(_toggleWatchlist()), ), if (_requestSource case final SeerrCatalogSource seerr) @@ -608,7 +613,7 @@ class _CatalogItemDetailScreenState extends State { top: 0, left: 0, child: DesktopAppBarHelper.buildAdjustedLeading( - const AppBarBackButton(style: BackButtonStyle.circular), + AppBarBackButton(style: BackButtonStyle.circular, focusNode: _backButtonFocusNode), context: hostContext, )!, ), diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index 2bd853c6..39f5aaae 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -10,6 +10,7 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../focus/focusable_action_bar.dart'; import '../focus/hub_vertical_navigation.dart'; +import '../focus/locked_hub_controller.dart'; import '../focus/input_mode_tracker.dart'; import '../focus/key_event_utils.dart'; import 'package:cached_network_image_ce/cached_network_image.dart'; @@ -112,6 +113,7 @@ class _DiscoverScreenState extends State final Map> _hubKeysByIdentity = {}; List> _orderedHubKeys = const []; final _tvBrowseRailKey = GlobalKey(); + final _hubFocusMemory = HubFocusMemory(); // Hero and app bar focus late FocusNode _heroFocusNode; @@ -1045,6 +1047,7 @@ class _DiscoverScreenState extends State more: _hasMoreContinueWatching, items: _onDeck, ), + focusMemory: _hubFocusMemory, icon: Symbols.play_circle_rounded, onRefresh: _discover.updateItem, onRemoveFromContinueWatching: _discover.refreshContinueWatching, @@ -1062,6 +1065,7 @@ class _DiscoverScreenState extends State child: HubSection( key: i < _orderedHubKeys.length ? _orderedHubKeys[i] : null, hub: _hubs[i], + focusMemory: _hubFocusMemory, icon: _getHubIcon(_hubs[i].title), showServerName: showServerNameOnHubs || hubsSpanMultipleServers, onRefresh: _discover.updateItem, @@ -1146,6 +1150,7 @@ class _DiscoverScreenState extends State return _tvBrowseRailWidget = TvBrowseRail( key: _tvBrowseRailKey, hubs: browseHubs, + focusMemory: _hubFocusMemory, showServerName: showServerName, iconForHub: (hub, _) => hub.id == 'continue_watching' ? Symbols.play_circle_rounded : _getHubIcon(hub.title), onFocusedItemChanged: _setSpotlightItem, diff --git a/lib/screens/explore_screen.dart b/lib/screens/explore_screen.dart index ff859f83..0d3550f7 100644 --- a/lib/screens/explore_screen.dart +++ b/lib/screens/explore_screen.dart @@ -6,6 +6,7 @@ import 'package:provider/provider.dart'; import '../focus/focusable_action_bar.dart'; import '../focus/hub_vertical_navigation.dart'; +import '../focus/locked_hub_controller.dart'; import '../i18n/strings.g.dart'; import '../media/ids.dart'; import '../media/media_hub.dart'; @@ -54,6 +55,7 @@ class ExploreScreenState extends State final _sourceMenuKey = GlobalKey>(); final _tvBrowseRailKey = GlobalKey(); + final _hubFocusMemory = HubFocusMemory(); final TvSpotlightController _spotlight = TvSpotlightController(); @override @@ -302,6 +304,7 @@ class ExploreScreenState extends State child: HubSection( key: _orderedHubKeys[i], hub: rowHubs[i].hub, + focusMemory: _hubFocusMemory, icon: _rowIcon(rowHubs[i].row), loadMoreItems: rowHubs[i].hub.more ? () => _explore.loadAllForHub(rowHubs[i]) : null, onVerticalNavigation: (isUp) => _handleVerticalNavigation(i, isUp), @@ -431,6 +434,7 @@ class ExploreScreenState extends State child: TvBrowseRail( key: _tvBrowseRailKey, hubs: tvHubs, + focusMemory: _hubFocusMemory, iconForHub: (hub, _) => _rowIcon(_rowForHub(hub)?.row), onFocusedItemChanged: _setSpotlightItem, loadMoreItems: (hub) { diff --git a/lib/screens/focusable_detail_screen_mixin.dart b/lib/screens/focusable_detail_screen_mixin.dart index 37b7bba8..02c7bc9c 100644 --- a/lib/screens/focusable_detail_screen_mixin.dart +++ b/lib/screens/focusable_detail_screen_mixin.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import '../focus/focusable_action_bar.dart'; import '../focus/input_mode_tracker.dart'; import '../focus/key_event_utils.dart'; +import '../i18n/strings.g.dart'; import '../media/media_item.dart'; import '../media/media_playlist.dart'; import '../mixins/grid_focus_node_mixin.dart'; @@ -195,6 +196,7 @@ mixin FocusableDetailScreenMixin on State, GridFocu key: Key(_idForItem(item)), item: item, focusNode: focusNode, + semanticValue: _semanticPosition(position), disableScale: position.disableScale, onRefresh: onRefresh, collectionId: collectionId, @@ -233,7 +235,8 @@ mixin FocusableDetailScreenMixin on State, GridFocu final fullCardLayout = PlatformDetector.isTV() && svc.read(SettingsService.tvFullCardLayout); final useFullCardLayout = fullCardLayout && shape != CardShape.square; - Widget buildTile(int index, {required bool inFirstRow, required bool disableScale}) { + Widget buildTile(MediaCardSliverPosition position) { + final index = position.index; final item = itemAt(index); if (item == null) { onSkeletonVisible?.call(index); @@ -244,13 +247,14 @@ mixin FocusableDetailScreenMixin on State, GridFocu key: Key(item.id), item: item, focusNode: focusNode, - disableScale: disableScale, + semanticValue: _semanticPosition(position), + disableScale: position.disableScale, onRefresh: onRefresh, collectionId: collectionId, onListRefresh: onListRefresh, - fullBleedImage: useFullCardLayout && !disableScale, + fullBleedImage: useFullCardLayout && position.isGrid, cardShapeOverride: shape, - onNavigateUp: inFirstRow ? navigateToAppBar : null, + onNavigateUp: position.isFirstRow ? navigateToAppBar : null, onBack: handleBackFromContent, onFocusChange: (hasFocus) => trackGridItemFocus(index, hasFocus), ); @@ -263,10 +267,23 @@ mixin FocusableDetailScreenMixin on State, GridFocu padding: const EdgeInsets.all(8), fullBleedImage: useFullCardLayout, shape: shape, - itemBuilder: (context, position) => - buildTile(position.index, inFirstRow: position.isFirstRow, disableScale: position.disableScale), + itemBuilder: (context, position) => buildTile(position), ); }, ); } + + String _semanticPosition(MediaCardSliverPosition position) { + if (!position.isGrid) { + return t.accessibility.rowPosition(row: position.index + 1, rowCount: position.itemCount); + } + + final rowCount = (position.itemCount + position.columnCount - 1) ~/ position.columnCount; + return t.accessibility.rowColumnPosition( + row: position.index ~/ position.columnCount + 1, + rowCount: rowCount, + column: position.index % position.columnCount + 1, + columnCount: position.columnCount, + ); + } } diff --git a/lib/screens/libraries/alpha_jump_bar.dart b/lib/screens/libraries/alpha_jump_bar.dart index d22a40c2..aff12331 100644 --- a/lib/screens/libraries/alpha_jump_bar.dart +++ b/lib/screens/libraries/alpha_jump_bar.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../focus/key_event_utils.dart'; +import '../../i18n/strings.g.dart'; import '../../media/library_first_character.dart'; import '../../widgets/clickable_cursor.dart'; import 'alpha_jump_helper.dart'; @@ -217,73 +218,89 @@ class _AlphaJumpBarState extends State { final markerSize = (letterSlotHeight - 2).clamp(10.0, 18.0).toDouble(); final fontSize = (letterSlotHeight * 0.58).clamp(7.0, 10.0).toDouble(); - return ClickableCursor( - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTapDown: (details) { - final idx = _letterIndexFromDy(details.localPosition.dy, constraints.maxHeight); - setState(() => _highlightedIndex = idx); - _jumpToLetter(_displayed[idx]); - }, - onVerticalDragUpdate: (details) { - final idx = _letterIndexFromDy(details.localPosition.dy, constraints.maxHeight); - if (idx != _highlightedIndex) { + return Semantics( + container: true, + explicitChildNodes: true, + label: t.accessibility.alphabetNavigation, + child: ClickableCursor( + child: GestureDetector( + excludeFromSemantics: true, + behavior: HitTestBehavior.opaque, + onTapDown: (details) { + final idx = _letterIndexFromDy(details.localPosition.dy, constraints.maxHeight); setState(() => _highlightedIndex = idx); _jumpToLetter(_displayed[idx]); - } - }, - child: Container( - width: 20, - decoration: BoxDecoration( - color: colorScheme.surface.withValues(alpha: 0.7), - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - child: Column( - mainAxisAlignment: .spaceEvenly, - children: List.generate(_displayed.length, (i) { - final letter = _displayed[i]; - final isCurrent = letter == currentLetter && !_hasFocus; - final isHighlighted = _hasFocus && i == _highlightedIndex; + }, + onVerticalDragUpdate: (details) { + final idx = _letterIndexFromDy(details.localPosition.dy, constraints.maxHeight); + if (idx != _highlightedIndex) { + setState(() => _highlightedIndex = idx); + _jumpToLetter(_displayed[idx]); + } + }, + child: Container( + width: 20, + decoration: BoxDecoration( + color: colorScheme.surface.withValues(alpha: 0.7), + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + child: Column( + mainAxisAlignment: .spaceEvenly, + children: List.generate(_displayed.length, (i) { + final letter = _displayed[i]; + final isCurrent = letter == currentLetter && !_hasFocus; + final isHighlighted = _hasFocus && i == _highlightedIndex; - BoxDecoration? decoration; - if (isHighlighted) { - decoration = BoxDecoration(color: colorScheme.primary, shape: BoxShape.circle); - } else if (isCurrent) { - decoration = BoxDecoration( - color: colorScheme.primary.withValues(alpha: 0.3), - shape: BoxShape.circle, - ); - } + BoxDecoration? decoration; + if (isHighlighted) { + decoration = BoxDecoration(color: colorScheme.primary, shape: BoxShape.circle); + } else if (isCurrent) { + decoration = BoxDecoration( + color: colorScheme.primary.withValues(alpha: 0.3), + shape: BoxShape.circle, + ); + } - Color letterColor; - if (isHighlighted) { - letterColor = colorScheme.onPrimary; - } else if (isCurrent) { - letterColor = colorScheme.primary; - } else { - letterColor = colorScheme.onSurface; - } + Color letterColor; + if (isHighlighted) { + letterColor = colorScheme.onPrimary; + } else if (isCurrent) { + letterColor = colorScheme.primary; + } else { + letterColor = colorScheme.onSurface; + } - return SizedBox( - height: letterSlotHeight, - child: Center( - child: Container( - width: markerSize, - height: markerSize, - decoration: decoration, - alignment: .center, - child: Text( - letter, - style: TextStyle( - fontSize: fontSize, - fontWeight: (isCurrent || isHighlighted) ? FontWeight.bold : FontWeight.normal, - color: letterColor, + return Semantics( + button: true, + selected: isCurrent || isHighlighted, + label: letter, + excludeSemantics: true, + onTap: () { + setState(() => _highlightedIndex = i); + _jumpToLetter(letter); + }, + child: SizedBox( + height: letterSlotHeight, + child: Center( + child: Container( + width: markerSize, + height: markerSize, + decoration: decoration, + alignment: .center, + child: Text( + letter, + style: TextStyle( + fontSize: fontSize, + fontWeight: (isCurrent || isHighlighted) ? FontWeight.bold : FontWeight.normal, + color: letterColor, + ), + ), ), ), ), - ), - ); - }), + ); + }), + ), ), ), ), diff --git a/lib/screens/libraries/alpha_scroll_handle.dart b/lib/screens/libraries/alpha_scroll_handle.dart index cac1ba73..d47fcdce 100644 --- a/lib/screens/libraries/alpha_scroll_handle.dart +++ b/lib/screens/libraries/alpha_scroll_handle.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import '../../i18n/strings.g.dart'; import '../../media/library_first_character.dart'; import 'alpha_jump_helper.dart'; @@ -141,88 +142,117 @@ class _AlphaScrollHandleState extends State with SingleTicker _scheduleHide(); } + int get _semanticLetterIndex { + if (_helper.letters.isEmpty) return -1; + final index = _helper.letters.indexOf(_dragLetter ?? widget.currentLetter); + return index < 0 ? 0 : index; + } + + void _stepSemantics(int delta) { + final currentIndex = _semanticLetterIndex; + if (currentIndex < 0) return; + final targetIndex = (currentIndex + delta).clamp(0, _helper.letters.length - 1); + if (targetIndex == currentIndex) return; + final letter = _helper.letters[targetIndex]; + _show(); + _scheduleHide(); + widget.onJump(_helper.indexForLetter(letter) ?? 0); + } + @override Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _opacityController, - builder: (context, child) { - final opacity = _opacityController.value; - // Prevent stealing taps when fully hidden - if (opacity == 0.0) return const SizedBox.shrink(); + final semanticIndex = _semanticLetterIndex; + return Semantics( + label: t.accessibility.alphabetNavigation, + value: _dragLetter ?? widget.currentLetter, + hint: t.accessibility.alphabetScrollHint, + increasedValue: semanticIndex >= 0 && semanticIndex < _helper.letters.length - 1 + ? _helper.letters[semanticIndex + 1] + : null, + decreasedValue: semanticIndex > 0 ? _helper.letters[semanticIndex - 1] : null, + onIncrease: semanticIndex >= 0 && semanticIndex < _helper.letters.length - 1 ? () => _stepSemantics(1) : null, + onDecrease: semanticIndex > 0 ? () => _stepSemantics(-1) : null, + child: AnimatedBuilder( + animation: _opacityController, + builder: (context, child) { + final opacity = _opacityController.value; + return IgnorePointer( + ignoring: opacity == 0.0, + child: Opacity(opacity: opacity, child: child), + ); + }, + child: LayoutBuilder( + builder: (context, constraints) { + final trackHeight = constraints.maxHeight; + _trackHeight = trackHeight; - return Opacity(opacity: opacity, child: child); - }, - child: LayoutBuilder( - builder: (context, constraints) { - final trackHeight = constraints.maxHeight; - _trackHeight = trackHeight; + final fraction = _isDragging && _dragFraction != null + ? _dragFraction! + : _helper.fractionForLetter(widget.currentLetter); + final usableHeight = trackHeight - _handleHeight; + final handleTop = usableHeight > 0 ? (fraction * usableHeight) : 0.0; - final fraction = _isDragging && _dragFraction != null - ? _dragFraction! - : _helper.fractionForLetter(widget.currentLetter); - final usableHeight = trackHeight - _handleHeight; - final handleTop = usableHeight > 0 ? (fraction * usableHeight) : 0.0; - - final colorScheme = Theme.of(context).colorScheme; - - return SizedBox( - width: _touchTargetWidth + _bubbleSize + _bubbleMarginRight, - height: trackHeight, - child: Stack( - clipBehavior: Clip.none, - children: [ - // Handle pill with touch target โ€” only around the pill's position, - // not the full track, so it doesn't steal scroll gestures. - // Extra vertical padding makes it easier to grab. - Positioned( - right: 0, - top: handleTop - _touchTargetVerticalPadding, - child: MouseRegion( - cursor: SystemMouseCursors.resizeUpDown, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onVerticalDragStart: _onDragStart, - onVerticalDragUpdate: _onDragUpdate, - onVerticalDragEnd: _onDragEnd, - child: SizedBox( - width: _touchTargetWidth, - height: _handleHeight + _touchTargetVerticalPadding * 2, - child: Align( - alignment: .centerRight, - child: Container( - margin: const EdgeInsets.only(right: 2), - width: _handleWidth, - height: _handleHeight, - decoration: BoxDecoration( - color: colorScheme.onSurface.withValues(alpha: 0.5), - borderRadius: const BorderRadius.all(Radius.circular(_handleRadius)), + final colorScheme = Theme.of(context).colorScheme; + return SizedBox( + width: _touchTargetWidth + _bubbleSize + _bubbleMarginRight, + height: trackHeight, + child: Stack( + clipBehavior: Clip.none, + children: [ + // Handle pill with touch target โ€” only around the pill's position, + // not the full track, so it doesn't steal scroll gestures. + // Extra vertical padding makes it easier to grab. + Positioned( + right: 0, + top: handleTop - _touchTargetVerticalPadding, + child: MouseRegion( + cursor: SystemMouseCursors.resizeUpDown, + child: GestureDetector( + excludeFromSemantics: true, + behavior: HitTestBehavior.opaque, + onVerticalDragStart: _onDragStart, + onVerticalDragUpdate: _onDragUpdate, + onVerticalDragEnd: _onDragEnd, + child: SizedBox( + width: _touchTargetWidth, + height: _handleHeight + _touchTargetVerticalPadding * 2, + child: Align( + alignment: .centerRight, + child: Container( + margin: const EdgeInsets.only(right: 2), + width: _handleWidth, + height: _handleHeight, + decoration: BoxDecoration( + color: colorScheme.onSurface.withValues(alpha: 0.5), + borderRadius: const BorderRadius.all(Radius.circular(_handleRadius)), + ), ), ), ), ), ), ), - ), - // Letter bubble (only while dragging) - if (_isDragging && _dragLetter != null) - Positioned( - right: _touchTargetWidth + _bubbleMarginRight, - top: handleTop + (_handleHeight - _bubbleSize) / 2, - child: Container( - width: _bubbleSize, - height: _bubbleSize, - decoration: BoxDecoration(color: colorScheme.primary, shape: BoxShape.circle), - alignment: .center, - child: Text( - _dragLetter!, - style: TextStyle(color: colorScheme.onPrimary, fontSize: _bubbleFontSize, fontWeight: .bold), + // Letter bubble (only while dragging) + if (_isDragging && _dragLetter != null) + Positioned( + right: _touchTargetWidth + _bubbleMarginRight, + top: handleTop + (_handleHeight - _bubbleSize) / 2, + child: Container( + width: _bubbleSize, + height: _bubbleSize, + decoration: BoxDecoration(color: colorScheme.primary, shape: BoxShape.circle), + alignment: .center, + child: Text( + _dragLetter!, + style: TextStyle(color: colorScheme.onPrimary, fontSize: _bubbleFontSize, fontWeight: .bold), + ), ), ), - ), - ], - ), - ); - }, + ], + ), + ); + }, + ), ), ); } diff --git a/lib/screens/libraries/tabs/library_recommended_tab.dart b/lib/screens/libraries/tabs/library_recommended_tab.dart index ef508b2a..5826a780 100644 --- a/lib/screens/libraries/tabs/library_recommended_tab.dart +++ b/lib/screens/libraries/tabs/library_recommended_tab.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; import '../../../focus/hub_vertical_navigation.dart'; +import '../../../focus/locked_hub_controller.dart'; import '../../../i18n/strings.g.dart'; import '../../../media/media_hub.dart'; import '../../../media/media_item.dart'; @@ -50,9 +51,18 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState> _hubKeys = []; final _tvBrowseRailKey = GlobalKey(); final TvSpotlightController _spotlight = TvSpotlightController(); + HubFocusMemory _hubFocusMemory = HubFocusMemory(); void _setSpotlightItem(MediaItem item) => _spotlight.select(item); + @override + void didUpdateWidget(LibraryRecommendedTab oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.library.globalKey != widget.library.globalKey) { + _hubFocusMemory = HubFocusMemory(); + } + } + @override void dispose() { _spotlight.dispose(); @@ -305,6 +315,7 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState _getHubIcon(hub), onFocusedItemChanged: _setSpotlightItem, onRefresh: updateItem, diff --git a/lib/screens/livetv/tabs/whats_on_tab.dart b/lib/screens/livetv/tabs/whats_on_tab.dart index 7409cdb8..70c400b9 100644 --- a/lib/screens/livetv/tabs/whats_on_tab.dart +++ b/lib/screens/livetv/tabs/whats_on_tab.dart @@ -6,6 +6,7 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../../../focus/hub_vertical_navigation.dart'; +import '../../../focus/locked_hub_controller.dart'; import '../../../i18n/strings.g.dart'; import '../../../media/media_hub.dart'; import '../../../media/media_item.dart'; @@ -39,6 +40,7 @@ class WhatsOnTabState extends State Timer? _refreshTimer; final Map> _hubKeysById = {}; List> _hubKeys = []; + final _hubFocusMemory = HubFocusMemory(); bool _refreshRequested = true; bool _tickerEnabled = false; bool _appRefreshActive = true; @@ -210,6 +212,7 @@ class WhatsOnTabState extends State return HubSection( key: _hubKeys[index], hub: hub.mediaHub, + focusMemory: _hubFocusMemory, icon: Symbols.live_tv_rounded, cardSizing: HubCardSizing.grid, episodePosterModeOverride: EpisodePosterMode.seriesPoster, diff --git a/lib/screens/media_detail_screen.dart b/lib/screens/media_detail_screen.dart index 00b9e63f..e692356f 100644 --- a/lib/screens/media_detail_screen.dart +++ b/lib/screens/media_detail_screen.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:math' as math; import '../media/ids.dart'; -import 'dart:io'; import 'package:cached_network_image_ce/cached_network_image.dart'; import 'package:flutter/material.dart'; @@ -23,6 +22,7 @@ import '../focus/dpad_select_long_press_controller.dart'; import '../focus/focusable_action_bar.dart'; import '../focus/focusable_wrapper.dart'; import '../focus/hub_vertical_navigation.dart'; +import '../focus/locked_hub_controller.dart'; import '../focus/key_event_utils.dart'; import '../focus/input_mode_tracker.dart'; import '../widgets/cast_member_strip.dart'; @@ -288,6 +288,7 @@ class _MediaDetailScreenState extends State bool _hasLoadedExtras = false; bool _hasLoadedRelatedHubs = false; final _tvDetailRailKey = GlobalKey(); + final _hubFocusMemory = HubFocusMemory(); PageRoute? _route; RouteObserver>? _routeObserver; late final ScrollController _scrollController; @@ -361,6 +362,7 @@ class _MediaDetailScreenState extends State late final FocusNode _playButtonFocusNode; late final FocusNode _ratingChipFocusNode; + late final FocusNode _backButtonFocusNode; final _extrasSelectLongPress = DpadSelectLongPressController(); // Context menu key for the three-dots button @@ -687,6 +689,7 @@ class _MediaDetailScreenState extends State _extrasFocusNode.addListener(_handleExtrasFocusChange); _playButtonFocusNode = FocusNode(debugLabel: 'play_button'); _ratingChipFocusNode = FocusNode(debugLabel: 'rating_chip'); + _backButtonFocusNode = FocusNode(debugLabel: 'media_detail_back'); _overviewFocusNode = FocusNode(debugLabel: 'overview'); _infoRowsFocusNode = FocusNode(debugLabel: 'info_rows'); _loadFullMetadata(); @@ -881,6 +884,7 @@ class _MediaDetailScreenState extends State _focusedExtraIndexNotifier.dispose(); _playButtonFocusNode.dispose(); _ratingChipFocusNode.dispose(); + _backButtonFocusNode.dispose(); _overviewFocusNode.dispose(); _infoRowsFocusNode.dispose(); _extrasSelectLongPress.dispose(); @@ -1190,20 +1194,23 @@ class _MediaDetailScreenState extends State required ImageType imageType, Alignment alignment = Alignment.center, Widget Function(BuildContext, String, dynamic)? errorWidget, + Widget Function(BuildContext, String)? placeholder, }) { if (!widget.isOffline || _metadata.serverId == null) return null; for (final artworkPath in artworkPaths) { - final localPath = _offlineArtworkLocalPath(context, artworkPath); + final localPath = _offlineArtworkCandidatePath(context, artworkPath); if (localPath == null) continue; return OptimizedMediaImage( client: null, imagePath: null, localFilePath: localPath, + cacheMissingLocalFile: true, fit: fit, alignment: alignment, imageType: imageType, + placeholder: placeholder, errorWidget: errorWidget, ); } @@ -1211,11 +1218,9 @@ class _MediaDetailScreenState extends State return null; } - String? _offlineArtworkLocalPath(BuildContext context, String? artworkPath) { + String? _offlineArtworkCandidatePath(BuildContext context, String? artworkPath) { if (!widget.isOffline || _metadata.serverId == null) return null; - final localPath = context.read().getArtworkLocalPath(ServerId(_metadata.serverId!), artworkPath); - if (localPath == null || !File(localPath).existsSync()) return null; - return localPath; + return context.read().getArtworkLocalPath(ServerId(_metadata.serverId!), artworkPath); } String _syncRuleKeyForMetadata(BuildContext context, DownloadProvider downloadProvider, MediaItem metadata) { @@ -2235,6 +2240,7 @@ class _MediaDetailScreenState extends State artworkPaths: [posterPath], fit: BoxFit.cover, imageType: ImageType.poster, + placeholder: (context, url) => const PlaceholderContainer(), errorWidget: (context, url, error) => const PlaceholderContainer(), ); topImage = SizedBox( @@ -3044,6 +3050,7 @@ class _MediaDetailScreenState extends State final backButton = AppBarBackButton( style: BackButtonStyle.plain, onPressed: () => Navigator.pop(context, _watchStateChanged), + focusNode: _backButtonFocusNode, ); final loading = ListenableBuilder( listenable: FullscreenStateManager(), @@ -3219,6 +3226,7 @@ class _MediaDetailScreenState extends State HubSection( key: _relatedHubKeys[i], hub: _relatedHubs[i], + focusMemory: _hubFocusMemory, icon: _getRelatedHubIcon(_relatedHubs[i]), inset: true, onVerticalNavigation: (isUp) => _handleRelatedHubNavigation(i, isUp), @@ -3294,6 +3302,7 @@ class _MediaDetailScreenState extends State AppBarBackButton( style: BackButtonStyle.circular, onPressed: () => Navigator.pop(context, _watchStateChanged), + focusNode: _backButtonFocusNode, ), context: context, )!, @@ -3353,6 +3362,7 @@ class _MediaDetailScreenState extends State AppBarBackButton( style: BackButtonStyle.circular, onPressed: () => Navigator.pop(context, _watchStateChanged), + focusNode: _backButtonFocusNode, ), context: context, )!, @@ -3365,6 +3375,7 @@ class _MediaDetailScreenState extends State child: TvBrowseRail( key: _tvDetailRailKey, hubs: detailHubs, + focusMemory: _hubFocusMemory, iconForHub: _getTvDetailHubIcon, onFocusedHubItemChanged: _handleTvDetailFocusedRailItemChanged, onRefresh: (source) => unawaited(_refreshItemInPlace(source)), @@ -3399,7 +3410,9 @@ class _MediaDetailScreenState extends State item: metadata, client: _getArtworkMediaClient(context), showInfo: false, - localArtworkPathResolver: widget.isOffline ? (path) => _offlineArtworkLocalPath(context, path) : null, + localArtworkPathResolver: widget.isOffline + ? (path) => _offlineArtworkCandidatePath(context, path) + : null, allowNetwork: !widget.isOffline, ), _buildTvDetailRevealGate(revealContent, handleBack), @@ -3642,6 +3655,7 @@ class _MediaDetailScreenState extends State fit: BoxFit.contain, alignment: .centerLeft, imageType: ImageType.heroLogo, + placeholder: (context, url) => titleFallback(context), errorWidget: (context, url, error) => titleFallback(context), ); if (localArtwork != null) return localArtwork; @@ -4184,7 +4198,7 @@ class _MediaDetailScreenState extends State fallbackImagePaths: heroArtPaths, client: _getArtworkMediaClient(context), localArtworkPathResolver: widget.isOffline - ? (path) => _offlineArtworkLocalPath(context, path) + ? (path) => _offlineArtworkCandidatePath(context, path) : null, allowNetwork: !widget.isOffline, width: size.width, diff --git a/lib/screens/profile/borrow_connection_screen.dart b/lib/screens/profile/borrow_connection_screen.dart index 8559bc3d..6ccc427d 100644 --- a/lib/screens/profile/borrow_connection_screen.dart +++ b/lib/screens/profile/borrow_connection_screen.dart @@ -60,7 +60,22 @@ class _BorrowConnectionScreenState extends State { _candidatesFuture = _loadCandidates(); } + void _retryCandidates() { + setState(() { + _candidatesFuture = _loadCandidates(); + }); + } + Future> _loadCandidates() async { + try { + return await _loadCandidatesUnchecked(); + } catch (error, stackTrace) { + appLogger.w('Borrow candidate load failed', error: error, stackTrace: stackTrace); + Error.throwWithStackTrace(error, stackTrace); + } + } + + Future> _loadCandidatesUnchecked() async { final pcRegistry = context.read(); final connRegistry = context.read(); final profileRegistry = context.read(); @@ -159,7 +174,59 @@ class _BorrowConnectionScreenState extends State { return FutureBuilder>( future: _candidatesFuture, builder: (context, snapshot) { - final candidates = snapshot.data ?? const <_BorrowCandidate>[]; + late final Widget candidateSliver; + if (snapshot.connectionState != ConnectionState.done) { + candidateSliver = LoadingIndicatorBox.sliver; + } else if (snapshot.hasError) { + candidateSliver = SliverFillRemaining( + child: ErrorStateWidget( + message: t.profiles.borrowLoadFailed, + onRetry: _retryCandidates, + actionAutofocus: true, + actionUseBackgroundFocus: true, + ), + ); + } else { + final candidates = snapshot.requireData; + if (candidates.isEmpty) { + candidateSliver = SliverFillRemaining( + child: EmptyStateWidget( + message: t.profiles.borrowEmpty, + subtitle: t.profiles.borrowEmptySubtitle, + icon: Symbols.share_rounded, + iconSize: 48, + ), + ); + } else { + candidateSliver = SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + final cand = candidates[index]; + // M3E connected-group geometry: large outer corners, small + // inner corners, hairline gaps between tiles. + final tokensRef = tokens(context); + final tileRadii = BorderRadius.vertical( + top: Radius.circular(index == 0 ? tokensRef.radiusLg : tokensRef.radiusXs), + bottom: Radius.circular(index == candidates.length - 1 ? tokensRef.radiusLg : tokensRef.radiusXs), + ); + return Padding( + padding: EdgeInsets.fromLTRB(16, index == 0 ? 4 : tokensRef.groupGap, 16, 0), + child: FocusableWrapper( + autofocus: index == 0, + disableScale: true, + borderRadii: tileRadii, + onSelect: _busy ? null : () => _borrow(cand), + child: Card( + shape: RoundedRectangleBorder(borderRadius: tileRadii), + clipBehavior: Clip.antiAlias, + child: _BorrowTile(candidate: cand, borderRadius: tileRadii, onTap: () => _borrow(cand)), + ), + ), + ); + }, childCount: candidates.length), + ); + } + } + return FocusedScrollScaffold( title: Text(t.profiles.borrowAddTo(displayName: widget.targetProfile.displayName)), slivers: [ @@ -169,44 +236,7 @@ class _BorrowConnectionScreenState extends State { child: Text(t.profiles.borrowExplain, style: Theme.of(context).textTheme.bodySmall), ), ), - if (snapshot.connectionState != ConnectionState.done) - LoadingIndicatorBox.sliver - else if (candidates.isEmpty) - SliverFillRemaining( - child: EmptyStateWidget( - message: t.profiles.borrowEmpty, - subtitle: t.profiles.borrowEmptySubtitle, - icon: Symbols.share_rounded, - iconSize: 48, - ), - ) - else - SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - final cand = candidates[index]; - // M3E connected-group geometry: large outer corners, small - // inner corners, hairline gaps between tiles. - final tokensRef = tokens(context); - final tileRadii = BorderRadius.vertical( - top: Radius.circular(index == 0 ? tokensRef.radiusLg : tokensRef.radiusXs), - bottom: Radius.circular(index == candidates.length - 1 ? tokensRef.radiusLg : tokensRef.radiusXs), - ); - return Padding( - padding: EdgeInsets.fromLTRB(16, index == 0 ? 4 : tokensRef.groupGap, 16, 0), - child: FocusableWrapper( - autofocus: index == 0, - disableScale: true, - borderRadii: tileRadii, - onSelect: _busy ? null : () => _borrow(cand), - child: Card( - shape: RoundedRectangleBorder(borderRadius: tileRadii), - clipBehavior: Clip.antiAlias, - child: _BorrowTile(candidate: cand, borderRadius: tileRadii, onTap: () => _borrow(cand)), - ), - ), - ); - }, childCount: candidates.length), - ), + candidateSliver, ], ); }, diff --git a/lib/screens/profile/pin_entry_dialog.dart b/lib/screens/profile/pin_entry_dialog.dart index df25dd4d..fd9a693c 100644 --- a/lib/screens/profile/pin_entry_dialog.dart +++ b/lib/screens/profile/pin_entry_dialog.dart @@ -421,12 +421,15 @@ class _TvPinInputState extends State<_TvPinInput> with ControllerDisposerMixin { } } - return KeyEventResult.handled; + return KeyEventResult.ignored; } void _onMobilePinChanged(String value) { final digitsOnly = value.replaceAll(RegExp(r'\D'), ''); - final pin = digitsOnly.length > 4 ? digitsOnly.substring(0, 4) : digitsOnly; + final pin = switch (digitsOnly.length) { + <= 4 => digitsOnly, + _ => digitsOnly.substring(0, expandToGraphemeRange(digitsOnly, const TextRange(start: 0, end: 4)).end), + }; if (pin != value) { _mobileController.value = TextEditingValue( text: pin, diff --git a/lib/screens/settings/hotkey_recorder_widget.dart b/lib/screens/settings/hotkey_recorder_widget.dart index 47e2b4da..d9066af1 100644 --- a/lib/screens/settings/hotkey_recorder_widget.dart +++ b/lib/screens/settings/hotkey_recorder_widget.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:plezy/widgets/app_icon.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -11,7 +13,7 @@ import '../../widgets/dialog_action_button.dart'; class HotKeyRecorderWidget extends StatefulWidget { final String actionName; final HotKey? currentHotKey; - final Function(HotKey) onHotKeyRecorded; + final FutureOr Function(HotKey?) onHotKeyRecorded; final VoidCallback onCancel; const HotKeyRecorderWidget({ @@ -29,6 +31,8 @@ class HotKeyRecorderWidget extends StatefulWidget { class _HotKeyRecorderWidgetState extends State { HotKey? _recordedHotKey; bool _isCapturing = false; + bool _hasPendingEdit = false; + bool _isSaving = false; final _recorderFocusNode = FocusNode(debugLabel: 'HotKeyRecorder.record'); final _clearFocusNode = FocusNode(debugLabel: 'HotKeyRecorder.clear'); final _cancelFocusNode = FocusNode(debugLabel: 'HotKeyRecorder.cancel'); @@ -50,6 +54,7 @@ class _HotKeyRecorderWidgetState extends State { } void _startCapturing() { + if (_isSaving) return; setState(() => _isCapturing = true); _recorderFocusNode.requestFocus(); } @@ -57,6 +62,7 @@ class _HotKeyRecorderWidgetState extends State { void _handleHotKeyRecorded(HotKey hotKey) { setState(() { _recordedHotKey = hotKey; + _hasPendingEdit = true; _isCapturing = false; }); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -65,115 +71,139 @@ class _HotKeyRecorderWidgetState extends State { } void _clearShortcut() { + if (_isSaving) return; setState(() { _recordedHotKey = null; + _hasPendingEdit = true; _isCapturing = false; }); _recorderFocusNode.requestFocus(); } + void _cancel() { + if (!_isSaving) widget.onCancel(); + } + + Future _save() async { + final canSave = (_recordedHotKey != null || _hasPendingEdit) && !_isCapturing && !_isSaving; + if (!canSave) return; + + final hotkey = _recordedHotKey; + setState(() => _isSaving = true); + try { + await widget.onHotKeyRecorded(hotkey); + } finally { + if (mounted) setState(() => _isSaving = false); + } + } + @override Widget build(BuildContext context) { final hasShortcut = _recordedHotKey != null; + final canEdit = !_isSaving; + final canSave = (hasShortcut || _hasPendingEdit) && !_isCapturing && !_isSaving; final recordLabel = _isCapturing ? t.hotkeys.recordingShortcut : t.hotkeys.pressToRecord; - return AlertDialog( - title: Text(t.hotkeys.setShortcutFor(actionName: widget.actionName)), - content: SizedBox( - width: double.maxFinite, - child: SingleChildScrollView( - child: Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - children: [ - Text( - t.hotkeys.currentShortcut, - style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: .bold), - ), - const SizedBox(height: 6), - Row( - children: [ - Expanded( - child: FocusableWrapper( - focusNode: _recorderFocusNode, - autofocus: true, - onSelect: _startCapturing, - onBack: widget.onCancel, - onNavigateRight: hasShortcut ? _clearFocusNode.requestFocus : null, - onNavigateDown: (hasShortcut ? _saveFocusNode : _cancelFocusNode).requestFocus, - semanticLabel: recordLabel, - descendantsAreFocusable: false, - useBackgroundFocus: true, - child: GestureDetector( - onTap: _startCapturing, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - border: Border.fromBorderSide(BorderSide(color: Theme.of(context).dividerColor)), - borderRadius: const BorderRadius.all(Radius.circular(6)), - ), - child: HotKeyRecorder( - initalHotKey: _recordedHotKey, - enabled: _isCapturing, - placeholder: Text(recordLabel), - onHotKeyRecorded: _handleHotKeyRecorded, + return PopScope( + canPop: !_isSaving, + child: AlertDialog( + title: Text(t.hotkeys.setShortcutFor(actionName: widget.actionName)), + content: SizedBox( + width: double.maxFinite, + child: SingleChildScrollView( + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Text( + t.hotkeys.currentShortcut, + style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: .bold), + ), + const SizedBox(height: 6), + Row( + children: [ + Expanded( + child: FocusableWrapper( + focusNode: _recorderFocusNode, + autofocus: true, + onSelect: canEdit ? _startCapturing : null, + onBack: _cancel, + onNavigateRight: canEdit && hasShortcut ? _clearFocusNode.requestFocus : null, + onNavigateDown: (canSave ? _saveFocusNode : _cancelFocusNode).requestFocus, + semanticLabel: recordLabel, + descendantsAreFocusable: false, + useBackgroundFocus: true, + child: GestureDetector( + onTap: canEdit ? _startCapturing : null, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + border: Border.fromBorderSide(BorderSide(color: Theme.of(context).dividerColor)), + borderRadius: const BorderRadius.all(Radius.circular(6)), + ), + child: HotKeyRecorder( + initalHotKey: _recordedHotKey, + enabled: _isCapturing && canEdit, + placeholder: Text(recordLabel), + onHotKeyRecorded: _handleHotKeyRecorded, + ), ), ), ), ), - ), - if (hasShortcut) ...[ - const SizedBox(width: 8), - FocusableButton( - focusNode: _clearFocusNode, - onPressed: _clearShortcut, - onBack: widget.onCancel, - onNavigateLeft: _recorderFocusNode.requestFocus, - onNavigateDown: _saveFocusNode.requestFocus, - autoScroll: false, - child: IconButton( - icon: const AppIcon(Symbols.backspace_rounded, fill: 1, size: 18), - onPressed: _clearShortcut, - padding: .zero, - constraints: const BoxConstraints(minWidth: 24, minHeight: 24), - tooltip: t.hotkeys.clearShortcut, + if (hasShortcut) ...[ + const SizedBox(width: 8), + FocusableButton( + focusNode: _clearFocusNode, + onPressed: canEdit ? _clearShortcut : null, + onBack: _cancel, + onNavigateLeft: _recorderFocusNode.requestFocus, + onNavigateDown: _saveFocusNode.requestFocus, + autoScroll: false, + child: IconButton( + icon: const AppIcon(Symbols.backspace_rounded, fill: 1, size: 18), + onPressed: canEdit ? _clearShortcut : null, + padding: .zero, + constraints: const BoxConstraints(minWidth: 24, minHeight: 24), + tooltip: t.hotkeys.clearShortcut, + ), ), - ), + ], ], - ], - ), - const SizedBox(height: 8), - Text( - recordLabel, - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7)), - textAlign: TextAlign.center, - ), - ], + ), + const SizedBox(height: 8), + Text( + recordLabel, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), + ), + textAlign: TextAlign.center, + ), + ], + ), ), ), + actions: [ + DialogActionButton( + focusNode: _cancelFocusNode, + onPressed: canEdit ? _cancel : null, + onBack: _cancel, + onNavigateUp: _recorderFocusNode.requestFocus, + onNavigateRight: canSave ? _saveFocusNode.requestFocus : null, + label: t.common.cancel, + ), + DialogActionButton( + focusNode: _saveFocusNode, + onPressed: canSave ? _save : null, + onBack: _cancel, + onNavigateUp: _recorderFocusNode.requestFocus, + onNavigateLeft: _cancelFocusNode.requestFocus, + label: t.common.save, + isPrimary: true, + ), + ], ), - actions: [ - DialogActionButton( - focusNode: _cancelFocusNode, - onPressed: widget.onCancel, - onBack: widget.onCancel, - onNavigateUp: _recorderFocusNode.requestFocus, - onNavigateRight: _saveFocusNode.requestFocus, - label: t.common.cancel, - ), - DialogActionButton( - focusNode: _saveFocusNode, - onPressed: hasShortcut ? () => widget.onHotKeyRecorded(_recordedHotKey!) : null, - onBack: widget.onCancel, - onNavigateUp: _recorderFocusNode.requestFocus, - onNavigateLeft: _cancelFocusNode.requestFocus, - label: t.common.save, - isPrimary: true, - ), - ], ); } } diff --git a/lib/screens/settings/keyboard_shortcuts_screen.dart b/lib/screens/settings/keyboard_shortcuts_screen.dart index 01f3f6c1..ab34ccc1 100644 --- a/lib/screens/settings/keyboard_shortcuts_screen.dart +++ b/lib/screens/settings/keyboard_shortcuts_screen.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import '../../i18n/strings.g.dart'; import '../../models/hotkey_model.dart'; import '../../services/keyboard_shortcuts_service.dart'; +import '../../utils/app_logger.dart'; import '../../services/shader_service.dart'; import '../../utils/dialogs.dart'; import '../../utils/snackbar_helper.dart'; @@ -58,11 +60,11 @@ class KeyboardShortcutsScreen extends StatelessWidget { borderRadius: BorderRadius.circular(tokens(context).radiusSm), ), child: Text( - keyboardService.formatHotkey(hotkeys[action]!), + keyboardService.formatHotkey(hotkeys[action]), style: const TextStyle(fontFamily: 'monospace'), ), ), - onTap: () => _editHotkey(context, action, hotkeys[action]!), + onTap: () => _editHotkey(context, action, hotkeys[action]), ), ], ), @@ -79,37 +81,42 @@ class KeyboardShortcutsScreen extends StatelessWidget { if (context.mounted) showSuccessSnackBar(context, t.settings.shortcutsReset); } - void _editHotkey(BuildContext screenContext, String action, HotKey currentHotkey) { + void _editHotkey(BuildContext screenContext, String action, HotKey? currentHotkey) { + final actionId = action; + final actionName = keyboardService.getActionDisplayName(actionId); showScopedDialog( context: screenContext, builder: (BuildContext context) { return HotKeyRecorderWidget( - actionName: keyboardService.getActionDisplayName(action), + actionName: actionName, currentHotKey: currentHotkey, onHotKeyRecorded: (newHotkey) async { final navigator = Navigator.of(context); - // Check for conflicts - final existingAction = keyboardService.getActionForHotkey(newHotkey); - if (existingAction != null && existingAction != action) { - navigator.pop(); - showErrorSnackBar( - context, - t.settings.shortcutAlreadyAssigned(action: keyboardService.getActionDisplayName(existingAction)), - ); + if (newHotkey != null) { + final existingAction = keyboardService.getActionForHotkey(newHotkey); + if (existingAction != null && existingAction != actionId) { + navigator.pop(); + showErrorSnackBar( + screenContext, + t.settings.shortcutAlreadyAssigned(action: keyboardService.getActionDisplayName(existingAction)), + ); + return; + } + } + + try { + await keyboardService.setHotkey(actionId, newHotkey); + } on PlatformException catch (error, stackTrace) { + appLogger.e('Failed to update keyboard shortcut', error: error, stackTrace: stackTrace); + if (screenContext.mounted) showErrorSnackBar(screenContext, t.common.error); return; } - // Save the new hotkey - await keyboardService.setHotkey(action, newHotkey); - + if (!context.mounted) return; navigator.pop(); - if (screenContext.mounted) { - showSuccessSnackBar( - screenContext, - t.settings.shortcutUpdated(action: keyboardService.getActionDisplayName(action)), - ); + showSuccessSnackBar(screenContext, t.settings.shortcutUpdated(action: actionName)); } }, onCancel: () => Navigator.pop(context), diff --git a/lib/screens/settings/mpv_config_screen.dart b/lib/screens/settings/mpv_config_screen.dart index 515e92e6..5c4e6d05 100644 --- a/lib/screens/settings/mpv_config_screen.dart +++ b/lib/screens/settings/mpv_config_screen.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:plezy/widgets/app_icon.dart'; @@ -9,6 +11,8 @@ import '../../i18n/strings.g.dart'; import '../../mixins/controller_disposer_mixin.dart'; import '../../models/mpv_config_models.dart'; import '../../utils/dialogs.dart'; +import '../../utils/app_logger.dart'; +import '../../utils/debouncer.dart'; import '../../utils/platform_detector.dart'; import '../../utils/snackbar_helper.dart'; import '../../mixins/settings_effect_mixin.dart'; @@ -35,27 +39,145 @@ class _MpvConfigScreenState extends State with SettingsEffectMi ); final _savePresetFocusNode = FocusNode(); final _textFieldFocusNode = FocusNode(); + final _saveDebouncer = Debouncer(const Duration(milliseconds: 400)); + String _persistedText = ''; + int _revision = 0; + int _persistedRevision = 0; + _QueuedMpvConfig? _pendingSave; + _QueuedMpvConfig? _activeSave; + Future? _drainFuture; + bool _isLeaving = false; + bool _allowPop = false; + bool _disposing = false; @override void initState() { super.initState(); - // Sync the editor when the pref is mutated externally (e.g. loadMpvPreset). - // Skip when the listener fires for the same value the controller already - // holds โ€” avoids fighting user-typed text mid-edit. - bindEffect(SettingsService.mpvConfigText, (v) { - if (_textController.text != v) _textController.text = v; - }, fireImmediately: false); + _persistedText = _textController.text; + _textFieldFocusNode.addListener(_handleTextFieldFocusChanged); + // Keep a clean editor synchronized with imports, reset, and other + // settings producers without allowing a completed local write to replace + // a newer queued edit. + bindEffect(SettingsService.mpvConfigText, _handlePersistedText, fireImmediately: false); } @override void dispose() { + _disposing = true; + _saveDebouncer.dispose(); + _textFieldFocusNode.removeListener(_handleTextFieldFocusChanged); + if (_pendingSave != null || _drainFuture != null) { + unawaited(_flushPending()); + } _savePresetFocusNode.dispose(); _textFieldFocusNode.dispose(); super.dispose(); } - Future _saveText() async { - await _settingsService.write(SettingsService.mpvConfigText, _textController.text); + bool get _hasUnsavedWork => _pendingSave != null || _activeSave != null || _drainFuture != null; + + void _handleTextFieldFocusChanged() { + if (!_textFieldFocusNode.hasFocus) { + unawaited(_flushPending()); + } + } + + void _handlePersistedText(String value) { + final active = _activeSave; + if (active != null && active.text == value) return; + if (active == null && _pendingSave == null && _textController.text == value) { + _persistedText = value; + return; + } + + // An import/reset/other producer wins when observed. An in-flight local + // write cannot be cancelled, so queue the external value behind it to + // ensure that obsolete write cannot become the final persisted value. + _saveDebouncer.cancel(); + final revision = ++_revision; + _persistedText = value; + _persistedRevision = revision; + _pendingSave = active == null ? null : _QueuedMpvConfig(text: value, revision: revision); + _textController.value = TextEditingValue( + text: value, + selection: TextSelection.collapsed(offset: value.length), + ); + _notifySaveStateChanged(); + } + + void _queueTextSave(String text) { + if (_disposing) return; + if (_activeSave == null && _pendingSave == null && text == _persistedText) { + _notifySaveStateChanged(); + return; + } + + _pendingSave = _QueuedMpvConfig(text: text, revision: ++_revision); + _saveDebouncer.run(() => unawaited(_flushPending())); + _notifySaveStateChanged(); + } + + Future _flushPending() { + _saveDebouncer.cancel(); + final existing = _drainFuture; + if (existing != null) return existing; + + late final Future drain; + drain = _drainPending().whenComplete(() { + if (identical(_drainFuture, drain)) { + _drainFuture = null; + _notifySaveStateChanged(); + } + }); + _drainFuture = drain; + _notifySaveStateChanged(); + return drain; + } + + Future _drainPending() async { + while (true) { + final next = _pendingSave; + if (next == null) return true; + + _pendingSave = null; + _activeSave = next; + try { + await _settingsService.write(SettingsService.mpvConfigText, next.text); + } catch (error, stackTrace) { + _pendingSave ??= next; + _activeSave = null; + appLogger.e('MPV configuration save failed', error: error, stackTrace: stackTrace); + if (mounted && !_disposing) showErrorSnackBar(context, t.settings.saveFailed); + return false; + } + + _activeSave = null; + if (next.revision >= _persistedRevision) { + _persistedRevision = next.revision; + _persistedText = next.text; + } + } + } + + void _notifySaveStateChanged() { + if (mounted && !_disposing) setState(() {}); + } + + Future _flushAndPop() async { + if (_isLeaving) return; + _isLeaving = true; + _notifySaveStateChanged(); + + final saved = await _flushPending(); + if (!mounted || _disposing) return; + if (!saved || _hasUnsavedWork) { + _isLeaving = false; + _notifySaveStateChanged(); + return; + } + + setState(() => _allowPop = true); + Navigator.pop(context); } Future _showSavePresetDialog() async { @@ -68,16 +190,21 @@ class _MpvConfigScreenState extends State with SettingsEffectMi hintText: t.mpvConfig.presetNameHint, ); - if (name != null && name.trim().isNotEmpty) { - await _settingsService.saveMpvPreset(name.trim(), _textController.text); - if (mounted) showSuccessSnackBar(context, t.mpvConfig.presetSaved); - } + if (name == null || name.trim().isEmpty) return; + if (!await _flushPending() || !mounted) return; + + await _settingsService.saveMpvPreset(name.trim(), _textController.text); + if (mounted) showSuccessSnackBar(context, t.mpvConfig.presetSaved); } Future _loadPreset(MpvPreset preset) async { - await _settingsService.loadMpvPreset(preset.name); - // Controller text is updated reactively via the bindEffect above. - if (mounted) showAppSnackBar(context, t.mpvConfig.presetLoaded); + _textController.value = TextEditingValue( + text: preset.text, + selection: TextSelection.collapsed(offset: preset.text.length), + ); + _queueTextSave(preset.text); + final saved = await _flushPending(); + if (mounted && saved) showAppSnackBar(context, t.mpvConfig.presetLoaded); } Future _deletePreset(MpvPreset preset) async { @@ -97,15 +224,20 @@ class _MpvConfigScreenState extends State with SettingsEffectMi listenable: _textFieldFocusNode, builder: (context, _) { return PopScope( - canPop: PlatformDetector.isHandheldIOS(context) && !_textFieldFocusNode.hasFocus, + canPop: + _allowPop || + (PlatformDetector.isHandheldIOS(context) && + !_textFieldFocusNode.hasFocus && + !_hasUnsavedWork && + !_isLeaving), onPopInvokedWithResult: (didPop, _) { - if (didPop) return; + if (didPop || _isLeaving) return; if (BackKeyCoordinator.consumeIfHandled()) return; BackKeyUpSuppressor.suppressBackUntilKeyUp(); if (_textFieldFocusNode.hasFocus && _savePresetFocusNode.canRequestFocus) { _savePresetFocusNode.requestFocus(); } else { - Navigator.pop(context); + unawaited(_flushAndPop()); } }, child: FocusedScrollScaffold( @@ -154,11 +286,12 @@ class _MpvConfigScreenState extends State with SettingsEffectMi final sel = _textController.selection; if (sel.isValid) { final text = _textController.text; - _textController.value = TextEditingValue( + final value = TextEditingValue( text: text.replaceRange(sel.start, sel.end, '\n'), selection: TextSelection.collapsed(offset: sel.start + 1), ); - _saveText(); + _textController.value = value; + _queueTextSave(value.text); } } return KeyEventResult.handled; @@ -187,7 +320,7 @@ class _MpvConfigScreenState extends State with SettingsEffectMi contentPadding: const EdgeInsets.all(12), ), style: const TextStyle(fontFamily: 'monospace', fontSize: 13), - onChanged: (_) => _saveText(), + onChanged: _queueTextSave, ), ); } @@ -244,3 +377,10 @@ class _MpvConfigScreenState extends State with SettingsEffectMi ); } } + +class _QueuedMpvConfig { + const _QueuedMpvConfig({required this.text, required this.revision}); + + final String text; + final int revision; +} diff --git a/lib/screens/settings/services_settings_screen.dart b/lib/screens/settings/services_settings_screen.dart index 81ea61a1..0db542aa 100644 --- a/lib/screens/settings/services_settings_screen.dart +++ b/lib/screens/settings/services_settings_screen.dart @@ -151,8 +151,8 @@ class _ServiceHubRow extends StatelessWidget { subtitle: Text(username != null ? t.services.connectedAs(username: username!) : t.services.notConnected), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: onTap, - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: settingsRowDense(context), + visualDensity: settingsRowVisualDensity(context), ); } } diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 2f5c634f..a3cee2af 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -31,6 +31,7 @@ import '../../providers/trakt_account_provider.dart'; import '../../services/keyboard_shortcuts_service.dart'; import '../../services/settings_service.dart' as settings; import '../../services/update_service.dart'; +import '../../utils/app_logger.dart'; import '../../utils/dialogs.dart'; import '../../utils/snackbar_helper.dart'; import '../../utils/platform_detector.dart'; @@ -57,11 +58,22 @@ import 'settings_utils.dart'; import '../../widgets/loading_indicator_box.dart'; class SettingsScreen extends StatefulWidget { - const SettingsScreen({super.key, this.downloadDirectoryWritableChecker}); + const SettingsScreen({ + super.key, + this.downloadDirectoryWritableChecker, + this.settingsExporter, + this.settingsImporter, + }); @visibleForTesting final Future Function(Directory directory)? downloadDirectoryWritableChecker; + @visibleForTesting + final Future Function()? settingsExporter; + + @visibleForTesting + final Future Function()? settingsImporter; + @override State createState() => _SettingsScreenState(); } @@ -345,8 +357,8 @@ class _SettingsScreenState extends State with FocusableTab, Moun subtitle: Text(currentPath, maxLines: 2, overflow: .ellipsis), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showDownloadLocationDialog(), - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: false, + visualDensity: VisualDensity.standard, ); }, ), @@ -542,8 +554,8 @@ class _SettingsScreenState extends State with FocusableTab, Moun _checkForUpdates(); } }, - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: false, + visualDensity: VisualDensity.standard, ), _buildAutoCheckUpdatesOnStartupTile(), ], @@ -590,8 +602,8 @@ class _SettingsScreenState extends State with FocusableTab, Moun DialogActionButton(onPressed: () => Navigator.pop(dialogContext), label: t.common.cancel), DialogActionButton( onPressed: () async { - await _selectDownloadLocation(); - if (dialogContext.mounted) Navigator.pop(dialogContext); + final changed = await _selectDownloadLocation(); + if (changed && dialogContext.mounted) Navigator.pop(dialogContext); }, label: t.settings.selectFolder, isPrimary: true, @@ -601,55 +613,65 @@ class _SettingsScreenState extends State with FocusableTab, Moun ); } - Future _selectDownloadLocation() async { + Future _selectDownloadLocation() async { try { String? selectedPath; String pathType = 'file'; if (Platform.isAndroid) { - final safService = SafStorageService.instance; - selectedPath = await safService.pickDirectory(); - if (selectedPath != null) { - pathType = 'saf'; - } else if (PlatformDetector.isTV()) { - if (mounted) { - showErrorSnackBar(context, t.settings.downloadLocationSelectError); - } - return; + final safStorage = SafStorageService.instance; + if (!safStorage.supportsDirectoryPicker) { + showErrorSnackBar(context, t.settings.downloadLocationPickerUnavailable); + return false; } + selectedPath = await safStorage.pickDirectory(); + if (!mounted) return false; + if (selectedPath != null) pathType = 'saf'; } else { - final result = await FilePickerService.instance.getDirectoryPath(dialogTitle: t.settings.selectFolder); - selectedPath = result; + selectedPath = await FilePickerService.instance.getDirectoryPath(dialogTitle: t.settings.selectFolder); + if (!mounted) return false; } + if (selectedPath == null) return false; - if (selectedPath != null) { - if (pathType == 'file') { - final dir = Directory(selectedPath); - final isWritable = - await (widget.downloadDirectoryWritableChecker ?? DownloadStorageService.instance.isDirectoryWritable)( - dir, - ); - if (!isWritable) { - if (mounted) { - showErrorSnackBar(context, t.settings.downloadLocationInvalid); - } - return; - } - } - - if (!mounted) return; - await context.read().setDownloadLocation(path: selectedPath, pathType: pathType); - - if (mounted) { - // ignore: no-empty-block - setState triggers rebuild to reflect new download path - setState(() {}); - showSuccessSnackBar(context, t.settings.downloadLocationChanged); + if (pathType == 'file') { + final dir = Directory(selectedPath); + final isWritable = + await (widget.downloadDirectoryWritableChecker ?? DownloadStorageService.instance.isDirectoryWritable)(dir); + if (!mounted) return false; + if (!isWritable) { + showErrorSnackBar(context, t.settings.downloadLocationInvalid); + return false; } } - } catch (e) { - if (mounted) { - showErrorSnackBar(context, t.settings.downloadLocationSelectError); + + await context.read().setDownloadLocation(path: selectedPath, pathType: pathType); + if (!mounted) return false; + + // ignore: no-empty-block - setState triggers rebuild to reflect new download path + setState(() {}); + showSuccessSnackBar(context, t.settings.downloadLocationChanged); + return true; + } on DownloadStorageException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Download directory selection failed', error: error, stackTrace: stackTrace); + return false; } + showSettingsFailure(context, operation: 'Download directory selection', error: error, stackTrace: stackTrace); + return false; + } on PlatformException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Download directory selection failed', error: error, stackTrace: stackTrace); + return false; + } + showSettingsFailure(context, operation: 'Download directory selection', error: error, stackTrace: stackTrace); + return false; + } on FileSystemException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Download directory selection failed', error: error, stackTrace: stackTrace); + return false; + } + showSettingsFailure(context, operation: 'Download directory selection', error: error, stackTrace: stackTrace); + return false; } } @@ -699,14 +721,27 @@ class _SettingsScreenState extends State with FocusableTab, Moun Future _handleExportSettings() async { try { - final path = await SettingsExportService.exportToFile(); - if (!mounted) return; - if (path == null) return; // user cancelled + final path = await (widget.settingsExporter ?? SettingsExportService.exportToFile)(); + if (!mounted || path == null) return; showSuccessSnackBar(context, t.settings.exportSettingsSuccess); - } on SettingsExportException { - if (mounted) showErrorSnackBar(context, t.settings.exportSettingsFailed); - } catch (_) { - if (mounted) showErrorSnackBar(context, t.settings.exportSettingsFailed); + } on SettingsExportException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Settings export failed', error: error, stackTrace: stackTrace); + return; + } + showSettingsFailure(context, operation: 'Settings export', error: error, stackTrace: stackTrace); + } on PlatformException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Settings export failed', error: error, stackTrace: stackTrace); + return; + } + showSettingsFailure(context, operation: 'Settings export', error: error, stackTrace: stackTrace); + } on FileSystemException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Settings export failed', error: error, stackTrace: stackTrace); + return; + } + showSettingsFailure(context, operation: 'Settings export', error: error, stackTrace: stackTrace); } } @@ -717,22 +752,20 @@ class _SettingsScreenState extends State with FocusableTab, Moun message: t.settings.importSettingsConfirm, confirmText: t.settings.importSettings, ); - if (!confirmed) return; + if (!mounted || !confirmed) return; await _handleImportSettings(); } Future _handleImportSettings() async { - // Capture providers before any awaits so we don't reach through `context` - // after the widget may have been unmounted. - final themeProvider = context.read(); - final hiddenLibrariesProvider = context.read(); - final librariesProvider = context.read(); - try { - final result = await SettingsExportService.importFromFile(); + final result = await (widget.settingsImporter ?? SettingsExportService.importFromFile)(); if (!mounted) return; if (result == null) return; // user cancelled file picker + final themeProvider = context.read(); + final hiddenLibrariesProvider = context.read(); + final librariesProvider = context.read(); + // Import wrote directly to SharedPreferences, bypassing `write`. Push // fresh values into active listenables before providers re-read settings. _settingsService.refreshListenables(); @@ -750,10 +783,24 @@ class _SettingsScreenState extends State with FocusableTab, Moun if (mounted) showErrorSnackBar(context, t.settings.importSettingsNoUser); } on InvalidExportFileException { if (mounted) showErrorSnackBar(context, t.settings.importSettingsInvalidFile); - } on SettingsExportException { - if (mounted) showErrorSnackBar(context, t.settings.importSettingsFailed); - } catch (_) { - if (mounted) showErrorSnackBar(context, t.settings.importSettingsFailed); + } on SettingsExportException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Settings import failed', error: error, stackTrace: stackTrace); + return; + } + showSettingsFailure(context, operation: 'Settings import', error: error, stackTrace: stackTrace); + } on PlatformException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Settings import failed', error: error, stackTrace: stackTrace); + return; + } + showSettingsFailure(context, operation: 'Settings import', error: error, stackTrace: stackTrace); + } on FileSystemException catch (error, stackTrace) { + if (!mounted) { + appLogger.e('Settings import failed', error: error, stackTrace: stackTrace); + return; + } + showSettingsFailure(context, operation: 'Settings import', error: error, stackTrace: stackTrace); } } diff --git a/lib/screens/settings/settings_utils.dart b/lib/screens/settings/settings_utils.dart index 1ea1147b..8443923d 100644 --- a/lib/screens/settings/settings_utils.dart +++ b/lib/screens/settings/settings_utils.dart @@ -1,5 +1,8 @@ +import 'dart:io'; + import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:material_symbols_icons/symbols.dart'; import '../../focus/focusable_text_field.dart'; @@ -7,6 +10,8 @@ import '../../focus/input_mode_tracker.dart'; import '../../i18n/strings.g.dart'; import '../../services/settings_service.dart' as settings; import '../../utils/dialogs.dart'; +import '../../utils/app_logger.dart'; +import '../../utils/snackbar_helper.dart'; import '../../widgets/app_icon.dart'; import '../../widgets/dialog_action_button.dart'; import '../../widgets/focusable_list_tile.dart'; @@ -39,6 +44,18 @@ typedef _SettingsDialogContentBuilder = typedef _SettingsDialogActionsBuilder = List Function(BuildContext dialogContext, StateSetter setDialogState); +/// Reports a recoverable settings persistence failure without swallowing +/// programming errors or other unexpected exception types. +void showSettingsFailure( + BuildContext context, { + required String operation, + required Object error, + required StackTrace stackTrace, +}) { + appLogger.e('$operation failed', error: error, stackTrace: stackTrace); + if (context.mounted) showErrorSnackBar(context, t.settings.saveFailed); +} + void _showSettingsInputDialog({ required BuildContext context, required String title, @@ -89,8 +106,16 @@ class _SettingsInputDialogState extends State<_SettingsInputDialog> { } Future _save() async { - final shouldClose = await widget.onSave(context); - if (shouldClose && mounted) Navigator.pop(context); + try { + final shouldClose = await widget.onSave(context); + if (shouldClose && mounted) Navigator.pop(context); + } on PlatformException catch (error, stackTrace) { + if (!mounted) return; + showSettingsFailure(context, operation: 'Settings input save', error: error, stackTrace: stackTrace); + } on FileSystemException catch (error, stackTrace) { + if (!mounted) return; + showSettingsFailure(context, operation: 'Settings input save', error: error, stackTrace: stackTrace); + } } @override @@ -347,7 +372,16 @@ Future _showColorInputDialogStandard({ }, actionButtons: const ColorPickerActionButtons(okButton: true, closeButton: true, dialogActionButtons: false), ); - if (selected != initial) await onSave(colorToHex(selected)); + if (selected == initial || !context.mounted) return; + try { + await onSave(colorToHex(selected)); + } on PlatformException catch (error, stackTrace) { + if (!context.mounted) return; + showSettingsFailure(context, operation: 'Color setting save', error: error, stackTrace: stackTrace); + } on FileSystemException catch (error, stackTrace) { + if (!context.mounted) return; + showSettingsFailure(context, operation: 'Color setting save', error: error, stackTrace: stackTrace); + } } void _showColorInputDialogTV({ diff --git a/lib/services/keyboard_shortcuts_service.dart b/lib/services/keyboard_shortcuts_service.dart index c949ad1b..84585e77 100644 --- a/lib/services/keyboard_shortcuts_service.dart +++ b/lib/services/keyboard_shortcuts_service.dart @@ -16,7 +16,8 @@ class KeyboardShortcutsService extends ChangeNotifier { static KeyboardShortcutsService? _instance; late final SettingsBindingOwner _settingsBinding; - Map _hotkeys = {}; + Map _hotkeys = {}; + Future _shortcutMutationTail = Future.value(); int _seekTimeSmall = 10; // Default, loaded from settings int _seekTimeLarge = 30; // Default, loaded from settings bool _settingsInitialized = false; @@ -55,7 +56,7 @@ class KeyboardShortcutsService extends ChangeNotifier { final changed = !_hotkeyMapsEqual(_hotkeys, hotkeys) || _seekTimeSmall != seekTimeSmall || _seekTimeLarge != seekTimeLarge; - _hotkeys = Map.from(hotkeys); + _hotkeys = Map.from(hotkeys); _seekTimeSmall = seekTimeSmall; _seekTimeLarge = seekTimeLarge; @@ -64,32 +65,49 @@ class KeyboardShortcutsService extends ChangeNotifier { if (notify && changed) notifyListeners(); } - bool _hotkeyMapsEqual(Map a, Map b) { + bool _hotkeyMapsEqual(Map a, Map b) { if (a.length != b.length) return false; for (final entry in a.entries) { + if (!b.containsKey(entry.key)) return false; + final value = entry.value; final other = b[entry.key]; - if (other == null || !_hotkeyEquals(entry.value, other)) return false; + if (value == null || other == null) { + if (value != other) return false; + } else if (!_hotkeyEquals(value, other)) { + return false; + } } return true; } - Map get hotkeys => Map.from(_hotkeys); + Map get hotkeys => Map.from(_hotkeys); HotKey? getHotkey(String action) { return _hotkeys[action]; } - Future setHotkey(String action, HotKey hotkey) async { - await _settingsService.write(SettingsService.keyboardHotkeys, {..._hotkeys, action: hotkey}); + Future setHotkey(String action, HotKey? hotkey) { + return _serializeShortcutMutation(() async { + await _settingsService.write(SettingsService.keyboardHotkeys, {..._hotkeys, action: hotkey}); + }); } Future refreshFromStorage() async { _settingsBinding.refresh(); } - Future resetToDefaults() async { - final hotkeys = SettingsService.defaultKeyboardHotkeys(); - await _settingsService.write(SettingsService.keyboardHotkeys, hotkeys); + Future resetToDefaults() { + return _serializeShortcutMutation(() async { + await _settingsService.write(SettingsService.keyboardHotkeys, { + ...SettingsService.defaultKeyboardHotkeys(), + }); + }); + } + + Future _serializeShortcutMutation(Future Function() operation) { + final result = _shortcutMutationTail.then((_) => operation()); + _shortcutMutationTail = result.then((_) {}, onError: (Object _, StackTrace _) {}); + return result; } @override @@ -175,6 +193,7 @@ class KeyboardShortcutsService extends ChangeNotifier { for (final entry in _hotkeys.entries) { final action = entry.key; final hotkey = entry.value; + if (hotkey == null) continue; if (physicalKey != hotkey.key) continue; @@ -470,7 +489,8 @@ class KeyboardShortcutsService extends ChangeNotifier { // Check if a hotkey is already assigned to another action String? getActionForHotkey(HotKey hotkey) { for (final entry in _hotkeys.entries) { - if (_hotkeyEquals(entry.value, hotkey)) { + final assignedHotkey = entry.value; + if (assignedHotkey != null && _hotkeyEquals(assignedHotkey, hotkey)) { return entry.key; } } diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index 911b384d..1f370978 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -296,6 +296,15 @@ class PlexClient @override ApiCache get cache => _cache; + /// Snapshot the profile identity used by a cache-first request before its + /// cache lookup can yield. [PlexConfig] is immutable, and [headers] returns a + /// fresh map, so both the token/client headers and cache namespace stay bound + /// to the same profile even if [applyProfileUpdate] runs on a cache miss. + ({ServerId cacheScope, Map headers}) _captureCacheFirstRequestContext() { + final requestConfig = config; + return (cacheScope: profileScopeId.cacheServerId, headers: Map.unmodifiable(requestConfig.headers)); + } + /// Whether to operate in offline mode (use cache only) bool _offlineMode = false; @@ -886,9 +895,9 @@ class PlexClient Future isHealthy() async => (await checkHealth()) == HealthStatus.online; /// Get running background tasks (thumbnail generation, credit detection, etc.) - Future> getActivities() async { + Future> getActivities({AbortController? abort}) async { try { - final response = await _getWithFailover('/activities'); + final response = await _getWithFailover('/activities', abort: abort); final container = _getMediaContainer(response); if (container == null) return []; final activityList = container['Activity'] as List?; @@ -904,6 +913,10 @@ class PlexClient } } return activities; + } on MediaServerHttpException catch (e) { + if (e.isCancellation) rethrow; + appLogger.e('Failed to get activities', error: e); + return []; } catch (e) { appLogger.e('Failed to get activities', error: e); return []; @@ -1557,14 +1570,29 @@ class PlexClient bool forceRefresh = false, }) async { try { - final fetch = forceRefresh ? fetchWithCacheFallback : fetchWithCacheFirst; - final data = await fetch>( - cacheKey: '/library/metadata/$ratingKey', - networkCall: () => - _http.get('/library/metadata/$ratingKey', queryParameters: {'includeChapters': 1, 'includeMarkers': 1}), - parseCache: (cached) => cached as Map?, - parseResponse: (response) => response.data as Map?, + final requestContext = _captureCacheFirstRequestContext(); + final cacheKey = '/library/metadata/$ratingKey'; + Future networkCall() => _http.get( + cacheKey, + queryParameters: {'includeChapters': 1, 'includeMarkers': 1}, + headers: requestContext.headers, ); + Map? parseCache(dynamic cached) => cached as Map?; + Map? parseResponse(MediaServerResponse response) => response.data as Map?; + final data = forceRefresh + ? await fetchWithCacheFallback>( + cacheKey: cacheKey, + networkCall: networkCall, + parseCache: parseCache, + parseResponse: parseResponse, + ) + : await fetchWithCacheFirst>( + cacheScope: requestContext.cacheScope, + cacheKey: cacheKey, + networkCall: networkCall, + parseCache: parseCache, + parseResponse: parseResponse, + ); final metadataJson = _getFirstMetadataJsonFromData(data); return _parsePlaybackExtrasFromMetadataJson( metadataJson, @@ -1696,11 +1724,14 @@ class PlexClient /// for the cache-only readers ([fetchPlaybackExtrasFromCacheOnly], /// [fetchCachedMediaSourceInfo]). Future?> _fetchRawMetadataJsonCacheFirst(String ratingKey) async { + final requestContext = _captureCacheFirstRequestContext(); final data = await fetchWithCacheFirst>( + cacheScope: requestContext.cacheScope, cacheKey: '/library/metadata/$ratingKey', networkCall: () => _http.get( '/library/metadata/$ratingKey', queryParameters: {'includeChapters': 1, 'includeMarkers': 1, 'checkFiles': 1, 'includeStreams': 1}, + headers: requestContext.headers, ), parseCache: (cached) => cached as Map?, parseResponse: (response) => response.data as Map?, @@ -3833,10 +3864,18 @@ class PlexClient } @override - Future resolveDownload(MediaItem item, {int mediaIndex = 0}) async { - final playbackData = await getVideoPlaybackData(item.id, mediaIndex: mediaIndex); + Future resolveDownload(MediaItem item, {int mediaIndex = 0, String? mediaSourceId}) async { + final playbackData = await getVideoPlaybackData( + item.id, + mediaIndex: mediaIndex, + selectedMediaSourceId: mediaSourceId, + ); final subtitles = []; final mediaInfo = playbackData.mediaInfo; + final requestedSourceId = mediaSourceId?.trim(); + if (requestedSourceId != null && requestedSourceId.isNotEmpty && mediaInfo?.mediaSourceId != requestedSourceId) { + throw StateError('Requested Plex download source is no longer available'); + } if (mediaInfo != null) { for (final subtitle in mediaInfo.subtitleTracks) { if (!subtitle.isExternal || subtitle.key == null) continue; diff --git a/lib/services/settings_export_service.dart b/lib/services/settings_export_service.dart index 3fff2bb5..13f3987b 100644 --- a/lib/services/settings_export_service.dart +++ b/lib/services/settings_export_service.dart @@ -4,17 +4,20 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../models/shader_preset.dart'; import '../utils/app_logger.dart'; import '../utils/formatters.dart'; import '../utils/platform_detector.dart'; import 'file_picker_service.dart'; import 'settings_service.dart'; import 'storage_service.dart'; +import 'trackers/tracker_constants.dart'; class ImportResult { final int keysImported; @@ -64,8 +67,7 @@ class _StoredPreferenceValue { /// Serializes / restores user-facing SharedPreferences to a JSON file. /// -/// Strategy is allow-by-default: every key is exported unless it matches an -/// exact denylist or a prefix denylist of auth/cache/internal keys. User-scoped +/// Only preferences in the closed portable registry are exported. User-scoped /// keys (prefixed with `user_{uuid}_`) have that prefix stripped on export and /// re-applied with the current user's prefix on import, so preferences follow /// whichever account is signed in on the target device. @@ -193,12 +195,20 @@ class SettingsExportService { SettingsService.keyboardHotkeys, ]) pref.key: _PreferencePolicy(_storageTypeFor(pref)), + for (final service in TrackerService.values) + for (final pref in >[ + SettingsService.trackerFilterModePref(service), + SettingsService.trackerFilterIdsPref(service), + ]) + pref.key: _PreferencePolicy(_storageTypeFor(pref)), }; + static const Set _jsonStringListPreferenceKeys = {'hidden_libraries', 'library_order'}; + static const Map _userScopedPreferences = { - 'hidden_libraries': _PreferencePolicy(_typeStringList, userScoped: true), + 'hidden_libraries': _PreferencePolicy(_typeString, userScoped: true), 'library_filters': _PreferencePolicy(_typeString, userScoped: true), - 'library_order': _PreferencePolicy(_typeStringList, userScoped: true), + 'library_order': _PreferencePolicy(_typeString, userScoped: true), }; static final List<(RegExp, _PreferencePolicy)> _dynamicUserScopedPreferences = [ @@ -269,7 +279,7 @@ class SettingsExportService { final policy = _policyFor(baseKey); if (policy == null || policy.userScoped != sourceIsUserScoped) continue; - final entry = _encodeValue(prefs.get(fullKey), policy.type); + final entry = _encodeValue(_portableExportValue(baseKey, prefs.get(fullKey)), policy.type); if (entry != null) prefsOut[baseKey] = entry; } @@ -282,6 +292,11 @@ class SettingsExportService { }; } + static Object? _portableExportValue(String baseKey, Object? value) { + if (baseKey != SettingsService.globalShaderPreset.key) return value; + return value is String && ShaderPreset.fromId(value) != null ? value : ShaderPreset.none.id; + } + static Map? _encodeValue(Object? value, String expectedType) { return switch (expectedType) { _typeBool when value is bool => {'type': _typeBool, 'value': value}, @@ -331,12 +346,26 @@ class SettingsExportService { continue; } - final type = rawEntry['type']; - final value = rawEntry['value']; + var type = rawEntry['type']; + var value = rawEntry['value']; + // Early format-v1 exports described these JSON-backed values as native + // string lists. Normalize that narrowly admitted legacy shape to the + // String representation consumed by StorageService. + if (version == 1 && + _jsonStringListPreferenceKeys.contains(baseKey) && + type == _typeStringList && + _isValidValue(_typeStringList, value)) { + type = _typeString; + value = jsonEncode((value as List).cast()); + } if (type is! String || type != policy.type || !_isValidValue(type, value)) { skipped++; continue; } + if (baseKey == SettingsService.globalShaderPreset.key && value is String && ShaderPreset.fromId(value) == null) { + skipped++; + continue; + } pending.add( _PendingImport(targetKey: policy.userScoped ? '$userPrefix$baseKey' : baseKey, type: type, value: value), @@ -362,8 +391,7 @@ class SettingsExportService { } catch (rollbackError, rollbackStackTrace) { appLogger.e('Settings import rollback failed', error: rollbackError, stackTrace: rollbackStackTrace); } - appLogger.e('Settings import failed', error: error, stackTrace: stackTrace); - throw const SettingsExportException('Could not apply settings import'); + Error.throwWithStackTrace(error, stackTrace); } return ImportResult(keysImported: pending.length, keysSkipped: skipped); @@ -436,7 +464,7 @@ class SettingsExportService { /// the user's choosing. Returns the saved path, or `null` if the user /// cancelled the picker. /// - /// Throws [SettingsExportException] on failure. + /// Platform and filesystem failures retain their original exception types. static Future exportToFile() async { final prefs = (await SettingsService.getInstance()).prefs; final storage = await StorageService.getInstance(); @@ -444,8 +472,8 @@ class SettingsExportService { try { final info = await PackageInfo.fromPlatform(); appVersion = info.version; - } catch (_) { - // best-effort; tolerate platforms without PackageInfo + } on PlatformException { + // Best-effort metadata; platforms without PackageInfo still export. } final exportMap = buildExportMap(prefs, currentUserUuid: storage.activeUserScope(), appVersion: appVersion); @@ -459,18 +487,13 @@ class SettingsExportService { return _writeToAppDocuments(fileName, bytes); } - try { - return await FilePickerService.instance.saveFile( - dialogTitle: 'Export Plezy settings', - fileName: fileName, - bytes: bytes, - type: FileType.custom, - allowedExtensions: const [fileExtension], - ); - } catch (e, st) { - appLogger.e('Settings export failed', error: e, stackTrace: st); - throw const SettingsExportException('Could not write export file'); - } + return FilePickerService.instance.saveFile( + dialogTitle: 'Export Plezy settings', + fileName: fileName, + bytes: bytes, + type: FileType.custom, + allowedExtensions: const [fileExtension], + ); } static Future _writeToAppDocuments(String fileName, Uint8List bytes) async { @@ -483,8 +506,9 @@ class SettingsExportService { /// Prompts the user to pick a settings JSON and writes its contents into /// SharedPreferences. Requires a signed-in user. /// - /// Returns `null` if the user cancelled. Throws [SettingsExportException] on - /// malformed files or unsupported versions. + /// Returns `null` if the user cancelled. Malformed files throw + /// [InvalidExportFileException]; platform and filesystem failures retain + /// their original exception types. static Future importFromFile() async { final storage = await StorageService.getInstance(); final uuid = storage.activeUserScope(); @@ -501,30 +525,29 @@ class SettingsExportService { final file = picked.files.first; String contents; - try { - final bytes = file.bytes; - if (bytes != null) { + final bytes = file.bytes; + if (bytes != null) { + try { contents = utf8.decode(bytes); - } else if (file.path != null) { - contents = await File(file.path!).readAsString(); - } else { + } on FormatException { throw const InvalidExportFileException('Could not read the selected file'); } - } catch (e, st) { - appLogger.e('Settings import read failed', error: e, stackTrace: st); + } else if (file.path != null) { + contents = await File(file.path!).readAsString(); + } else { throw const InvalidExportFileException('Could not read the selected file'); } - Map data; + final Object? decoded; try { - final decoded = json.decode(contents); - if (decoded is! Map) { - throw const InvalidExportFileException('Invalid export file'); - } - data = decoded; - } catch (_) { + decoded = json.decode(contents); + } on FormatException { throw const InvalidExportFileException('Invalid export file'); } + if (decoded is! Map) { + throw const InvalidExportFileException('Invalid export file'); + } + final data = decoded; final prefs = (await SettingsService.getInstance()).prefs; return applyImportMap(data, prefs, currentUserUuid: uuid); diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 24d40a56..0ec70026 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -304,13 +304,19 @@ Map _defaultKeyboardHotkeys() => { 'screenshot': const HotKey(key: PhysicalKeyboardKey.keyS, modifiers: [HotKeyModifier.control]), }; -Map _decodeKeyboardHotkeys(dynamic raw) { - final result = {}; +Map _decodeKeyboardHotkeys(dynamic raw) { + final result = {}; for (final entry in (raw as Map).entries) { - final hk = SettingsService.deserializeHotKey(entry.value as Map); - if (hk != null) result[entry.key] = hk; + final value = entry.value; + if (value is! Map) continue; + if (value['disabled'] == true) { + result[entry.key] = null; + continue; + } + final hotkey = SettingsService.deserializeHotKey(value); + if (hotkey != null) result[entry.key] = hotkey; } - return {..._defaultKeyboardHotkeys(), ...result}; + return {..._defaultKeyboardHotkeys(), ...result}; } class SettingsService extends BaseSharedPreferencesService { @@ -500,10 +506,12 @@ class SettingsService extends BaseSharedPreferencesService { ); static const mpvConfigText = _MpvConfigTextPref(); - static final keyboardHotkeys = JsonPref>( + static final keyboardHotkeys = JsonPref>( 'keyboard_hotkeys', - defaultValue: _defaultKeyboardHotkeys(), - encode: (v) => json.encode(v.map((k, hk) => MapEntry(k, SettingsService.serializeHotKey(hk)))), + defaultValue: {..._defaultKeyboardHotkeys()}, + encode: (values) => json.encode( + values.map((key, hotkey) => MapEntry(key, hotkey == null ? const {'disabled': true} : serializeHotKey(hotkey))), + ), decode: _decodeKeyboardHotkeys, ); static final mediaVersionPreferences = JsonPref>( @@ -672,13 +680,6 @@ class SettingsService extends BaseSharedPreferencesService { await write(mpvPresets, presets); } - /// Load a preset (replaces current config text). - Future loadMpvPreset(String name) async { - final presets = read(mpvPresets); - final preset = presets.firstWhere((p) => p.name == name, orElse: () => throw Exception('Preset not found: $name')); - await write(mpvConfigText, preset.text); - } - static const _modifierMap = { 'alt': HotKeyModifier.alt, 'control': HotKeyModifier.control, diff --git a/lib/services/shader_asset_loader.dart b/lib/services/shader_asset_loader.dart index b4747dbb..56a27252 100644 --- a/lib/services/shader_asset_loader.dart +++ b/lib/services/shader_asset_loader.dart @@ -1,3 +1,5 @@ +import 'dart:async' show unawaited; + import 'dart:io'; import 'package:flutter/services.dart'; @@ -15,6 +17,10 @@ import '../utils/app_logger.dart'; class ShaderAssetLoader { static const String _shaderAssetBase = 'assets/shaders'; static String? _cachedShaderDir; + static final RegExp _customShaderFileNamePattern = RegExp(r'^[A-Za-z0-9-]+\.glsl$', caseSensitive: false); + static final Map _verifiedBuiltInShaderPaths = {}; + static final Map> _inFlightBuiltInShaders = {}; + static int _cacheGeneration = 0; /// NVScaler shader file static const String _nvscalerShader = 'nvscaler/NVScaler.glsl'; @@ -42,13 +48,12 @@ class ShaderAssetLoader { 'downscale_post': 'anime4k/Anime4K_AutoDownscalePre_x4.glsl', }; - /// Get the shader cache directory path, creating it if necessary. + /// Get the application-owned shader cache directory, creating it if needed. static Future _getShaderDirectory() async { if (_cachedShaderDir != null) return _cachedShaderDir!; - final cacheDir = await getTemporaryDirectory(); + final cacheDir = await getApplicationCacheDirectory(); final shaderDir = Directory(path.join(cacheDir.path, 'shaders')); - if (!await shaderDir.exists()) { await shaderDir.create(recursive: true); } @@ -59,30 +64,102 @@ class ShaderAssetLoader { /// Extract a single shader file from assets to the cache directory. /// Returns the absolute file path of the extracted shader. - static Future _extractShader(String assetPath) async { - try { - final shaderDir = await _getShaderDirectory(); - final fileName = path.basename(assetPath); - final subDir = path.dirname(assetPath); + static Future _extractShader(String assetPath) { + final generation = _cacheGeneration; + final operationKey = '$generation:$assetPath'; + final active = _inFlightBuiltInShaders[operationKey]; + if (active != null) return active; - final targetDir = Directory(path.join(shaderDir, subDir)); + final operation = _materializeBuiltInShader(assetPath, generation); + _inFlightBuiltInShaders[operationKey] = operation; + unawaited( + operation.whenComplete(() { + if (identical(_inFlightBuiltInShaders[operationKey], operation)) { + _inFlightBuiltInShaders.remove(operationKey); + } + }), + ); + return operation; + } + + static Future _materializeBuiltInShader(String assetPath, int generation) async { + File? pendingFile; + try { + final verifiedPath = _verifiedBuiltInShaderPaths[assetPath]; + if (verifiedPath != null) { + if (await File(verifiedPath).exists()) return verifiedPath; + _verifiedBuiltInShaderPaths.remove(assetPath); + } + + final shaderDir = await _getShaderDirectory(); + final targetDir = Directory(path.join(shaderDir, path.dirname(assetPath))); if (!await targetDir.exists()) { await targetDir.create(recursive: true); } + final targetFile = File(path.join(targetDir.path, path.basename(assetPath))); + final data = await rootBundle.load('$_shaderAssetBase/$assetPath'); + final bundledBytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); - final targetFile = File(path.join(targetDir.path, fileName)); - - // Only extract if not already cached - if (!await targetFile.exists()) { - final fullAssetPath = '$_shaderAssetBase/$assetPath'; - final data = await rootBundle.load(fullAssetPath); - await targetFile.writeAsBytes(data.buffer.asUint8List()); + if (await _fileMatches(targetFile, bundledBytes)) { + if (generation == _cacheGeneration) { + _verifiedBuiltInShaderPaths[assetPath] = targetFile.path; + } + return targetFile.path; } + pendingFile = File('${targetFile.path}.pending.${const Uuid().v4()}'); + await pendingFile.writeAsBytes(bundledBytes, flush: true); + if (!await _promotePendingShader(pendingFile, targetFile, bundledBytes)) { + return null; + } + if (!await _fileMatches(targetFile, bundledBytes)) return null; + + if (generation == _cacheGeneration) { + _verifiedBuiltInShaderPaths[assetPath] = targetFile.path; + } return targetFile.path; } catch (e, st) { appLogger.w('Failed to extract shader $assetPath', error: e, stackTrace: st); return null; + } finally { + if (pendingFile != null) { + try { + if (await pendingFile.exists()) await pendingFile.delete(); + } on FileSystemException { + // The pending path is never returned and can be reclaimed with cache. + } + } + } + } + + static Future _promotePendingShader(File pendingFile, File targetFile, List bundledBytes) async { + try { + await pendingFile.rename(targetFile.path); + return true; + } on FileSystemException { + if (await _fileMatches(targetFile, bundledBytes)) return true; + } + + try { + if (await targetFile.exists()) await targetFile.delete(); + await pendingFile.rename(targetFile.path); + return true; + } on FileSystemException { + return _fileMatches(targetFile, bundledBytes); + } + } + + static Future _fileMatches(File file, List expected) async { + try { + if (!await file.exists()) return false; + final actual = await file.readAsBytes(); + if (actual.length != expected.length) return false; + for (var index = 0; index < expected.length; index++) { + if (actual[index] != expected[index]) return false; + } + return true; + } on FileSystemException { + return false; } } @@ -194,6 +271,14 @@ class ShaderAssetLoader { return shaders; } + /// Whether [fileName] is a direct managed GLSL shader basename. + /// + /// UUID names generated by current builds and alphanumeric names generated + /// by older builds are both accepted. + static bool isValidCustomShaderFileName(String fileName) { + return _customShaderFileNamePattern.hasMatch(fileName); + } + /// Get the custom shader directory path, creating it if necessary. /// Uses app support directory (persistent) rather than temp/cache. static Future _getCustomShaderDirectory() async { @@ -210,29 +295,31 @@ class ShaderAssetLoader { /// Import a custom shader file into the custom shaders directory. /// Returns the stored file name (UUID-based to avoid collisions). static Future importCustomShader(String sourcePath) async { - final customDir = await _getCustomShaderDirectory(); - final ext = path.extension(sourcePath); - final uuid = const Uuid().v4(); - final storedName = '$uuid$ext'; - final targetFile = File(path.join(customDir, storedName)); + if (path.extension(sourcePath).toLowerCase() != '.glsl') { + throw ArgumentError.value(sourcePath, 'sourcePath', 'Custom shaders must use the .glsl extension'); + } - await File(sourcePath).copy(targetFile.path); + final customDir = await _getCustomShaderDirectory(); + final storedName = '${const Uuid().v4()}.glsl'; + await File(sourcePath).copy(path.join(customDir, storedName)); return storedName; } /// Delete a custom shader file from the custom shaders directory. static Future deleteCustomShader(String fileName) async { - final customDir = await _getCustomShaderDirectory(); - final file = File(path.join(customDir, fileName)); - if (await file.exists()) { + final file = await _resolveManagedCustomShaderFile(fileName); + if (file != null && await file.exists()) { await file.delete(); } } - /// Get the absolute path for a custom shader file. - static Future getCustomShaderPath(String fileName) async { - final customDir = await _getCustomShaderDirectory(); - return path.join(customDir, fileName); + static Future _resolveManagedCustomShaderFile(String fileName) async { + if (!isValidCustomShaderFileName(fileName)) return null; + + final customDir = path.canonicalize(await _getCustomShaderDirectory()); + final candidate = path.canonicalize(path.join(customDir, fileName)); + if (!path.equals(path.dirname(candidate), customDir)) return null; + return File(candidate); } /// Get shader paths for a given preset. @@ -250,16 +337,19 @@ class ShaderAssetLoader { if (preset.anime4kConfig == null) return []; return getAnime4KShaders(preset.anime4kConfig!); case ShaderPresetType.custom: - if (preset.fileName == null) return []; - final shaderPath = await getCustomShaderPath(preset.fileName!); - if (!await File(shaderPath).exists()) return []; - return [shaderPath]; + final fileName = preset.fileName; + if (fileName == null) return []; + final shaderFile = await _resolveManagedCustomShaderFile(fileName); + if (shaderFile == null || !await shaderFile.exists()) return []; + return [shaderFile.path]; } } /// Clear cached shader directory reference. /// Call when clearing app cache. static void clearCache() { + _cacheGeneration++; _cachedShaderDir = null; + _verifiedBuiltInShaderPaths.clear(); } } diff --git a/lib/utils/initials_palette.dart b/lib/utils/initials_palette.dart index 6f526eb4..6e82b463 100644 --- a/lib/utils/initials_palette.dart +++ b/lib/utils/initials_palette.dart @@ -4,8 +4,7 @@ import 'package:flutter/material.dart'; String initialOf(String name) { final trimmed = name.trim(); if (trimmed.isEmpty) return '?'; - final first = trimmed.runes.first; - return String.fromCharCode(first).toUpperCase(); + return trimmed.characters.first.toUpperCase(); } /// Deterministic colour for [name] from a curated palette. The palette is diff --git a/lib/utils/search_relevance.dart b/lib/utils/search_relevance.dart index f67f3011..78d443bb 100644 --- a/lib/utils/search_relevance.dart +++ b/lib/utils/search_relevance.dart @@ -1,85 +1,98 @@ import 'dart:math' as math; +import 'package:collection/collection.dart'; import 'package:string_similarity/string_similarity.dart'; +import 'package:unorm_dart/unorm_dart.dart'; import '../media/media_item.dart'; const int defaultMediaSearchLimit = 100; +final RegExp _searchSeparatorPattern = RegExp(r'[^\p{L}\p{N}\p{M}]+', unicode: true); + List rankMediaSearchResults(List items, String query, {int? limit}) { - final normalizedQuery = normalizeSearchText(query); - if (normalizedQuery.isEmpty) { + if (limit != null) { + RangeError.checkNotNegative(limit, 'limit'); + if (limit == 0) return const []; + } + if (items.isEmpty) return const []; + + final searchQuery = _NormalizedSearchQuery(query); + if (searchQuery.text.isEmpty) { return limit == null ? List.of(items) : items.take(limit).toList(); } - final ranked = <_RankedMediaItem>[ - for (var i = 0; i < items.length; i++) - _RankedMediaItem(item: items[i], score: mediaSearchRelevanceScore(items[i], normalizedQuery), originalIndex: i), - ]; + if (limit == null || limit >= items.length) { + final ranked = <_RankedMediaItem>[ + for (var i = 0; i < items.length; i++) + _RankedMediaItem( + item: items[i], + score: _mediaSearchRelevanceScoreNormalized(items[i], searchQuery), + originalIndex: i, + ), + ]..sort(_compareRankedBestFirst); + return [for (final entry in ranked) entry.item]; + } - ranked.sort((a, b) { - final scoreComparison = b.score.compareTo(a.score); - if (scoreComparison != 0) return scoreComparison; - return a.originalIndex.compareTo(b.originalIndex); - }); + final retained = HeapPriorityQueue<_RankedMediaItem>(_compareRankedWorstFirst); + for (var i = 0; i < items.length; i++) { + final item = items[i]; + final score = _mediaSearchRelevanceScoreNormalized(item, searchQuery); + if (retained.length < limit) { + retained.add(_RankedMediaItem(item: item, score: score, originalIndex: i)); + continue; + } - final result = ranked.map((entry) => entry.item); - return limit == null ? result.toList() : result.take(limit).toList(); + final worst = retained.first; + if (score > worst.score || (score == worst.score && i < worst.originalIndex)) { + retained + ..removeFirst() + ..add(_RankedMediaItem(item: item, score: score, originalIndex: i)); + } + } + + final ranked = retained.toList()..sort(_compareRankedBestFirst); + return [for (final entry in ranked) entry.item]; } -double mediaSearchRelevanceScore(MediaItem item, String query) { - final normalizedQuery = normalizeSearchText(query); - if (normalizedQuery.isEmpty) return 0; - final fields = <({String? value, double weight})>[ - (value: item.title, weight: 1.0), - (value: item.titleSort, weight: 0.98), - (value: item.originalTitle, weight: 0.96), - (value: item.grandparentTitle, weight: 0.9), - (value: item.parentTitle, weight: 0.8), - ]; - - var best = 0.0; - for (final field in fields) { - final candidate = normalizeSearchText(field.value); - if (candidate.isEmpty) continue; - best = math.max(best, _scoreNormalizedField(normalizedQuery, candidate) * field.weight); - } +double _mediaSearchRelevanceScoreNormalized(MediaItem item, _NormalizedSearchQuery query) { + var best = _scoreWeightedField(item.title, query, 1.0); + best = math.max(best, _scoreWeightedField(item.titleSort, query, 0.98)); + best = math.max(best, _scoreWeightedField(item.originalTitle, query, 0.96)); + best = math.max(best, _scoreWeightedField(item.grandparentTitle, query, 0.9)); + best = math.max(best, _scoreWeightedField(item.parentTitle, query, 0.8)); return best; } -String normalizeSearchText(String? value) { - if (value == null) return ''; - return value - .toLowerCase() - .replaceAll(RegExp(r'[\u0000-\u002f\u003a-\u0040\u005b-\u0060\u007b-\u007f]+'), ' ') - .replaceAll(RegExp(r'\s+'), ' ') - .trim(); +double _scoreWeightedField(String? value, _NormalizedSearchQuery query, double weight) { + final candidate = normalizeSearchText(value); + if (candidate.isEmpty) return 0; + return _scoreNormalizedField(query, candidate) * weight; } -double _scoreNormalizedField(String query, String candidate) { - if (candidate == query) return 1000; +/// Produces an accent-sensitive search key where canonical/compatibility +/// equivalents and Unicode typography compare alike. +String normalizeSearchText(String? value) { + if (value == null) return ''; + return nfkc(value).toLowerCase().replaceAll(_searchSeparatorPattern, ' ').trim(); +} - final queryWithoutArticle = _withoutLeadingArticle(query); - final candidateWithoutArticle = _withoutLeadingArticle(candidate); - if (queryWithoutArticle.isNotEmpty && candidateWithoutArticle == queryWithoutArticle) return 980; +double _scoreNormalizedField(_NormalizedSearchQuery query, String candidate) { + if (candidate == query.text) return 1000; - if (candidate.startsWith(query)) return 900 + _lengthCloseness(query, candidate, 50); - if (queryWithoutArticle.isNotEmpty && candidateWithoutArticle.startsWith(queryWithoutArticle)) { - return 880 + _lengthCloseness(queryWithoutArticle, candidateWithoutArticle, 50); - } + if (candidate.startsWith(query.text)) return 900 + _lengthCloseness(query.text, candidate, 50); - if (candidate.contains(query)) return 800 + _lengthCloseness(query, candidate, 50); + if (candidate.contains(query.text)) return 800 + _lengthCloseness(query.text, candidate, 50); - final queryTokens = _tokens(query); + final queryTokens = query.tokens; final candidateTokens = _tokens(candidate); if (queryTokens.isEmpty || candidateTokens.isEmpty) return 0; final candidateTokenSet = candidateTokens.toSet(); final matchingTokens = queryTokens.where(candidateTokenSet.contains).length; - final sortedQuery = _sortedTokens(queryTokens); final sortedCandidate = _sortedTokens(candidateTokens); - final tokenSimilarity = StringSimilarity.compareTwoStrings(sortedQuery, sortedCandidate); - final rawSimilarity = StringSimilarity.compareTwoStrings(query, candidate); + final tokenSimilarity = StringSimilarity.compareTwoStrings(query.sortedTokens, sortedCandidate); + final rawSimilarity = StringSimilarity.compareTwoStrings(query.text, candidate); final fuzzyScore = math.max(rawSimilarity, tokenSimilarity) * 650; if (matchingTokens == queryTokens.length) return math.max(700 + tokenSimilarity * 100, fuzzyScore); @@ -95,13 +108,6 @@ String _sortedTokens(List tokens) { return sorted.join(' '); } -String _withoutLeadingArticle(String value) { - for (final article in const ['the ', 'a ', 'an ']) { - if (value.startsWith(article)) return value.substring(article.length); - } - return value; -} - double _lengthCloseness(String query, String candidate, double maxBonus) { final longest = math.max(query.length, candidate.length); if (longest == 0) return 0; @@ -110,6 +116,26 @@ double _lengthCloseness(String query, String candidate, double maxBonus) { return maxBonus * closeness; } +int _compareRankedBestFirst(_RankedMediaItem a, _RankedMediaItem b) { + final scoreComparison = b.score.compareTo(a.score); + if (scoreComparison != 0) return scoreComparison; + return a.originalIndex.compareTo(b.originalIndex); +} + +int _compareRankedWorstFirst(_RankedMediaItem a, _RankedMediaItem b) { + final scoreComparison = a.score.compareTo(b.score); + if (scoreComparison != 0) return scoreComparison; + return b.originalIndex.compareTo(a.originalIndex); +} + +class _NormalizedSearchQuery { + _NormalizedSearchQuery(String value) : text = normalizeSearchText(value); + + final String text; + late final List tokens = _tokens(text); + late final String sortedTokens = _sortedTokens(tokens); +} + class _RankedMediaItem { const _RankedMediaItem({required this.item, required this.score, required this.originalIndex}); diff --git a/lib/widgets/app_bar_back_button.dart b/lib/widgets/app_bar_back_button.dart index 9c95a732..28c9a4d1 100644 --- a/lib/widgets/app_bar_back_button.dart +++ b/lib/widgets/app_bar_back_button.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:plezy/widgets/app_icon.dart'; +import 'package:flutter/services.dart'; import 'package:material_symbols_icons/symbols.dart'; +import '../focus/focusable_wrapper.dart'; +import '../i18n/strings.g.dart'; +import 'app_icon.dart'; + /// Defines the visual style of the back button enum BackButtonStyle { /// Back button with circular semi-transparent background (used in detail screens) @@ -31,13 +35,15 @@ class AppBarBackButton extends StatefulWidget { /// [style] determines the visual appearance of the back button. /// [onPressed] is called when the button is tapped. If null, defaults to Navigator.pop. /// [color] overrides the default icon color. If null, uses white for circular/video, theme default for plain. - /// [semanticLabel] provides accessibility label for screen readers. + /// [focusNode] allows callers to connect this control to an explicit focus graph. + /// [semanticLabel] overrides the localized back-button label. const AppBarBackButton({ super.key, this.style = BackButtonStyle.circular, this.onPressed, this.color, this.semanticLabel, + this.focusNode, }); final BackButtonStyle style; @@ -49,6 +55,7 @@ class AppBarBackButton extends StatefulWidget { final Color? color; final String? semanticLabel; + final FocusNode? focusNode; @override State createState() => _AppBarBackButtonState(); @@ -90,6 +97,12 @@ class _AppBarBackButtonState extends State with TickerProvider } } + KeyEventResult _handleKeyEvent(FocusNode _, KeyEvent event) { + if (event.logicalKey != LogicalKeyboardKey.space) return KeyEventResult.ignored; + if (event is KeyDownEvent) _handlePressed(); + return KeyEventResult.handled; + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -123,33 +136,44 @@ class _AppBarBackButtonState extends State with TickerProvider break; } - final buttonWidget = MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) => _onHoverChange(true), - onExit: (_) => _onHoverChange(false), - child: GestureDetector( - onTap: _handlePressed, - child: AnimatedBuilder( - animation: _backgroundAnimation, - builder: (context, child) { - final currentColor = Color.lerp(baseColor, hoverColor, _backgroundAnimation.value); + final semanticLabel = widget.semanticLabel ?? t.common.back; + final button = FocusableWrapper( + focusNode: widget.focusNode, + semanticLabel: semanticLabel, + onSelect: _handlePressed, + onKeyEvent: _handleKeyEvent, + autoScroll: false, + disableScale: true, + descendantsAreFocusable: false, + borderRadius: 20, + child: Tooltip( + message: semanticLabel, + excludeFromSemantics: true, + child: MouseRegion( + onEnter: (_) => _onHoverChange(true), + onExit: (_) => _onHoverChange(false), + child: GestureDetector( + excludeFromSemantics: true, + onTap: _handlePressed, + child: AnimatedBuilder( + animation: _backgroundAnimation, + builder: (context, child) { + final currentColor = Color.lerp(baseColor, hoverColor, _backgroundAnimation.value); - return Container( - margin: const EdgeInsets.all(8), - width: 40, - height: 40, - decoration: BoxDecoration(color: currentColor, shape: BoxShape.circle), - child: AppIcon(Symbols.arrow_back_rounded, fill: 1, color: effectiveColor, size: 20), - ); - }, + return Container( + margin: const EdgeInsets.all(8), + width: 40, + height: 40, + decoration: BoxDecoration(color: currentColor, shape: BoxShape.circle), + child: AppIcon(Symbols.arrow_back_rounded, fill: 1, color: effectiveColor, size: 20), + ); + }, + ), + ), ), ), ); - final button = widget.semanticLabel != null - ? Semantics(label: widget.semanticLabel, button: true, excludeSemantics: true, child: buttonWidget) - : buttonWidget; - return widget.style == BackButtonStyle.circular ? SafeArea(child: button) : button; } } diff --git a/lib/widgets/cycling_media_backdrop.dart b/lib/widgets/cycling_media_backdrop.dart index b5ff232a..f77125ce 100644 --- a/lib/widgets/cycling_media_backdrop.dart +++ b/lib/widgets/cycling_media_backdrop.dart @@ -7,6 +7,9 @@ import 'package:flutter/material.dart'; import '../media/media_server_client.dart'; import '../services/device_performance.dart'; import '../utils/media_image_helper.dart'; +import 'optimized_media_image.dart'; + +Future _defaultLocalFileExists(File file) => file.exists(); /// Displays server artwork and rotates through multiple backdrops in order. /// @@ -25,6 +28,7 @@ class CyclingMediaBackdrop extends StatefulWidget { this.localArtworkPathResolver, this.imageProviderResolver, this.allowNetwork = true, + this.localFileExists = _defaultLocalFileExists, this.active = true, this.fit = BoxFit.cover, this.alignment = Alignment.center, @@ -41,6 +45,10 @@ class CyclingMediaBackdrop extends StatefulWidget { /// Overrides provider construction for deterministic widget tests. @visibleForTesting final ImageProvider? Function(String artworkPath)? imageProviderResolver; + + /// Overrides local file checks for deterministic widget tests. + @visibleForTesting + final Future Function(File file) localFileExists; final bool allowNetwork; final bool active; final double width; @@ -220,7 +228,7 @@ class _CyclingMediaBackdropState extends State with Widget _restartRotationTimer(); } - ImageProvider? _providerFor(BuildContext context, String path) { + ImageProvider? _providerFor(BuildContext context, String path, File? localFile) { final providerOverride = widget.imageProviderResolver; if (providerOverride != null) return providerOverride(path); @@ -234,12 +242,8 @@ class _CyclingMediaBackdropState extends State with Widget imageType: ImageType.art, ); - final localPath = widget.localArtworkPathResolver?.call(path); - if (localPath != null) { - final file = File(localPath); - if (file.existsSync()) { - return MediaImageHelper.boundedDecode(FileImage(file), memWidth: memWidth, memHeight: memHeight); - } + if (localFile != null) { + return MediaImageHelper.boundedDecode(FileImage(localFile), memWidth: memWidth, memHeight: memHeight); } if (!widget.allowNetwork) return null; @@ -262,17 +266,22 @@ class _CyclingMediaBackdropState extends State with Widget }); } - @override - Widget build(BuildContext context) { - final path = _currentPath; - final provider = path == null ? null : _providerFor(context, path); - if (path != null && provider == null) _reportMissingProvider(path); + Widget _buildCrossfade( + BuildContext context, + String? path, { + required LocalFileResolution localResolution, + File? localFile, + }) { + final pending = localResolution == LocalFileResolution.pending; + final provider = path == null || pending ? null : _providerFor(context, path, localFile); + if (path != null && provider == null && !pending) _reportMissingProvider(path); final fadeDuration = _disableAnimations ? Duration.zero : DevicePerformance.reducedDuration(widget.fadeDuration); return _BackdropArtworkCrossfade( artworkKey: (widget.mediaKey, path), imageErrorKey: path, image: provider, + pending: pending, duration: fadeDuration, fit: widget.fit, alignment: widget.alignment, @@ -280,6 +289,27 @@ class _CyclingMediaBackdropState extends State with Widget onImageError: _handleImageError, ); } + + @override + Widget build(BuildContext context) { + final path = _currentPath; + if (path == null) { + return _buildCrossfade(context, null, localResolution: LocalFileResolution.missing); + } + + final localPath = widget.localArtworkPathResolver?.call(path); + if (localPath == null) { + return _buildCrossfade(context, path, localResolution: LocalFileResolution.missing); + } + + return ResolvedLocalFile( + path: localPath, + cacheMissing: true, + fileExists: widget.localFileExists, + builder: (context, resolution, file) => + _buildCrossfade(context, path, localResolution: resolution, localFile: file), + ); + } } class _BackdropArtworkCrossfade extends StatefulWidget { @@ -287,6 +317,7 @@ class _BackdropArtworkCrossfade extends StatefulWidget { required this.artworkKey, required this.imageErrorKey, required this.image, + required this.pending, required this.duration, required this.fit, required this.alignment, @@ -297,6 +328,7 @@ class _BackdropArtworkCrossfade extends StatefulWidget { final Object? artworkKey; final Object? imageErrorKey; final ImageProvider? image; + final bool pending; final Duration duration; final BoxFit fit; final Alignment alignment; @@ -310,7 +342,7 @@ class _BackdropArtworkCrossfade extends StatefulWidget { class _BackdropArtworkCrossfadeState extends State<_BackdropArtworkCrossfade> with SingleTickerProviderStateMixin { late final AnimationController _fade; late Object? _currentKey = widget.artworkKey; - late ImageProvider? _base = widget.image; + late ImageProvider? _base = widget.pending ? null : widget.image; late Object? _baseErrorKey = widget.imageErrorKey; ImageProvider? _incoming; Object? _incomingErrorKey; @@ -328,6 +360,11 @@ class _BackdropArtworkCrossfadeState extends State<_BackdropArtworkCrossfade> wi super.didUpdateWidget(oldWidget); _fade.duration = widget.duration; if (widget.artworkKey == _currentKey) { + if (widget.pending) return; + if (oldWidget.pending) { + _transitionToIncoming(); + return; + } if (widget.image != null && widget.image != _base && _incoming == null) { _base = widget.image; _baseErrorKey = widget.imageErrorKey; @@ -336,12 +373,27 @@ class _BackdropArtworkCrossfadeState extends State<_BackdropArtworkCrossfade> wi } _currentKey = widget.artworkKey; + if (widget.pending) { + if (_base == null && _incoming != null && _fade.value == 1) { + _base = _incoming; + _baseErrorKey = _incomingErrorKey; + } + setState(() { + _fade.stop(); + _dropIncoming(); + }); + return; + } if (widget.image != null && widget.image == _base) { _baseErrorKey = widget.imageErrorKey; _dropIncoming(); return; } + _transitionToIncoming(); + } + + void _transitionToIncoming() { setState(() { _fade.stop(); _fade.value = 0; diff --git a/lib/widgets/desktop_app_bar.dart b/lib/widgets/desktop_app_bar.dart index 95bb7ab9..6ec9b6eb 100644 --- a/lib/widgets/desktop_app_bar.dart +++ b/lib/widgets/desktop_app_bar.dart @@ -52,11 +52,7 @@ class DesktopAppBarSections { final canPop = parentRoute?.canPop ?? false; if (canPop) { - effectiveLeading = AppBarBackButton( - style: BackButtonStyle.plain, - onPressed: () => Navigator.of(context).pop(), - semanticLabel: MaterialLocalizations.of(context).backButtonTooltip, - ); + effectiveLeading = AppBarBackButton(style: BackButtonStyle.plain, onPressed: () => Navigator.of(context).pop()); } } diff --git a/lib/widgets/focus_builders.dart b/lib/widgets/focus_builders.dart index 3035a1c3..b38a99a4 100644 --- a/lib/widgets/focus_builders.dart +++ b/lib/widgets/focus_builders.dart @@ -29,6 +29,8 @@ class FocusBuilders { required FocusNode focusNode, required KeyEventResult Function(FocusNode, KeyEvent) onKeyEvent, required VoidCallback onTap, + required String semanticLabel, + bool? selected, required EdgeInsetsGeometry padding, required Color backgroundColor, double borderRadius = 20, @@ -39,15 +41,22 @@ class FocusBuilders { return Focus( focusNode: focusNode, onKeyEvent: onKeyEvent, - child: ClickableCursor( - child: GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: duration, - curve: Curves.easeOutCubic, - padding: padding, - decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(borderRadius)), - child: child, + child: Semantics( + label: semanticLabel, + button: true, + selected: selected, + onTap: onTap, + excludeSemantics: true, + child: ClickableCursor( + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: duration, + curve: Curves.easeOutCubic, + padding: padding, + decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(borderRadius)), + child: child, + ), ), ), ), diff --git a/lib/widgets/focusable_filter_chip.dart b/lib/widgets/focusable_filter_chip.dart index e976d80b..8c2afb92 100644 --- a/lib/widgets/focusable_filter_chip.dart +++ b/lib/widgets/focusable_filter_chip.dart @@ -104,6 +104,7 @@ class _FocusableFilterChipState extends State with Focusabl focusNode: focusNode, onKeyEvent: _handleKeyEvent, onTap: widget.onPressed, + semanticLabel: widget.label, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), backgroundColor: backgroundColor, child: Row( diff --git a/lib/widgets/focusable_media_card.dart b/lib/widgets/focusable_media_card.dart index a58fff67..1bb72c3b 100644 --- a/lib/widgets/focusable_media_card.dart +++ b/lib/widgets/focusable_media_card.dart @@ -16,6 +16,9 @@ class FocusableMediaCard extends StatefulWidget { /// Either a [MediaItem] or a [MediaPlaylist]. Typed as [Object] because /// Dart has no nominal union type. Forwarded as-is to the inner [MediaCard]. final Object item; + + /// Optional row/column position announced with this card. + final String? semanticValue; final double? width; final double? height; final void Function(MediaItem source)? onRefresh; @@ -78,6 +81,7 @@ class FocusableMediaCard extends StatefulWidget { const FocusableMediaCard({ super.key, required this.item, + this.semanticValue, this.width, this.height, this.onRefresh, @@ -134,6 +138,7 @@ class _FocusableMediaCardState extends State { child: MediaCard( key: _mediaCardKey, item: widget.item, + semanticValue: widget.semanticValue, width: widget.width, height: widget.height, onRefresh: widget.onRefresh, diff --git a/lib/widgets/focusable_tab_chip.dart b/lib/widgets/focusable_tab_chip.dart index 38aa8870..bbf26ed5 100644 --- a/lib/widgets/focusable_tab_chip.dart +++ b/lib/widgets/focusable_tab_chip.dart @@ -174,6 +174,8 @@ class _FocusableTabChipState extends State with FocusableChipS focusNode: focusNode, onKeyEvent: _handleKeyEvent, onTap: widget.onSelect, + semanticLabel: widget.label, + selected: widget.isSelected, padding: hasImage ? const EdgeInsets.all(8) : const EdgeInsets.symmetric(horizontal: 16, vertical: 8), backgroundColor: backgroundColor, borderRadius: hasImage ? 12 : 20, diff --git a/lib/widgets/hotkey_recorder.dart b/lib/widgets/hotkey_recorder.dart index ffc0d3f4..bcb78e83 100644 --- a/lib/widgets/hotkey_recorder.dart +++ b/lib/widgets/hotkey_recorder.dart @@ -49,6 +49,7 @@ class _HotKeyRecorderState extends State { bool _handleKeyEvent(KeyEvent keyEvent) { if (!widget.enabled) return false; + if (keyEvent.logicalKey.isBackKey) return false; if (keyEvent is KeyUpEvent) return false; final physicalKeysPressed = HardwareKeyboard.instance.physicalKeysPressed; diff --git a/lib/widgets/hub_section.dart b/lib/widgets/hub_section.dart index 234a49d7..1475720c 100644 --- a/lib/widgets/hub_section.dart +++ b/lib/widgets/hub_section.dart @@ -46,6 +46,7 @@ enum HubCardSizing { /// - Focus never "escapes" to random elements class HubSection extends StatefulWidget { final MediaHub hub; + final HubFocusMemory focusMemory; final IconData icon; final void Function(MediaItem source)? onRefresh; final VoidCallback? onRemoveFromContinueWatching; @@ -94,6 +95,7 @@ class HubSection extends StatefulWidget { const HubSection({ super.key, required this.hub, + required this.focusMemory, required this.icon, this.onRefresh, this.onRemoveFromContinueWatching, @@ -199,7 +201,7 @@ class HubSectionState extends State with MountedSetStateMixin, Skele final clamped = index.clamp(0, _totalItemCount - 1).toInt(); _focusedIndex = clamped; // Remember this position for this specific hub - HubFocusMemory.setForHub(_focusMemoryKey, clamped); + widget.focusMemory.setForHub(_focusMemoryKey, clamped); _notifyFocusedItemChanged(); _scrollToIndex(clamped); _hubFocusNode.requestFocus(); @@ -211,7 +213,7 @@ class HubSectionState extends State with MountedSetStateMixin, Skele /// Request focus using the stored memory for this hub void requestFocusFromMemory() { - final index = HubFocusMemory.getForHub(_focusMemoryKey, _totalItemCount); + final index = widget.focusMemory.getForHub(_focusMemoryKey, _totalItemCount); requestFocusAt(index); } @@ -286,7 +288,7 @@ class HubSectionState extends State with MountedSetStateMixin, Skele setState(() { _focusedIndex--; }); - HubFocusMemory.setForHub(_focusMemoryKey, _focusedIndex); + widget.focusMemory.setForHub(_focusMemoryKey, _focusedIndex); _notifyFocusedItemChanged(); _scrollToIndex(_focusedIndex); } else if (widget.onNavigateToSidebar != null) { @@ -303,7 +305,7 @@ class HubSectionState extends State with MountedSetStateMixin, Skele setState(() { _focusedIndex++; }); - HubFocusMemory.setForHub(_focusMemoryKey, _focusedIndex); + widget.focusMemory.setForHub(_focusMemoryKey, _focusedIndex); _notifyFocusedItemChanged(); _scrollToIndex(_focusedIndex); } @@ -708,7 +710,7 @@ class HubSectionState extends State with MountedSetStateMixin, Skele setState(() { _focusedIndex = clamped; }); - HubFocusMemory.setForHub(_focusMemoryKey, clamped); + widget.focusMemory.setForHub(_focusMemoryKey, clamped); _notifyFocusedItemChanged(); _scrollToIndex(clamped); _hubFocusNode.requestFocus(); diff --git a/lib/widgets/media_card.dart b/lib/widgets/media_card.dart index 26866fe0..8274395a 100644 --- a/lib/widgets/media_card.dart +++ b/lib/widgets/media_card.dart @@ -119,6 +119,9 @@ class MediaCard extends StatefulWidget { /// Either a [MediaItem] or a [MediaPlaylist]. Typed as [Object] because Dart /// has no nominal union type โ€” runtime `is` checks select the variant. final Object item; + + /// Optional collection position announced with the card. + final String? semanticValue; final double? width; final double? height; final void Function(MediaItem source)? onRefresh; @@ -148,6 +151,7 @@ class MediaCard extends StatefulWidget { const MediaCard({ super.key, required this.item, + this.semanticValue, this.width, this.height, this.onRefresh, @@ -298,13 +302,14 @@ class MediaCardState extends State with ContextMenuTapMixin _handleTap(context, item), onTapDown: storeTapPosition, onLongPress: showContextMenuFromTap, @@ -316,9 +321,21 @@ class MediaCardState extends State with ContextMenuTapMixin with ContextMenuTapMixin with ContextMenuTapMixin with ContextMenuTapMixin with ContextMenuTapMixin _navigateToFocusedDetail(context, item, isOffline: widget.isOffline), ) else - Text( - item is MediaPlaylist ? item.title : (item as MediaItem).displayTitle, - maxLines: 1, - overflow: .ellipsis, - style: const TextStyle(fontWeight: .w600, fontSize: 13, height: 1.1), + ExcludeSemantics( + child: Text( + item is MediaPlaylist ? item.title : (item as MediaItem).displayTitle, + maxLines: 1, + overflow: .ellipsis, + style: const TextStyle(fontWeight: .w600, fontSize: 13, height: 1.1), + ), ), // Subtitle if (item is MediaPlaylist) @@ -498,7 +524,6 @@ class MediaCardState extends State with ContextMenuTapMixin _navigateToFocusedDetail(context, mi, isOffline: isOffline), ) else - Text('S${mi.parentIndex}', style: style), - Text('$episodeNum ยท ', style: style), + ExcludeSemantics(child: Text('S${mi.parentIndex}', style: style)), + ExcludeSemantics(child: Text('$episodeNum ยท ', style: style)), Expanded( - child: Text(episodeTitle, maxLines: 1, overflow: .ellipsis, style: style), + child: ExcludeSemantics( + child: Text(episodeTitle, maxLines: 1, overflow: .ellipsis, style: style), + ), ), ], ); @@ -699,26 +725,28 @@ class _MediaCardList extends StatelessWidget { child: Row( crossAxisAlignment: .start, children: [ - SizedBox( - width: _posterWidth(), - height: _posterHeight(), - child: Stack( - children: [ - _clipPosterImage( - context, - item, - _buildPosterImage( + ExcludeSemantics( + child: SizedBox( + width: _posterWidth(), + height: _posterHeight(), + child: Stack( + children: [ + _clipPosterImage( context, item, - isOffline: isOffline, - localPosterPath: localPosterPath, - episodePosterModeOverride: episodePosterModeOverride, - cardShapeOverride: cardShapeOverride, + _buildPosterImage( + context, + item, + isOffline: isOffline, + localPosterPath: localPosterPath, + episodePosterModeOverride: episodePosterModeOverride, + cardShapeOverride: cardShapeOverride, + ), ), - ), - if (item is MediaItem && _showsWatchedIndicator(item as MediaItem)) - WatchedIndicator(item: item as MediaItem), - ], + if (item is MediaItem && _showsWatchedIndicator(item as MediaItem)) + WatchedIndicator(item: item as MediaItem), + ], + ), ), ), const SizedBox(width: 12), @@ -734,22 +762,26 @@ class _MediaCardList extends StatelessWidget { onTap: () => _navigateToFocusedDetail(context, item as MediaItem, isOffline: isOffline), ) else - Text( - _displayTitle(), - maxLines: 2, - overflow: .ellipsis, - style: TextStyle(fontWeight: .w600, fontSize: _titleFontSize, height: 1.2), + ExcludeSemantics( + child: Text( + _displayTitle(), + maxLines: 2, + overflow: .ellipsis, + style: TextStyle(fontWeight: .w600, fontSize: _titleFontSize, height: 1.2), + ), ), const SizedBox(height: 4), if (metadataLine.isNotEmpty) ...[ - Text( - metadataLine, - maxLines: 1, - overflow: .ellipsis, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: tokens(context).textMuted.withValues(alpha: 0.9), - fontSize: _metadataFontSize, - fontWeight: .w500, + ExcludeSemantics( + child: Text( + metadataLine, + maxLines: 1, + overflow: .ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: tokens(context).textMuted.withValues(alpha: 0.9), + fontSize: _metadataFontSize, + fontWeight: .w500, + ), ), ), const SizedBox(height: 2), @@ -761,13 +793,15 @@ class _MediaCardList extends StatelessWidget { _buildEpisodeSubtitle(context, item as MediaItem), const SizedBox(height: 4), ] else if (subtitle != null) ...[ - Text( - subtitle, - maxLines: 1, - overflow: .ellipsis, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: tokens(context).textMuted.withValues(alpha: 0.85), - fontSize: _subtitleFontSize, + ExcludeSemantics( + child: Text( + subtitle, + maxLines: 1, + overflow: .ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: tokens(context).textMuted.withValues(alpha: 0.85), + fontSize: _subtitleFontSize, + ), ), ), const SizedBox(height: 4), @@ -776,39 +810,43 @@ class _MediaCardList extends StatelessWidget { SettingsService.instance.read(SettingsService.hideSpoilers) && (item as MediaItem).shouldHideSpoiler) && _summary() != null) ...[ - Text( - _summary()!, - maxLines: _summaryMaxLines, - overflow: .ellipsis, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: tokens(context).textMuted.withValues(alpha: 0.7), - fontSize: _summaryFontSize, - height: 1.3, + ExcludeSemantics( + child: Text( + _summary()!, + maxLines: _summaryMaxLines, + overflow: .ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: tokens(context).textMuted.withValues(alpha: 0.7), + fontSize: _summaryFontSize, + height: 1.3, + ), ), ), ], if (showServerName && item is MediaItem && (item as MediaItem).serverName != null) ...[ const SizedBox(height: 4), - Row( - children: [ - BackendBadge( - backend: (item as MediaItem).backend, - size: _metadataFontSize + 2, - color: tokens(context).textMuted.withValues(alpha: 0.6), - ), - const SizedBox(width: 4), - Flexible( - child: Text( - (item as MediaItem).serverName!, - maxLines: 1, - overflow: .ellipsis, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: tokens(context).textMuted.withValues(alpha: 0.6), - fontSize: _metadataFontSize, + ExcludeSemantics( + child: Row( + children: [ + BackendBadge( + backend: (item as MediaItem).backend, + size: _metadataFontSize + 2, + color: tokens(context).textMuted.withValues(alpha: 0.6), + ), + const SizedBox(width: 4), + Flexible( + child: Text( + (item as MediaItem).serverName!, + maxLines: 1, + overflow: .ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: tokens(context).textMuted.withValues(alpha: 0.6), + fontSize: _metadataFontSize, + ), ), ), - ), - ], + ], + ), ), ], ], @@ -1001,13 +1039,15 @@ Widget _buildPosterImage( class _MediaCardHelpers { static Widget buildPlaylistMeta(BuildContext context, MediaPlaylist playlist) { if (playlist.leafCount != null && playlist.leafCount! > 0) { - return Text( - t.playlists.itemCount(count: playlist.leafCount!), - maxLines: 1, - overflow: .ellipsis, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: tokens(context).textMuted, fontSize: 11, height: 1.1), + return ExcludeSemantics( + child: Text( + t.playlists.itemCount(count: playlist.leafCount!), + maxLines: 1, + overflow: .ellipsis, + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(color: tokens(context).textMuted, fontSize: 11, height: 1.1), + ), ); } return const SizedBox.shrink(); @@ -1028,25 +1068,31 @@ class _MediaCardHelpers { if (mi.kind == MediaKind.collection) { final count = mi.childCount ?? mi.leafCount; if (count != null && count > 0) { - return Text( - t.playlists.itemCount(count: count), - maxLines: 1, - overflow: .ellipsis, - style: subtitleStyle, + return ExcludeSemantics( + child: Text( + t.playlists.itemCount(count: count), + maxLines: 1, + overflow: .ellipsis, + style: subtitleStyle, + ), ); } } // For albums, show the album artist if (mi.kind == MediaKind.album && mi.albumArtistTitle != null) { - return Text(mi.albumArtistTitle!, maxLines: 1, overflow: .ellipsis, style: subtitleStyle); + return ExcludeSemantics( + child: Text(mi.albumArtistTitle!, maxLines: 1, overflow: .ellipsis, style: subtitleStyle), + ); } // For tracks, show "Artist โ€ข duration" if (mi.kind == MediaKind.track) { final parts = [?mi.trackArtistTitle, if (mi.durationMs case final durationMs?) formatDurationTextual(durationMs)]; if (parts.isNotEmpty) { - return Text(parts.join(' โ€ข '), maxLines: 1, overflow: .ellipsis, style: subtitleStyle); + return ExcludeSemantics( + child: Text(parts.join(' โ€ข '), maxLines: 1, overflow: .ellipsis, style: subtitleStyle), + ); } } @@ -1063,33 +1109,43 @@ class _MediaCardHelpers { style: subtitleStyle, onTap: () => _navigateToFocusedDetail(context, mi, isOffline: isOffline), ), - Text('$episodeSuffix ยท ', style: subtitleStyle), + ExcludeSemantics(child: Text('$episodeSuffix ยท ', style: subtitleStyle)), Expanded( - child: Text(episodeTitle, maxLines: 1, overflow: .ellipsis, style: subtitleStyle), + child: ExcludeSemantics( + child: Text(episodeTitle, maxLines: 1, overflow: .ellipsis, style: subtitleStyle), + ), ), ], ); } - return Text( - 'S${mi.parentIndex}$episodeSuffix ยท $episodeTitle', - maxLines: 1, - overflow: .ellipsis, - style: subtitleStyle, + return ExcludeSemantics( + child: Text( + 'S${mi.parentIndex}$episodeSuffix ยท $episodeTitle', + maxLines: 1, + overflow: .ellipsis, + style: subtitleStyle, + ), ); } // For other media types, show subtitle/parent/year if (mi.displaySubtitle != null) { - return Text(mi.displaySubtitle!, maxLines: 1, overflow: .ellipsis, style: subtitleStyle); + return ExcludeSemantics( + child: Text(mi.displaySubtitle!, maxLines: 1, overflow: .ellipsis, style: subtitleStyle), + ); } else if (mi.parentTitle != null) { - return Text(mi.parentTitle!, maxLines: 1, overflow: .ellipsis, style: subtitleStyle); + return ExcludeSemantics( + child: Text(mi.parentTitle!, maxLines: 1, overflow: .ellipsis, style: subtitleStyle), + ); } else if (mi.year != null) { final edition = mi.editionTitle; - return Text( - edition != null ? '${mi.year} ยท $edition' : '${mi.year}', - maxLines: 1, - overflow: .ellipsis, - style: subtitleStyle, + return ExcludeSemantics( + child: Text( + edition != null ? '${mi.year} ยท $edition' : '${mi.year}', + maxLines: 1, + overflow: .ellipsis, + style: subtitleStyle, + ), ); } @@ -1097,6 +1153,12 @@ class _MediaCardHelpers { } } +/// Whether the card renders any pointer detail link for this item. +bool _hasPointerDetailLinks(MediaItem mi) { + if (_hasClickableTitle(mi)) return true; + return mi.isEpisode && mi.parentIndex != null && mi.parentId != null; +} + /// Whether this media item has a clickable title that navigates somewhere. /// Episodes/seasons navigate to their parent show; movies navigate to their detail page. bool _hasClickableTitle(MediaItem mi) { @@ -1111,7 +1173,8 @@ void _navigateToFocusedDetail(BuildContext context, MediaItem item, {bool isOffl } /// Text widget that shows hover underline + pointer cursor only in pointer mode. -/// In keyboard/dpad mode, renders as plain text with no interaction. +/// Keyboard/dpad mode keeps plain visual text while screen readers retain the +/// separately invokable detail action. class _ClickableText extends StatefulWidget { final String text; final TextStyle? style; @@ -1130,27 +1193,32 @@ class _ClickableTextState extends State<_ClickableText> { Widget build(BuildContext context) { final isKeyboard = InputModeTracker.isKeyboardMode(context); final baseStyle = widget.style ?? const TextStyle(); + final text = Semantics( + label: widget.text, + hint: t.mediaMenu.viewDetails, + button: true, + onTap: widget.onTap, + excludeSemantics: true, + child: Text( + widget.text, + maxLines: 1, + overflow: .ellipsis, + style: isKeyboard + ? baseStyle + : baseStyle.copyWith( + decoration: _isHovered ? TextDecoration.underline : null, + decorationColor: baseStyle.color, + ), + ), + ); - if (isKeyboard) { - return Text(widget.text, maxLines: 1, overflow: .ellipsis, style: baseStyle); - } + if (isKeyboard) return text; return MouseRegion( cursor: SystemMouseCursors.click, onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), - child: GestureDetector( - onTap: widget.onTap, - child: Text( - widget.text, - maxLines: 1, - overflow: .ellipsis, - style: baseStyle.copyWith( - decoration: _isHovered ? TextDecoration.underline : null, - decorationColor: baseStyle.color, - ), - ), - ), + child: GestureDetector(excludeFromSemantics: true, onTap: widget.onTap, child: text), ); } } @@ -1204,6 +1272,7 @@ class _CardTapRegion extends StatelessWidget { Widget build(BuildContext context) { if (!PlatformDetector.isDesktopOS()) { return GestureDetector( + excludeFromSemantics: true, behavior: HitTestBehavior.opaque, onTap: onTap, onTapDown: onTapDown, @@ -1214,6 +1283,7 @@ class _CardTapRegion extends StatelessWidget { ); } return InkWell( + excludeFromSemantics: true, mouseCursor: SystemMouseCursors.click, canRequestFocus: false, onTap: onTap, diff --git a/lib/widgets/optimized_media_image.dart b/lib/widgets/optimized_media_image.dart index 7e2d3ae6..3e4e1e0d 100644 --- a/lib/widgets/optimized_media_image.dart +++ b/lib/widgets/optimized_media_image.dart @@ -43,6 +43,7 @@ class OptimizedMediaImage extends StatelessWidget { final IconData? fallbackIcon; final ImageType imageType; final String? localFilePath; + final bool cacheMissingLocalFile; const OptimizedMediaImage._({ super.key, @@ -61,6 +62,7 @@ class OptimizedMediaImage extends StatelessWidget { this.fallbackIcon, this.imageType = ImageType.poster, this.localFilePath, + this.cacheMissingLocalFile = false, }); /// Generic constructor for optimized images. @@ -81,6 +83,7 @@ class OptimizedMediaImage extends StatelessWidget { IconData? fallbackIcon, ImageType imageType, String? localFilePath, + bool cacheMissingLocalFile, }) = OptimizedMediaImage._; /// Named constructor for poster images with default fallback icon. @@ -199,16 +202,27 @@ class OptimizedMediaImage extends StatelessWidget { Widget build(BuildContext context) { final path = localFilePath; if (path == null) { - return _buildResolved(context, _LocalFileResolution.missing, null); + return _buildResolved(context, LocalFileResolution.missing, null); } - return _ResolvedLocalFile(path: path, builder: _buildResolved); + return ResolvedLocalFile(path: path, cacheMissing: cacheMissingLocalFile, builder: _buildResolved); } - Widget _buildResolved(BuildContext context, _LocalFileResolution resolution, File? localFile) { - if (resolution == _LocalFileResolution.pending) return _surfacePlaceholder(context); - final hasLocal = resolution == _LocalFileResolution.present; + Widget _buildResolved(BuildContext context, LocalFileResolution resolution, File? localFile) { + if (resolution == LocalFileResolution.pending) { + return placeholder == null + ? _surfacePlaceholder(context) + : _buildPlaceholder(context, imagePath ?? localFilePath ?? ''); + } + final hasLocal = resolution == LocalFileResolution.present; if (!hasLocal && (imagePath == null || imagePath!.isEmpty)) { + if (errorWidget != null) { + return errorWidget!( + context, + localFilePath ?? '', + FileSystemException('Local image file is unavailable', localFilePath), + ); + } return _buildFallback(context); } @@ -484,22 +498,43 @@ class _FadeInNetworkImageState extends State<_FadeInNetworkImage> with SingleTic } } -enum _LocalFileResolution { pending, missing, present } +enum LocalFileResolution { pending, missing, present } -class _ResolvedLocalFile extends StatefulWidget { - const _ResolvedLocalFile({required this.path, required this.builder}); +typedef LocalFileResolutionBuilder = Widget Function(BuildContext context, LocalFileResolution resolution, File? file); + +Future _defaultLocalFileExists(File file) => file.exists(); + +/// Resolves local file availability without blocking the build isolate. +/// +/// Results are scoped to this widget state and keyed by [path]. Present files +/// are always cached. Missing files are cached only when [cacheMissing] is set, +/// allowing consumers that expect late file creation to retry on rebuild. +class ResolvedLocalFile extends StatefulWidget { + const ResolvedLocalFile({ + super.key, + required this.path, + required this.builder, + this.cacheMissing = false, + this.fileExists = _defaultLocalFileExists, + }); final String path; - final Widget Function(BuildContext context, _LocalFileResolution resolution, File? file) builder; + final LocalFileResolutionBuilder builder; + final bool cacheMissing; + + @visibleForTesting + final Future Function(File file) fileExists; @override - State<_ResolvedLocalFile> createState() => _ResolvedLocalFileState(); + State createState() => _ResolvedLocalFileState(); } -class _ResolvedLocalFileState extends State<_ResolvedLocalFile> { +class _ResolvedLocalFileState extends State { + final Map _presentFiles = {}; + final Set _missingFiles = {}; + final Set _pendingPaths = {}; File? _file; - _LocalFileResolution _resolution = _LocalFileResolution.pending; - int _generation = 0; + LocalFileResolution _resolution = LocalFileResolution.pending; @override void initState() { @@ -508,28 +543,74 @@ class _ResolvedLocalFileState extends State<_ResolvedLocalFile> { } @override - void didUpdateWidget(_ResolvedLocalFile oldWidget) { + void didUpdateWidget(ResolvedLocalFile oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.path != widget.path || _resolution == _LocalFileResolution.missing) _resolve(); + if (oldWidget.fileExists != widget.fileExists) { + _presentFiles.clear(); + _missingFiles.clear(); + _pendingPaths.clear(); + } else if (oldWidget.cacheMissing && !widget.cacheMissing) { + _missingFiles.clear(); + } + if (oldWidget.path != widget.path || + oldWidget.fileExists != widget.fileExists || + _resolution == LocalFileResolution.missing) { + _resolve(); + } } void _resolve() { - final generation = ++_generation; + final path = widget.path; + final present = _presentFiles[path]; + if (present != null) { + _file = present; + _resolution = LocalFileResolution.present; + return; + } + if (widget.cacheMissing && _missingFiles.contains(path)) { + _file = null; + _resolution = LocalFileResolution.missing; + return; + } + _file = null; - _resolution = _LocalFileResolution.pending; - final candidate = File(widget.path); - candidate.exists().then((exists) { - if (!mounted || generation != _generation) return; - setState(() { - _file = exists ? candidate : null; - _resolution = exists ? _LocalFileResolution.present : _LocalFileResolution.missing; - }); + _resolution = LocalFileResolution.pending; + if (!_pendingPaths.add(path)) return; + + final candidate = File(path); + try { + widget + .fileExists(candidate) + .then( + (exists) => _complete(path, candidate, exists), + onError: (Object _, StackTrace _) => _complete(path, candidate, false), + ); + } catch (_) { + _complete(path, candidate, false); + } + } + + void _complete(String path, File candidate, bool exists) { + if (!mounted) return; + _pendingPaths.remove(path); + if (exists) { + _presentFiles[path] = candidate; + _missingFiles.remove(path); + } else if (widget.cacheMissing) { + _missingFiles.add(path); + } + if (widget.path != path) return; + setState(() { + _file = exists ? candidate : null; + _resolution = exists ? LocalFileResolution.present : LocalFileResolution.missing; }); } @override void dispose() { - ++_generation; + _presentFiles.clear(); + _missingFiles.clear(); + _pendingPaths.clear(); super.dispose(); } diff --git a/lib/widgets/server_activities_button.dart b/lib/widgets/server_activities_button.dart index 4a825c75..94b63333 100644 --- a/lib/widgets/server_activities_button.dart +++ b/lib/widgets/server_activities_button.dart @@ -5,12 +5,14 @@ import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; +import '../exceptions/media_server_exceptions.dart'; import '../focus/key_event_utils.dart'; import '../i18n/strings.g.dart'; import '../theme/mono_tokens.dart'; import 'package:plezy/widgets/app_icon.dart'; import '../models/plex/plex_activity.dart'; import '../providers/multi_server_provider.dart'; +import '../utils/media_server_http_client.dart'; class ServerActivitiesButton extends StatefulWidget { const ServerActivitiesButton({super.key}); @@ -43,6 +45,8 @@ class ServerActivitiesButtonState extends State { OverlayEntry? _overlayEntry; final _panelNotifier = ValueNotifier<_PanelData>(_PanelData.loading); Timer? _pollTimer; + AbortController? _activeLoadAbort; + int _loadGeneration = 0; @override void deactivate() { @@ -60,6 +64,10 @@ class ServerActivitiesButtonState extends State { void _removeOverlay() { _pollTimer?.cancel(); _pollTimer = null; + _loadGeneration++; + final activeLoadAbort = _activeLoadAbort; + _activeLoadAbort = null; + activeLoadAbort?.abort(); _overlayEntry?.remove(); _overlayEntry = null; } @@ -84,10 +92,10 @@ class ServerActivitiesButtonState extends State { builder: (_) => _buildOverlay(right: right, top: top), ); Overlay.of(context).insert(_overlayEntry!); - _fetchActivities(); + _startRefresh(silent: false); } - Future> _loadFromServers() async { + Future> _loadFromServers(AbortController abort) async { final multiServer = Provider.of(context, listen: false); final serverIds = multiServer.onlineServerIds; @@ -95,7 +103,7 @@ class ServerActivitiesButtonState extends State { // Plex-only: `/activities` API is Plex-specific. final client = multiServer.getPlexClientForServer(ServerId(serverId)); if (client == null) return null; - final activities = await client.getActivities(); + final activities = await client.getActivities(abort: abort); return _ServerResult(serverId: serverId, serverName: client.serverName ?? serverId, activities: activities); }); @@ -103,32 +111,70 @@ class ServerActivitiesButtonState extends State { return rawResults.whereType<_ServerResult>().toList(); } - Future _fetchActivities() async { - try { - final results = await _loadFromServers(); - if (!mounted) return; - _panelNotifier.value = _PanelData(fetchState: _FetchState.loaded, results: results); - _startPolling(); - } catch (_) { - if (!mounted) return; - _panelNotifier.value = const _PanelData(fetchState: _FetchState.error, results: []); - } - } + void _startRefresh({required bool silent}) { + if (!mounted || _overlayEntry == null) return; - void _startPolling() { _pollTimer?.cancel(); - if (mounted && _overlayEntry != null) { - _pollTimer = Timer.periodic(const Duration(seconds: 3), (_) => _silentRefresh()); + _pollTimer = null; + final generation = ++_loadGeneration; + final supersededAbort = _activeLoadAbort; + final abort = AbortController(); + _activeLoadAbort = abort; + supersededAbort?.abort(); + + if (!silent) { + _panelNotifier.value = _PanelData.loading; + } + unawaited(_runRefresh(generation: generation, abort: abort, silent: silent)); + } + + Future _runRefresh({required int generation, required AbortController abort, required bool silent}) async { + var scheduleNext = false; + try { + final results = await _loadFromServers(abort); + if (!_ownsLoad(generation, abort)) return; + _panelNotifier.value = _PanelData(fetchState: _FetchState.loaded, results: results); + scheduleNext = true; + } on MediaServerHttpException catch (error) { + if (error.isCancellation || !_ownsLoad(generation, abort)) return; + if (silent) { + scheduleNext = true; + } else { + _panelNotifier.value = const _PanelData(fetchState: _FetchState.error, results: []); + } + } catch (_) { + if (!_ownsLoad(generation, abort)) return; + if (silent) { + scheduleNext = true; + } else { + _panelNotifier.value = const _PanelData(fetchState: _FetchState.error, results: []); + } + } finally { + if (identical(_activeLoadAbort, abort)) { + _activeLoadAbort = null; + if (scheduleNext && _isCurrentGeneration(generation)) { + _schedulePoll(generation); + } + } } } - Future _silentRefresh() async { - if (!mounted) return; - try { - final results = await _loadFromServers(); - if (!mounted) return; - _panelNotifier.value = _PanelData(fetchState: _FetchState.loaded, results: results); - } catch (_) {} + bool _ownsLoad(int generation, AbortController abort) { + return _isCurrentGeneration(generation) && identical(_activeLoadAbort, abort); + } + + bool _isCurrentGeneration(int generation) { + return mounted && _overlayEntry != null && _loadGeneration == generation; + } + + void _schedulePoll(int generation) { + _pollTimer?.cancel(); + if (!_isCurrentGeneration(generation)) return; + _pollTimer = Timer(const Duration(seconds: 3), () { + if (!_isCurrentGeneration(generation)) return; + _pollTimer = null; + _startRefresh(silent: true); + }); } Future _cancelActivity(ServerId serverId, String uuid) async { @@ -142,10 +188,7 @@ class ServerActivitiesButtonState extends State { return; } if (!mounted || _overlayEntry == null) return; - _pollTimer?.cancel(); - _pollTimer = null; - _panelNotifier.value = _PanelData.loading; - unawaited(_fetchActivities()); + _startRefresh(silent: false); } Widget _buildOverlay({required double right, required double top}) { diff --git a/lib/widgets/setting_tile.dart b/lib/widgets/setting_tile.dart index 56ccea0a..f8af24c2 100644 --- a/lib/widgets/setting_tile.dart +++ b/lib/widgets/setting_tile.dart @@ -49,8 +49,8 @@ class SettingSwitchTile extends StatelessWidget { title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: subtitle != null ? Text(subtitle!) : null, value: value, - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: settingsRowDense(context), + visualDensity: settingsRowVisualDensity(context), onChanged: enabled ? (v) async { await svc.write(pref, v); @@ -93,8 +93,8 @@ class SettingNavigationTile extends StatelessWidget { subtitle: subtitle != null ? Text(subtitle!) : null, trailing: AppIcon(trailingIcon, fill: 1), onTap: onTap ?? () => Navigator.push(context, MaterialPageRoute(builder: destinationBuilder!)), - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: settingsRowDense(context), + visualDensity: settingsRowVisualDensity(context), ); } } @@ -134,8 +134,8 @@ class SettingNumberTile extends StatelessWidget { title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: Text(subtitleBuilder(value)), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: settingsRowDense(context), + visualDensity: settingsRowVisualDensity(context), onTap: () => showNumericInputDialog( context: context, title: title, @@ -192,8 +192,8 @@ class SettingSelectionTile extends StatelessWidget { title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: Text(subtitleBuilder(value)), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: settingsRowDense(context), + visualDensity: settingsRowVisualDensity(context), onTap: () async { final picked = await showSelectionDialog( context: context, @@ -241,8 +241,8 @@ class SettingRegexTile extends StatelessWidget { title: Text(title, style: settingsOptionTitleStyle(context)), subtitle: Text(subtitle), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: settingsRowDense(context), + visualDensity: settingsRowVisualDensity(context), onTap: () => showRegexInputDialog( context: context, title: title, @@ -341,8 +341,8 @@ class SettingColorTile extends StatelessWidget { border: Border.all(color: Theme.of(context).colorScheme.outlineVariant), ), ), - dense: settingsRowDense(), - visualDensity: settingsRowVisualDensity(), + dense: settingsRowDense(context), + visualDensity: settingsRowVisualDensity(context), onTap: () => showColorInputDialog( context: context, title: title, diff --git a/lib/widgets/settings_section.dart b/lib/widgets/settings_section.dart index 6fdc40c9..8d8fd6fc 100644 --- a/lib/widgets/settings_section.dart +++ b/lib/widgets/settings_section.dart @@ -6,21 +6,19 @@ import 'expressive_button_group.dart'; /// Standard settings-option title style. /// -/// Matches Flutter's dense ListTile title size so custom controls and -/// ordinary settings rows keep the same hierarchy. TV retains the larger -/// body style used for D-pad readability at distance. +/// Mobile uses Flutter's compact ListTile title size. Desktop and TV retain +/// the larger body style used for pointer and D-pad readability. TextStyle? settingsOptionTitleStyle(BuildContext context) { final style = Theme.of(context).textTheme.bodyLarge; - return PlatformDetector.isTV() ? style : style?.copyWith(fontSize: 13); + return PlatformDetector.isMobile(context) ? style?.copyWith(fontSize: 13) : style; } /// Standard settings-row density, paired with [settingsOptionTitleStyle]: -/// compact rows everywhere except TV, where full-height rows keep D-pad -/// readability. -bool settingsRowDense() => !PlatformDetector.isTV(); +/// compact on mobile and full-height on desktop and TV. +bool settingsRowDense(BuildContext context) => PlatformDetector.isMobile(context); -VisualDensity settingsRowVisualDensity() => - settingsRowDense() ? const VisualDensity(vertical: -3) : VisualDensity.standard; +VisualDensity settingsRowVisualDensity(BuildContext context) => + settingsRowDense(context) ? const VisualDensity(vertical: -3) : VisualDensity.standard; class SettingsSectionHeader extends StatelessWidget { final String title; diff --git a/lib/widgets/tv_browse_rail.dart b/lib/widgets/tv_browse_rail.dart index 88cacf4e..56827ccb 100644 --- a/lib/widgets/tv_browse_rail.dart +++ b/lib/widgets/tv_browse_rail.dart @@ -306,6 +306,7 @@ enum TvRailTrailing { none, loading, error, viewAll } class TvBrowseRail extends StatefulWidget { final List hubs; + final HubFocusMemory focusMemory; final IconData Function(MediaHub hub, int index) iconForHub; /// Whether to show each hub's originating server name in its header. Used when @@ -354,6 +355,7 @@ class TvBrowseRail extends StatefulWidget { const TvBrowseRail({ super.key, required this.hubs, + required this.focusMemory, required this.iconForHub, this.showServerName = false, this.onFocusedItemChanged, @@ -774,7 +776,7 @@ class TvBrowseRailState extends State { final currentHub = _activeHub; if (currentHub != null) _rememberFocus(currentHub); final nextHub = widget.hubs[next]; - final remembered = HubFocusMemory.getForHubOnly(_hubKey(nextHub), _totalItemCount(nextHub)); + final remembered = widget.focusMemory.getForHubOnly(_hubKey(nextHub), _totalItemCount(nextHub)); // No setState: the active-hub change is observed through _focusModel // selectors (cards, headers, row dim), so a hub move repaints only the // two affected rows instead of rebuilding every visible card. Section @@ -857,7 +859,7 @@ class TvBrowseRailState extends State { } void _rememberFocus(MediaHub hub) { - HubFocusMemory.setForHub(_hubKey(hub), _itemIndex); + widget.focusMemory.setForHub(_hubKey(hub), _itemIndex); } void _scrollToItem({bool animate = true, Duration duration = _navigationScrollDuration}) { @@ -1354,7 +1356,7 @@ class TvBrowseRailState extends State { }) { final isActiveHub = hubIndex == _hubIndex; final totalCount = _totalItemCount(hub); - final inactiveIndex = HubFocusMemory.getForHubOnly(_hubKey(hub), totalCount); + final inactiveIndex = widget.focusMemory.getForHubOnly(_hubKey(hub), totalCount); final focusedIndex = isActiveHub ? _itemIndex : inactiveIndex; final scrollController = _scrollControllerForHub(hub, metrics, railViewportWidth, scale, focusedIndex); _metricsByHub[_hubKey(hub)] = metrics; diff --git a/lib/widgets/tv_virtual_keyboard.dart b/lib/widgets/tv_virtual_keyboard.dart index 1b3ae138..893a0d60 100644 --- a/lib/widgets/tv_virtual_keyboard.dart +++ b/lib/widgets/tv_virtual_keyboard.dart @@ -398,7 +398,7 @@ class _TvVirtualKeyboardDialogState extends State<_TvVirtualKeyboardDialog> { } } - return KeyEventResult.handled; + return KeyEventResult.ignored; } bool _handlePhysicalKeyboardTextInput(KeyEvent event) { @@ -564,22 +564,15 @@ class _TvVirtualKeyboardDialogState extends State<_TvVirtualKeyboardDialog> { void _backspace() { final value = widget.controller.value; final (:start, :end) = _selectionRangeForEdit(value); + if (start == end && start == 0) return; - if (start != end) { - _replace( - value.copyWith( - text: value.text.replaceRange(start, end, ''), - selection: TextSelection.collapsed(offset: start), - ), - ); - return; - } - if (start == 0) return; - + final codeUnitRange = start == end ? TextRange(start: start - 1, end: start) : TextRange(start: start, end: end); + final range = expandToGraphemeRange(value.text, codeUnitRange); + if (range.isCollapsed) return; _replace( value.copyWith( - text: value.text.replaceRange(start - 1, start, ''), - selection: TextSelection.collapsed(offset: start - 1), + text: value.text.replaceRange(range.start, range.end, ''), + selection: TextSelection.collapsed(offset: range.start), composing: TextRange.empty, ), ); @@ -588,23 +581,15 @@ class _TvVirtualKeyboardDialogState extends State<_TvVirtualKeyboardDialog> { void _deleteForward() { final value = widget.controller.value; final (:start, :end) = _selectionRangeForEdit(value); + if (start == end && start >= value.text.length) return; - if (start != end) { - _replace( - value.copyWith( - text: value.text.replaceRange(start, end, ''), - selection: TextSelection.collapsed(offset: start), - composing: TextRange.empty, - ), - ); - return; - } - if (start >= value.text.length) return; - + final codeUnitRange = start == end ? TextRange(start: start, end: start + 1) : TextRange(start: start, end: end); + final range = expandToGraphemeRange(value.text, codeUnitRange); + if (range.isCollapsed) return; _replace( value.copyWith( - text: value.text.replaceRange(start, start + 1, ''), - selection: TextSelection.collapsed(offset: start), + text: value.text.replaceRange(range.start, range.end, ''), + selection: TextSelection.collapsed(offset: range.start), composing: TextRange.empty, ), ); diff --git a/lib/widgets/video_controls/models/track_controls_state.dart b/lib/widgets/video_controls/models/track_controls_state.dart index 0180da1f..57e3a49e 100644 --- a/lib/widgets/video_controls/models/track_controls_state.dart +++ b/lib/widgets/video_controls/models/track_controls_state.dart @@ -47,8 +47,8 @@ class TrackControlsState { final VoidCallback? onToggleAlwaysOnTop; final Function(int)? onSwitchVersion; final ValueChanged? onSwitchQualityPreset; - final ValueChanged? onSwitchAudioStreamId; - final ValueChanged? onSwitchSubtitle; + final Future Function(int)? onSwitchAudioStreamId; + final Future Function(PlaybackSourceSubtitleChoice)? onSwitchSubtitle; final Function(AudioTrack)? onAudioTrackChanged; final Function(SubtitleTrack)? onSubtitleTrackChanged; final Function(SubtitleTrack)? onSecondarySubtitleTrackChanged; diff --git a/lib/widgets/video_controls/parts/track_controls.dart b/lib/widgets/video_controls/parts/track_controls.dart index 6cbc2966..d117162d 100644 --- a/lib/widgets/video_controls/parts/track_controls.dart +++ b/lib/widgets/video_controls/parts/track_controls.dart @@ -1,5 +1,7 @@ part of '../video_controls.dart'; +final Expando> _subtitleVisibilityWrites = Expando>(); + extension _PlexVideoControlsTrackMethods on _PlexVideoControlsState { void _toggleSubtitles() { final currentTrack = widget.player.state.track.subtitle; @@ -19,6 +21,8 @@ extension _PlexVideoControlsTrackMethods on _PlexVideoControlsState { void _setSubtitleVisibility(bool visible) { final targetPlayer = widget.player; + final coordinator = _subtitleVisibilityWrites[targetPlayer] ??= LatestAsyncWrite(); + final writeToken = coordinator.begin('sub-visibility'); final generation = ++_subtitleVisibilityWriteGeneration; _setControlsState(() { _subtitlesVisible = visible; @@ -26,12 +30,25 @@ extension _PlexVideoControlsTrackMethods on _PlexVideoControlsState { unawaited(() async { try { - await targetPlayer.setProperty('sub-visibility', visible ? 'yes' : 'no'); - if (!mounted || generation != _subtitleVisibilityWriteGeneration || targetPlayer != widget.player) return; - _confirmedSubtitlesVisible = visible; + final committed = await coordinator.commitIfLatest('sub-visibility', writeToken, () async { + await targetPlayer.setProperty('sub-visibility', visible ? 'yes' : 'no'); + if (mounted && targetPlayer == widget.player) { + // Preserve every successfully executed mutation as the rollback + // baseline, even when a newer optimistic toggle is queued. + _confirmedSubtitlesVisible = visible; + } + }); + if (!committed || + !mounted || + generation != _subtitleVisibilityWriteGeneration || + targetPlayer != widget.player) { + return; + } } catch (error, stackTrace) { appLogger.w('Failed to update subtitle visibility', error: error, stackTrace: stackTrace); - if (!mounted || generation != _subtitleVisibilityWriteGeneration || targetPlayer != widget.player) return; + if (!mounted || generation != _subtitleVisibilityWriteGeneration || targetPlayer != widget.player) { + return; + } _setControlsState(() { _subtitlesVisible = _confirmedSubtitlesVisible; }); diff --git a/lib/widgets/video_controls/parts/visibility.dart b/lib/widgets/video_controls/parts/visibility.dart index 5783539b..d9fe39ac 100644 --- a/lib/widgets/video_controls/parts/visibility.dart +++ b/lib/widgets/video_controls/parts/visibility.dart @@ -47,7 +47,12 @@ extension _PlexVideoControlsVisibilityMethods on _PlexVideoControlsState { } /// Controls hide delay: 5s on mobile/TV/keyboard-nav, 3s on desktop with mouse. + /// Maestro builds extend the delay because accessibility-tree queries can take + /// longer than the production timeout on physical devices. Duration get _hideDelay { + if (const bool.fromEnvironment('PLEZY_MAESTRO_E2E')) { + return const Duration(seconds: 30); + } final isMobile = (Platform.isIOS || Platform.isAndroid) && !PlatformDetector.isTV(); if (isMobile || PlatformDetector.isTV() || _videoPlayerNavigationEnabled) { return const Duration(seconds: 5); @@ -82,6 +87,15 @@ extension _PlexVideoControlsVisibilityMethods on _PlexVideoControlsState { widget.chromeController.toggle(); } + void _toggleControlsFromSemantics() { + if (_showControls) { + widget.chromeController.hide(); + return; + } + widget.chromeController.show(restartAutoHide: false); + widget.chromeController.cancelAutoHide(); + } + /// Apply preferred orientations for the given lock state. Wired to /// [SettingsService.rotationLocked] via [bindEffect] so any change โ€” from /// this toggle or from the settings screen โ€” fires the same SystemChrome call. diff --git a/lib/widgets/video_controls/sheets/track_sheet.dart b/lib/widgets/video_controls/sheets/track_sheet.dart index c6bf6896..b43a4f0f 100644 --- a/lib/widgets/video_controls/sheets/track_sheet.dart +++ b/lib/widgets/video_controls/sheets/track_sheet.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -135,7 +137,7 @@ class TrackSheet extends StatelessWidget { class _SourceAudioColumn extends StatefulWidget { final List tracks; final int? selectedStreamId; - final ValueChanged onSelected; + final Future Function(int) onSelected; final bool showHeader; const _SourceAudioColumn({ @@ -151,6 +153,7 @@ class _SourceAudioColumn extends StatefulWidget { class _SourceAudioColumnState extends State<_SourceAudioColumn> { final _initialScroll = InitialItemScrollController(); + bool _selectionPending = false; @override void dispose() { @@ -158,6 +161,17 @@ class _SourceAudioColumnState extends State<_SourceAudioColumn> { super.dispose(); } + Future _select(int streamId) async { + if (_selectionPending) return; + setState(() => _selectionPending = true); + try { + await widget.onSelected(streamId); + if (mounted) OverlaySheetController.of(context).close(); + } finally { + if (mounted) setState(() => _selectionPending = false); + } + } + @override Widget build(BuildContext context) { final selectedId = _effectiveSelectedStreamId(); @@ -167,6 +181,7 @@ class _SourceAudioColumnState extends State<_SourceAudioColumn> { return Column( children: [ if (widget.showHeader) SheetColumnHeader(label: t.videoControls.audioLabel), + if (_selectionPending) const LinearProgressIndicator(minHeight: 2), Expanded( child: ListView.builder( controller: _initialScroll.controller, @@ -179,10 +194,7 @@ class _SourceAudioColumnState extends State<_SourceAudioColumn> { key: index == 0 ? _initialScroll.firstItemKey : null, label: track.label, isSelected: isSelected, - onTap: () { - OverlaySheetController.of(context).close(); - widget.onSelected(track.id); - }, + onTap: () => unawaited(_select(track.id)), ); }, ), @@ -214,6 +226,7 @@ class _SourceSubtitleColumn extends StatefulWidget { class _SourceSubtitleColumnState extends State<_SourceSubtitleColumn> { final _initialScroll = InitialItemScrollController(); + bool _selectionPending = false; @override void dispose() { @@ -221,6 +234,17 @@ class _SourceSubtitleColumnState extends State<_SourceSubtitleColumn> { super.dispose(); } + Future _select(PlaybackSourceSubtitleChoice choice) async { + if (_selectionPending) return; + setState(() => _selectionPending = true); + try { + await widget.trackControlsState.onSwitchSubtitle!(choice); + if (mounted) OverlaySheetController.of(context).close(); + } finally { + if (mounted) setState(() => _selectionPending = false); + } + } + @override Widget build(BuildContext context) { final selectedChoice = _effectiveSelectedChoice(); @@ -231,6 +255,7 @@ class _SourceSubtitleColumnState extends State<_SourceSubtitleColumn> { return Column( children: [ if (widget.showHeader) SheetColumnHeader(label: t.videoControls.subtitlesLabel), + if (_selectionPending) const LinearProgressIndicator(minHeight: 2), Expanded( child: ListView.builder( controller: _initialScroll.controller, @@ -241,10 +266,7 @@ class _SourceSubtitleColumnState extends State<_SourceSubtitleColumn> { context: context, key: _initialScroll.firstItemKey, isSelected: selectedChoice.isOff, - onTap: () { - OverlaySheetController.of(context).close(); - widget.trackControlsState.onSwitchSubtitle!(const PlaybackSourceSubtitleChoice.off()); - }, + onTap: () => unawaited(_select(const PlaybackSourceSubtitleChoice.off())), ); } @@ -253,10 +275,7 @@ class _SourceSubtitleColumnState extends State<_SourceSubtitleColumn> { context: context, label: track.labelForIndex(index - 1), isSelected: track.id == selectedId, - onTap: () { - OverlaySheetController.of(context).close(); - widget.trackControlsState.onSwitchSubtitle!(PlaybackSourceSubtitleChoice.source(track.id)); - }, + onTap: () => unawaited(_select(PlaybackSourceSubtitleChoice.source(track.id))), ); }, ), @@ -372,6 +391,7 @@ class _SubtitleColumn extends StatefulWidget { class _SubtitleColumnState extends State<_SubtitleColumn> { final _initialScroll = InitialItemScrollController(); + bool _selectionPending = false; @override void dispose() { @@ -379,6 +399,17 @@ class _SubtitleColumnState extends State<_SubtitleColumn> { super.dispose(); } + Future _selectSourceSidecar(int streamId) async { + if (_selectionPending) return; + setState(() => _selectionPending = true); + try { + await widget.trackControlsState.onSwitchSubtitle!(PlaybackSourceSubtitleChoice.source(streamId)); + if (mounted) OverlaySheetController.of(context).close(); + } finally { + if (mounted) setState(() => _selectionPending = false); + } + } + @override Widget build(BuildContext context) { final selectedSub = widget.selection.subtitle; @@ -401,6 +432,7 @@ class _SubtitleColumnState extends State<_SubtitleColumn> { return Column( children: [ if (widget.showHeader) SheetColumnHeader(label: t.videoControls.subtitlesLabel), + if (_selectionPending) const LinearProgressIndicator(minHeight: 2), Expanded( child: ListView.builder( controller: _initialScroll.controller, @@ -444,10 +476,7 @@ class _SubtitleColumnState extends State<_SubtitleColumn> { context: context, label: sourceTrack.labelForIndex(trackIndex), isSelected: false, - onTap: () { - OverlaySheetController.of(context).close(); - widget.trackControlsState.onSwitchSubtitle!(PlaybackSourceSubtitleChoice.source(sourceTrack.id)); - }, + onTap: () => unawaited(_selectSourceSidecar(sourceTrack.id)), ); } diff --git a/lib/widgets/video_controls/video_control_button.dart b/lib/widgets/video_controls/video_control_button.dart index 5b9b5927..7c9402f2 100644 --- a/lib/widgets/video_controls/video_control_button.dart +++ b/lib/widgets/video_controls/video_control_button.dart @@ -23,6 +23,12 @@ class VideoControlButton extends StatelessWidget { /// If not provided, falls back to tooltip. final String? semanticLabel; + /// Optional current value announced after [semanticLabel]. + final String? semanticValue; + + /// Optional checked state for toggle-style controls. + final bool? checked; + /// Whether this button represents an active state (e.g., a feature is enabled). /// When true, the icon color defaults to amber instead of white. final bool isActive; @@ -45,6 +51,8 @@ class VideoControlButton extends StatelessWidget { this.color, this.tooltip, this.semanticLabel, + this.semanticValue, + this.checked, this.isActive = false, this.focusNode, this.onKeyEvent, @@ -64,9 +72,8 @@ class VideoControlButton extends StatelessWidget { constraints: const BoxConstraints(minWidth: 40, minHeight: 40), ); - Widget result = semanticLabel != null - ? Semantics(label: semanticLabel, button: true, excludeSemantics: true, child: button) - : button; + final effectiveSemanticLabel = semanticLabel ?? tooltip; + Widget result = button; if (focusNode != null) { result = FocusableWrapper( @@ -75,12 +82,25 @@ class VideoControlButton extends StatelessWidget { onKeyEvent: onKeyEvent, onFocusChange: onFocusChange, autofocus: autofocus, - semanticLabel: semanticLabel, + semanticLabel: effectiveSemanticLabel, + semanticValue: semanticValue, + checked: checked, borderRadius: 20, // Circular for icon buttons autoScroll: false, // Video controls don't scroll useBackgroundFocus: true, // Use background highlight for video controls child: result, ); + } else if (effectiveSemanticLabel != null) { + result = Semantics( + label: effectiveSemanticLabel, + value: semanticValue, + button: true, + enabled: onPressed != null, + checked: checked, + onTap: onPressed, + excludeSemantics: true, + child: result, + ); } return result; diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index f982ed72..bd306e44 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -61,6 +61,7 @@ import '../../utils/player_utils.dart'; import '../../theme/mono_tokens.dart'; import '../../utils/provider_extensions.dart'; import '../../utils/snackbar_helper.dart'; +import '../../utils/latest_async_write.dart'; import 'icons.dart'; import 'player_chrome_controller.dart'; import 'playback_extras_loader.dart'; @@ -802,6 +803,9 @@ class _PlexVideoControlsState extends State @override void didUpdateWidget(PlexVideoControls oldWidget) { super.didUpdateWidget(oldWidget); + if (oldWidget.player != widget.player) { + ++_subtitleVisibilityWriteGeneration; + } if (oldWidget.chromeController != widget.chromeController) { oldWidget.chromeController.removeListener(_onChromeChanged); _lastControlsVisible = widget.chromeController.controlsVisible; @@ -827,6 +831,7 @@ class _PlexVideoControlsState extends State @override void dispose() { + ++_subtitleVisibilityWriteGeneration; HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent); widget.chromeController.removeListener(_onChromeChanged); widget.hasFirstFrame?.removeListener(_onFirstFrameReady); @@ -977,13 +982,21 @@ class _PlexVideoControlsState extends State const Positioned(top: 0, left: 0, child: LinuxKeepAlive()), // Also handles long-press for 2x speed. Positioned.fill( - child: GestureDetector( - onTap: _handleOuterTap, - onLongPressStart: (_) => _handleLongPressStart(), - onLongPressEnd: (_) => _handleLongPressEnd(), - onLongPressCancel: _handleLongPressCancel, - behavior: HitTestBehavior.opaque, - child: const ColoredBox(color: Colors.transparent), + child: Semantics( + button: true, + label: _showControls + ? t.videoControls.hidePlaybackControls + : t.videoControls.showPlaybackControls, + onTap: _toggleControlsFromSemantics, + child: GestureDetector( + excludeFromSemantics: true, + onTap: _handleOuterTap, + onLongPressStart: (_) => _handleLongPressStart(), + onLongPressEnd: (_) => _handleLongPressEnd(), + onLongPressCancel: _handleLongPressCancel, + behavior: HitTestBehavior.opaque, + child: const ColoredBox(color: Colors.transparent), + ), ), ), // Mobile double-tap zones for skip forward/backward diff --git a/lib/widgets/video_controls/widgets/circular_control_button.dart b/lib/widgets/video_controls/widgets/circular_control_button.dart index 199e128f..ab2e111b 100644 --- a/lib/widgets/video_controls/widgets/circular_control_button.dart +++ b/lib/widgets/video_controls/widgets/circular_control_button.dart @@ -27,6 +27,8 @@ class CircularControlButton extends StatelessWidget { child: Semantics( label: semanticLabel, button: true, + enabled: isEnabled, + onTap: onPressed, excludeSemantics: true, child: IconButton( icon: AppIcon( diff --git a/lib/widgets/video_controls/widgets/content_strip.dart b/lib/widgets/video_controls/widgets/content_strip.dart index 691d70b9..f8cb894f 100644 --- a/lib/widgets/video_controls/widgets/content_strip.dart +++ b/lib/widgets/video_controls/widgets/content_strip.dart @@ -104,6 +104,18 @@ class ContentStripState extends State { if (!identical(oldWidget.player, widget.player) || !identical(oldWidget.chapters, widget.chapters)) { _bindChapterIndexStream(); } + _normalizeActiveTab(); + } + + void _normalizeActiveTab() { + if (_activeTab == _StripTab.chapters && !_hasChapters && _hasQueue) { + _activeTab = _StripTab.queue; + _lastAutoScrolledQueueItemID = null; + _lastAutoScrolledQueueIndex = null; + } else if (_activeTab == _StripTab.queue && !_hasQueue && _hasChapters) { + _activeTab = _StripTab.chapters; + _lastAutoScrolledChapterIndex = null; + } } void _bindChapterIndexStream() { diff --git a/lib/widgets/video_controls/widgets/sync_offset_control.dart b/lib/widgets/video_controls/widgets/sync_offset_control.dart index 8a68d976..ba14126d 100644 --- a/lib/widgets/video_controls/widgets/sync_offset_control.dart +++ b/lib/widgets/video_controls/widgets/sync_offset_control.dart @@ -69,6 +69,7 @@ class _SyncOffsetControlState extends State { late double _currentOffset; late double _confirmedOffset; int _writeGeneration = 0; + int _bindingGeneration = 0; Timer? _longPressTimer; @override @@ -85,6 +86,7 @@ class _SyncOffsetControlState extends State { widget.player != oldWidget.player || widget.propertyName != oldWidget.propertyName) { ++_writeGeneration; + ++_bindingGeneration; _currentOffset = widget.initialOffset.toDouble(); _confirmedOffset = _currentOffset; } @@ -93,6 +95,7 @@ class _SyncOffsetControlState extends State { @override void dispose() { ++_writeGeneration; + ++_bindingGeneration; _longPressTimer?.cancel(); super.dispose(); } @@ -104,16 +107,26 @@ class _SyncOffsetControlState extends State { final coordinator = _syncOffsetWrites[targetPlayer] ??= LatestAsyncWrite(); final writeToken = coordinator.begin(propertyName); final generation = ++_writeGeneration; + final bindingGeneration = _bindingGeneration; unawaited(() async { try { - // Convert milliseconds to seconds for mpv. - final offsetSeconds = offsetMs / 1000.0; - await targetPlayer.setProperty(propertyName, offsetSeconds.toString()); - final committed = await coordinator.commitIfLatest( - propertyName, - writeToken, - () => persistOffset(offsetMs.round()), - ); + final committed = await coordinator.commitIfLatest(propertyName, writeToken, () async { + // Convert milliseconds to seconds for mpv. Keep the native write and + // persistence on the same per-player/property queue so an older + // native write can never complete after a newer one. + final offsetSeconds = offsetMs / 1000.0; + await targetPlayer.setProperty(propertyName, offsetSeconds.toString()); + await persistOffset(offsetMs.round()); + if (mounted && + bindingGeneration == _bindingGeneration && + targetPlayer == widget.player && + propertyName == widget.propertyName) { + // A write may finish successfully after a newer intent was queued. + // Keep it as the rollback baseline without replacing the newer + // optimistic value currently shown by the control. + _confirmedOffset = offsetMs; + } + }); if (!committed || !mounted || generation != _writeGeneration || @@ -121,7 +134,6 @@ class _SyncOffsetControlState extends State { propertyName != widget.propertyName) { return; } - _confirmedOffset = offsetMs; } catch (error, stackTrace) { appLogger.w('Failed to update playback sync offset', error: error, stackTrace: stackTrace); if (!mounted || diff --git a/lib/widgets/video_controls/widgets/track_chapter_controls.dart b/lib/widgets/video_controls/widgets/track_chapter_controls.dart index 235abbbc..72efae64 100644 --- a/lib/widgets/video_controls/widgets/track_chapter_controls.dart +++ b/lib/widgets/video_controls/widgets/track_chapter_controls.dart @@ -9,6 +9,7 @@ import '../../../mpv/mpv.dart'; import '../../../media/media_source_info.dart'; import '../../../services/sleep_timer_service.dart'; import '../../../utils/platform_detector.dart'; +import '../../../utils/quality_preset_labels.dart'; import '../../../i18n/strings.g.dart'; import '../../../widgets/overlay_sheet.dart'; import '../models/track_controls_state.dart'; @@ -18,6 +19,7 @@ import '../sheets/queue_sheet.dart'; import '../sheets/track_sheet.dart'; import '../sheets/video_settings_sheet.dart'; import '../../../services/shader_service.dart'; +import '../../../utils/track_label_builder.dart'; import '../video_control_button.dart'; /// Row of track and chapter control buttons for the video player @@ -154,12 +156,16 @@ class TrackChapterControls extends StatelessWidget { required bool isMobile, required bool isDesktop, String? tooltip, + String? semanticValue, + bool? checked, bool isActive = false, }) { return VideoControlButton( icon: icon, tooltip: tooltip, semanticLabel: semanticLabel, + semanticValue: semanticValue, + checked: checked, isActive: isActive, focusNode: focusNodes != null && focusNodes!.length > buttonIndex ? focusNodes![buttonIndex] : null, onKeyEvent: focusNodes != null @@ -203,7 +209,9 @@ class TrackChapterControls extends StatelessWidget { buttonIndex: 0, icon: Symbols.tune_rounded, isActive: isActive, + checked: isActive, tooltip: t.videoControls.settingsButton, + semanticValue: _versionQualitySemanticValue(), semanticLabel: t.videoControls.settingsButton, isMobile: isMobile, isDesktop: isDesktop, @@ -250,28 +258,36 @@ class TrackChapterControls extends StatelessWidget { // Combined audio & subtitles button { final currentIndex = buttonIndex; - final hasSubtitleControls = trackControlsState.hasSubtitleControls(tracks); - final selectedSub = player.state.track.subtitle; - final hasActiveSubtitle = selectedSub != null && selectedSub.id != 'no'; - final isHidden = hasSubtitleControls && hasActiveSubtitle && !subtitlesVisible; - final icon = hasSubtitleControls - ? (isHidden ? Symbols.subtitles_off_rounded : Symbols.subtitles_rounded) - : Symbols.audiotrack_rounded; buttons.add( - _buildTrackButton( - buttonIndex: currentIndex, - icon: icon, - tooltip: t.videoControls.tracksButton, - semanticLabel: t.videoControls.tracksButton, - isMobile: isMobile, - isDesktop: isDesktop, - onPressed: () { - onCancelAutoHide?.call(); - OverlaySheetController.of(context) - .show( - builder: (_) => TrackSheet(player: player, trackControlsState: trackControlsState), - ) - .whenComplete(() => onStartAutoHide?.call()); + StreamBuilder( + stream: player.streams.track, + initialData: player.state.track, + builder: (context, selectionSnapshot) { + final selection = selectionSnapshot.data ?? player.state.track; + final hasSubtitleControls = trackControlsState.hasSubtitleControls(tracks); + final selectedSub = selection.subtitle; + final hasActiveSubtitle = selectedSub != null && selectedSub.id != SubtitleTrack.off.id; + final isHidden = hasSubtitleControls && hasActiveSubtitle && !subtitlesVisible; + final icon = hasSubtitleControls + ? (isHidden ? Symbols.subtitles_off_rounded : Symbols.subtitles_rounded) + : Symbols.audiotrack_rounded; + return _buildTrackButton( + buttonIndex: currentIndex, + icon: icon, + tooltip: t.videoControls.tracksButton, + semanticLabel: t.videoControls.tracksButton, + semanticValue: _selectionSemanticValue(tracks, selection), + isMobile: isMobile, + isDesktop: isDesktop, + onPressed: () { + onCancelAutoHide?.call(); + OverlaySheetController.of(context) + .show( + builder: (_) => TrackSheet(player: player, trackControlsState: trackControlsState), + ) + .whenComplete(() => onStartAutoHide?.call()); + }, + ); }, ), ); @@ -358,6 +374,7 @@ class TrackChapterControls extends StatelessWidget { icon: _getBoxFitIcon(boxFitMode), tooltip: _getBoxFitTooltip(boxFitMode), semanticLabel: t.videoControls.aspectRatioButton, + semanticValue: _getBoxFitTooltip(boxFitMode), isMobile: isMobile, isDesktop: isDesktop, onPressed: onCycleBoxFitMode, @@ -375,6 +392,7 @@ class TrackChapterControls extends StatelessWidget { icon: isRotationLocked ? Symbols.screen_lock_rotation_rounded : Symbols.screen_rotation_rounded, tooltip: isRotationLocked ? t.videoControls.unlockRotation : t.videoControls.lockRotation, semanticLabel: t.videoControls.rotationLockButton, + checked: isRotationLocked, isMobile: isMobile, isDesktop: isDesktop, onPressed: onToggleRotationLock, @@ -410,6 +428,7 @@ class TrackChapterControls extends StatelessWidget { tooltip: t.videoControls.alwaysOnTopButton, semanticLabel: t.videoControls.alwaysOnTopButton, isActive: isAlwaysOnTop, + checked: isAlwaysOnTop, isMobile: isMobile, isDesktop: isDesktop, onPressed: onToggleAlwaysOnTop, @@ -427,6 +446,7 @@ class TrackChapterControls extends StatelessWidget { icon: isFullscreen ? Symbols.fullscreen_exit_rounded : Symbols.fullscreen_rounded, tooltip: isFullscreen ? t.videoControls.exitFullscreenButton : t.videoControls.fullscreenButton, semanticLabel: isFullscreen ? t.videoControls.exitFullscreenButton : t.videoControls.fullscreenButton, + checked: isFullscreen, isMobile: isMobile, isDesktop: isDesktop, onPressed: onToggleFullscreen, @@ -441,6 +461,62 @@ class TrackChapterControls extends StatelessWidget { ); } + String? _versionQualitySemanticValue() { + final values = []; + if (availableVersions.length > 1) { + final index = selectedMediaIndex; + if (index >= 0 && index < availableVersions.length) { + values.add(availableVersions[index].displayLabel); + } + } + if (serverSupportsTranscoding) { + values.add(qualityPresetLabel(selectedQualityPreset)); + } + return values.isEmpty ? null : values.join(' / '); + } + + String? _selectionSemanticValue(Tracks? tracks, TrackSelection selection) { + final values = []; + final audio = selection.audio; + if (audio != null && audio.id != AudioTrack.off.id) { + final index = tracks?.audio.indexWhere((track) => track.id == audio.id) ?? -1; + final visibleIndex = index < 0 ? 0 : index; + final label = TrackLabelBuilder.audioLabel( + title: audio.title, + language: audio.language, + codec: audio.codec, + channels: audio.channelsCount, + index: visibleIndex, + ); + final fallback = 'Audio Track ${visibleIndex + 1}'; + values.add( + label.primary == fallback + ? _joinTrackLabel(t.audioTracks.track(n: visibleIndex + 1), label.secondary) + : label.joined, + ); + } + + final subtitle = selection.subtitle; + if (subtitle != null && subtitle.id != SubtitleTrack.off.id) { + final index = tracks?.subtitle.indexWhere((track) => track.id == subtitle.id) ?? -1; + values.add( + TrackLabelBuilder.subtitleLabel( + title: subtitle.title, + language: subtitle.language, + codec: subtitle.codec, + forced: subtitle.isForced, + index: index < 0 ? 0 : index, + ).joined, + ); + } + + return values.isEmpty ? null : values.join(', '); + } + + String _joinTrackLabel(String primary, String? secondary) { + return secondary == null ? primary : '$primary ยท $secondary'; + } + /// Calculate total button count for navigation int _getButtonCount(bool isMobile, bool isDesktop) { int count = 1; // Settings button always shown diff --git a/lib/widgets/video_controls/widgets/video_controls_header.dart b/lib/widgets/video_controls/widgets/video_controls_header.dart index 4597459c..1c7060ba 100644 --- a/lib/widgets/video_controls/widgets/video_controls_header.dart +++ b/lib/widgets/video_controls/widgets/video_controls_header.dart @@ -48,11 +48,7 @@ class VideoControlsHeader extends StatelessWidget { final itemTitle = metadata.title ?? t.common.unknown; return Row( children: [ - AppBarBackButton( - style: BackButtonStyle.video, - semanticLabel: t.common.back, - onPressed: onBack ?? () => Navigator.of(context).pop(true), - ), + AppBarBackButton(style: BackButtonStyle.video, onPressed: onBack ?? () => Navigator.of(context).pop(true)), const SizedBox(width: 16), Expanded( child: style == VideoHeaderStyle.singleLine diff --git a/lib/widgets/video_controls/widgets/volume_control.dart b/lib/widgets/video_controls/widgets/volume_control.dart index 45ccd969..4b63e224 100644 --- a/lib/widgets/video_controls/widgets/volume_control.dart +++ b/lib/widgets/video_controls/widgets/volume_control.dart @@ -128,6 +128,8 @@ class _VolumeControlState extends State { final muteButton = Semantics( label: isMuted ? t.videoControls.unmuteButton : t.videoControls.muteButton, button: true, + enabled: true, + onTap: widget.volumeController.toggleMute, excludeSemantics: true, child: IconButton( icon: AppIcon( diff --git a/pubspec.lock b/pubspec.lock index 4c68a051..ba6f39bc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1296,6 +1296,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.7" + unorm_dart: + dependency: "direct main" + description: + name: unorm_dart + sha256: "0c69186b03ca6addab0774bcc0f4f17b88d4ce78d9d4d8f0619e30a99ead58e7" + url: "https://pub.dev" + source: hosted + version: "0.3.2" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c75fbfd5..f507dafc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -81,6 +81,7 @@ dependencies: freezed_annotation: ^3.1.0 xml: ^6.6.1 string_similarity: ^2.2.0 + unorm_dart: ^0.3.2 dev_dependencies: flutter_test: diff --git a/scripts/check_codegen.py b/scripts/check_codegen.py index e2582891..e6103daf 100755 --- a/scripts/check_codegen.py +++ b/scripts/check_codegen.py @@ -10,7 +10,12 @@ import sys import tempfile from pathlib import Path -EXPLICIT_GENERATED = {"server/relay_protocol_gen.go"} +EXPLICIT_GENERATED = { + "lib/data/ducet_order.dart", + "lib/data/hid_key_labels.dart", + "lib/data/iso_639_data.dart", + "server/relay_protocol_gen.go", +} DEPENDENCY_STATE = ("package_config.json", "package_graph.json") @@ -95,7 +100,7 @@ def _dirty_generated_paths(root: Path) -> set[str]: "-z", "--", "lib", - "server/relay_protocol_gen.go", + *EXPLICIT_GENERATED, ).stdout ) return {relative for relative in changed + untracked if _is_generated(relative)} diff --git a/scripts/codegen.sh b/scripts/codegen.sh index 0424944b..24b99cc8 100755 --- a/scripts/codegen.sh +++ b/scripts/codegen.sh @@ -7,6 +7,9 @@ if [[ "${1:-}" == "--check" ]]; then exec python3 scripts/check_codegen.py "$@" fi +dart run scripts/generate_ducet_ranks.dart +dart run scripts/generate_hid_key_labels.dart +dart run scripts/generate_iso_639_data.dart python3 scripts/generate_relay_protocol.py dart run slang dart run build_runner build --delete-conflicting-outputs "$@" diff --git a/scripts/data/FractionalUCA-651afecf9ccf1541a49306993e8210fa2209aa0b.txt.gz b/scripts/data/FractionalUCA-651afecf9ccf1541a49306993e8210fa2209aa0b.txt.gz new file mode 100644 index 00000000..a0c9cce8 Binary files /dev/null and b/scripts/data/FractionalUCA-651afecf9ccf1541a49306993e8210fa2209aa0b.txt.gz differ diff --git a/scripts/data/allkeys-13.0.0.txt.gz b/scripts/data/allkeys-13.0.0.txt.gz new file mode 100644 index 00000000..ddb39691 Binary files /dev/null and b/scripts/data/allkeys-13.0.0.txt.gz differ diff --git a/scripts/data/hid_key_labels.json b/scripts/data/hid_key_labels.json new file mode 100644 index 00000000..a89dba4d --- /dev/null +++ b/scripts/data/hid_key_labels.json @@ -0,0 +1,1331 @@ +{ + "schemaVersion": 1, + "description": "Plezy's curated physical-key compatibility labels, grouped for generated output. This is not a complete USB HID usage registry.", + "groups": [ + { + "name": "Special", + "keys": [ + { + "id": "00000010", + "label": "Hyper" + }, + { + "id": "00000011", + "label": "Super" + }, + { + "id": "00000012", + "label": "Fn" + }, + { + "id": "00000013", + "label": "Fn Lock" + }, + { + "id": "00000014", + "label": "Suspend" + }, + { + "id": "00000015", + "label": "Resume" + }, + { + "id": "00000016", + "label": "Turbo" + }, + { + "id": "00000017", + "label": "Privacy Screen Toggle" + }, + { + "id": "00000018", + "label": "Microphone Mute" + } + ] + }, + { + "name": "Generic Desktop", + "keys": [ + { + "id": "00010082", + "label": "Sleep" + }, + { + "id": "00010083", + "label": "Wake Up" + }, + { + "id": "000100b5", + "label": "Display Toggle" + } + ] + }, + { + "name": "Game Buttons", + "keys": [ + { + "id": "0005ff01", + "label": "Game Button 1" + }, + { + "id": "0005ff02", + "label": "Game Button 2" + }, + { + "id": "0005ff03", + "label": "Game Button 3" + }, + { + "id": "0005ff04", + "label": "Game Button 4" + }, + { + "id": "0005ff05", + "label": "Game Button 5" + }, + { + "id": "0005ff06", + "label": "Game Button 6" + }, + { + "id": "0005ff07", + "label": "Game Button 7" + }, + { + "id": "0005ff08", + "label": "Game Button 8" + }, + { + "id": "0005ff09", + "label": "Game Button 9" + }, + { + "id": "0005ff0a", + "label": "Game Button 10" + }, + { + "id": "0005ff0b", + "label": "Game Button 11" + }, + { + "id": "0005ff0c", + "label": "Game Button 12" + }, + { + "id": "0005ff0d", + "label": "Game Button 13" + }, + { + "id": "0005ff0e", + "label": "Game Button 14" + }, + { + "id": "0005ff0f", + "label": "Game Button 15" + }, + { + "id": "0005ff10", + "label": "Game Button 16" + }, + { + "id": "0005ff11", + "label": "Game Button A" + }, + { + "id": "0005ff12", + "label": "Game Button B" + }, + { + "id": "0005ff13", + "label": "Game Button C" + }, + { + "id": "0005ff14", + "label": "Game Button Left 1" + }, + { + "id": "0005ff15", + "label": "Game Button Left 2" + }, + { + "id": "0005ff16", + "label": "Game Button Mode" + }, + { + "id": "0005ff17", + "label": "Game Button Right 1" + }, + { + "id": "0005ff18", + "label": "Game Button Right 2" + }, + { + "id": "0005ff19", + "label": "Game Button Select" + }, + { + "id": "0005ff1a", + "label": "Game Button Start" + }, + { + "id": "0005ff1b", + "label": "Game Button Thumb Left" + }, + { + "id": "0005ff1c", + "label": "Game Button Thumb Right" + }, + { + "id": "0005ff1d", + "label": "Game Button X" + }, + { + "id": "0005ff1e", + "label": "Game Button Y" + }, + { + "id": "0005ff1f", + "label": "Game Button Z" + } + ] + }, + { + "name": "Keyboard/Keypad", + "keys": [ + { + "id": "00070004", + "label": "A" + }, + { + "id": "00070005", + "label": "B" + }, + { + "id": "00070006", + "label": "C" + }, + { + "id": "00070007", + "label": "D" + }, + { + "id": "00070008", + "label": "E" + }, + { + "id": "00070009", + "label": "F" + }, + { + "id": "0007000a", + "label": "G" + }, + { + "id": "0007000b", + "label": "H" + }, + { + "id": "0007000c", + "label": "I" + }, + { + "id": "0007000d", + "label": "J" + }, + { + "id": "0007000e", + "label": "K" + }, + { + "id": "0007000f", + "label": "L" + }, + { + "id": "00070010", + "label": "M" + }, + { + "id": "00070011", + "label": "N" + }, + { + "id": "00070012", + "label": "O" + }, + { + "id": "00070013", + "label": "P" + }, + { + "id": "00070014", + "label": "Q" + }, + { + "id": "00070015", + "label": "R" + }, + { + "id": "00070016", + "label": "S" + }, + { + "id": "00070017", + "label": "T" + }, + { + "id": "00070018", + "label": "U" + }, + { + "id": "00070019", + "label": "V" + }, + { + "id": "0007001a", + "label": "W" + }, + { + "id": "0007001b", + "label": "X" + }, + { + "id": "0007001c", + "label": "Y" + }, + { + "id": "0007001d", + "label": "Z" + }, + { + "id": "0007001e", + "label": "1" + }, + { + "id": "0007001f", + "label": "2" + }, + { + "id": "00070020", + "label": "3" + }, + { + "id": "00070021", + "label": "4" + }, + { + "id": "00070022", + "label": "5" + }, + { + "id": "00070023", + "label": "6" + }, + { + "id": "00070024", + "label": "7" + }, + { + "id": "00070025", + "label": "8" + }, + { + "id": "00070026", + "label": "9" + }, + { + "id": "00070027", + "label": "0" + }, + { + "id": "00070028", + "label": "Enter" + }, + { + "id": "00070029", + "label": "Escape" + }, + { + "id": "0007002a", + "label": "Backspace" + }, + { + "id": "0007002b", + "label": "Tab" + }, + { + "id": "0007002c", + "label": "Space" + }, + { + "id": "0007002d", + "label": "-" + }, + { + "id": "0007002e", + "label": "=" + }, + { + "id": "0007002f", + "label": "[" + }, + { + "id": "00070030", + "label": "]" + }, + { + "id": "00070031", + "label": "\\" + }, + { + "id": "00070033", + "label": ";" + }, + { + "id": "00070034", + "label": "'" + }, + { + "id": "00070035", + "label": "`" + }, + { + "id": "00070036", + "label": "," + }, + { + "id": "00070037", + "label": "." + }, + { + "id": "00070038", + "label": "/" + }, + { + "id": "00070039", + "label": "Caps Lock" + }, + { + "id": "0007003a", + "label": "F1" + }, + { + "id": "0007003b", + "label": "F2" + }, + { + "id": "0007003c", + "label": "F3" + }, + { + "id": "0007003d", + "label": "F4" + }, + { + "id": "0007003e", + "label": "F5" + }, + { + "id": "0007003f", + "label": "F6" + }, + { + "id": "00070040", + "label": "F7" + }, + { + "id": "00070041", + "label": "F8" + }, + { + "id": "00070042", + "label": "F9" + }, + { + "id": "00070043", + "label": "F10" + }, + { + "id": "00070044", + "label": "F11" + }, + { + "id": "00070045", + "label": "F12" + }, + { + "id": "00070046", + "label": "Print Screen" + }, + { + "id": "00070047", + "label": "Scroll Lock" + }, + { + "id": "00070048", + "label": "Pause" + }, + { + "id": "00070049", + "label": "Insert" + }, + { + "id": "0007004a", + "label": "Home" + }, + { + "id": "0007004b", + "label": "Page Up" + }, + { + "id": "0007004c", + "label": "Delete" + }, + { + "id": "0007004d", + "label": "End" + }, + { + "id": "0007004e", + "label": "Page Down" + }, + { + "id": "0007004f", + "label": "Arrow Right" + }, + { + "id": "00070050", + "label": "Arrow Left" + }, + { + "id": "00070051", + "label": "Arrow Down" + }, + { + "id": "00070052", + "label": "Arrow Up" + }, + { + "id": "00070053", + "label": "Num Lock" + }, + { + "id": "00070054", + "label": "Numpad /" + }, + { + "id": "00070055", + "label": "Numpad *" + }, + { + "id": "00070056", + "label": "Numpad -" + }, + { + "id": "00070057", + "label": "Numpad +" + }, + { + "id": "00070058", + "label": "Numpad Enter" + }, + { + "id": "00070059", + "label": "Numpad 1" + }, + { + "id": "0007005a", + "label": "Numpad 2" + }, + { + "id": "0007005b", + "label": "Numpad 3" + }, + { + "id": "0007005c", + "label": "Numpad 4" + }, + { + "id": "0007005d", + "label": "Numpad 5" + }, + { + "id": "0007005e", + "label": "Numpad 6" + }, + { + "id": "0007005f", + "label": "Numpad 7" + }, + { + "id": "00070060", + "label": "Numpad 8" + }, + { + "id": "00070061", + "label": "Numpad 9" + }, + { + "id": "00070062", + "label": "Numpad 0" + }, + { + "id": "00070063", + "label": "Numpad ." + }, + { + "id": "00070064", + "label": "Intl Backslash" + }, + { + "id": "00070065", + "label": "Context Menu" + }, + { + "id": "00070066", + "label": "Power" + }, + { + "id": "00070067", + "label": "Numpad =" + }, + { + "id": "00070068", + "label": "F13" + }, + { + "id": "00070069", + "label": "F14" + }, + { + "id": "0007006a", + "label": "F15" + }, + { + "id": "0007006b", + "label": "F16" + }, + { + "id": "0007006c", + "label": "F17" + }, + { + "id": "0007006d", + "label": "F18" + }, + { + "id": "0007006e", + "label": "F19" + }, + { + "id": "0007006f", + "label": "F20" + }, + { + "id": "00070070", + "label": "F21" + }, + { + "id": "00070071", + "label": "F22" + }, + { + "id": "00070072", + "label": "F23" + }, + { + "id": "00070073", + "label": "F24" + }, + { + "id": "00070074", + "label": "Open" + }, + { + "id": "00070075", + "label": "Help" + }, + { + "id": "00070077", + "label": "Select" + }, + { + "id": "00070079", + "label": "Again" + }, + { + "id": "0007007a", + "label": "Undo" + }, + { + "id": "0007007b", + "label": "Cut" + }, + { + "id": "0007007c", + "label": "Copy" + }, + { + "id": "0007007d", + "label": "Paste" + }, + { + "id": "0007007e", + "label": "Find" + }, + { + "id": "0007007f", + "label": "Volume Mute" + }, + { + "id": "00070080", + "label": "Volume Up" + }, + { + "id": "00070081", + "label": "Volume Down" + }, + { + "id": "00070085", + "label": "Numpad ," + }, + { + "id": "00070087", + "label": "Intl Ro" + }, + { + "id": "00070088", + "label": "Kana Mode" + }, + { + "id": "00070089", + "label": "Intl Yen" + }, + { + "id": "0007008a", + "label": "Convert" + }, + { + "id": "0007008b", + "label": "Non Convert" + }, + { + "id": "00070090", + "label": "Lang 1" + }, + { + "id": "00070091", + "label": "Lang 2" + }, + { + "id": "00070092", + "label": "Lang 3" + }, + { + "id": "00070093", + "label": "Lang 4" + }, + { + "id": "00070094", + "label": "Lang 5" + }, + { + "id": "0007009b", + "label": "Abort" + }, + { + "id": "000700a3", + "label": "Props" + }, + { + "id": "000700b6", + "label": "Numpad (" + }, + { + "id": "000700b7", + "label": "Numpad )" + }, + { + "id": "000700bb", + "label": "Numpad Backspace" + }, + { + "id": "000700d0", + "label": "Numpad Memory Store" + }, + { + "id": "000700d1", + "label": "Numpad Memory Recall" + }, + { + "id": "000700d2", + "label": "Numpad Memory Clear" + }, + { + "id": "000700d3", + "label": "Numpad Memory Add" + }, + { + "id": "000700d4", + "label": "Numpad Memory Subtract" + }, + { + "id": "000700d7", + "label": "Numpad Sign Change" + }, + { + "id": "000700d8", + "label": "Numpad Clear" + }, + { + "id": "000700d9", + "label": "Numpad Clear Entry" + }, + { + "id": "000700e0", + "label": "Ctrl" + }, + { + "id": "000700e1", + "label": "Shift" + }, + { + "id": "000700e2", + "label": "Alt" + }, + { + "id": "000700e3", + "label": "Meta" + }, + { + "id": "000700e4", + "label": "Ctrl" + }, + { + "id": "000700e5", + "label": "Shift" + }, + { + "id": "000700e6", + "label": "Alt" + }, + { + "id": "000700e7", + "label": "Meta" + } + ] + }, + { + "name": "Consumer", + "keys": [ + { + "id": "000c0030", + "label": "Power" + }, + { + "id": "000c0032", + "label": "Sleep" + }, + { + "id": "000c0040", + "label": "Menu" + }, + { + "id": "000c0041", + "label": "Menu Pick" + }, + { + "id": "000c0042", + "label": "Menu Up" + }, + { + "id": "000c0043", + "label": "Menu Down" + }, + { + "id": "000c0044", + "label": "Menu Left" + }, + { + "id": "000c0045", + "label": "Menu Right" + }, + { + "id": "000c0046", + "label": "Menu Escape" + }, + { + "id": "000c0047", + "label": "Menu Value Increase" + }, + { + "id": "000c0048", + "label": "Menu Value Decrease" + }, + { + "id": "000c0060", + "label": "Info" + }, + { + "id": "000c0061", + "label": "Closed Caption" + }, + { + "id": "000c0062", + "label": "Closed Caption Select" + }, + { + "id": "000c0063", + "label": "VCR/TV" + }, + { + "id": "000c0064", + "label": "Broadcast Mode" + }, + { + "id": "000c0065", + "label": "Snapshot" + }, + { + "id": "000c0066", + "label": "Still" + }, + { + "id": "000c0067", + "label": "PiP Toggle" + }, + { + "id": "000c0068", + "label": "PiP Swap" + }, + { + "id": "000c0069", + "label": "Red Button" + }, + { + "id": "000c006a", + "label": "Green Button" + }, + { + "id": "000c006b", + "label": "Blue Button" + }, + { + "id": "000c006c", + "label": "Yellow Button" + }, + { + "id": "000c006d", + "label": "Aspect" + }, + { + "id": "000c006e", + "label": "3D Mode Select" + }, + { + "id": "000c006f", + "label": "Brightness Up" + }, + { + "id": "000c0070", + "label": "Brightness Down" + }, + { + "id": "000c0072", + "label": "Brightness Toggle" + }, + { + "id": "000c0073", + "label": "Brightness Min" + }, + { + "id": "000c0074", + "label": "Brightness Max" + }, + { + "id": "000c0075", + "label": "Brightness Auto" + }, + { + "id": "000c0079", + "label": "Kbd Illum Up" + }, + { + "id": "000c007a", + "label": "Kbd Illum Down" + }, + { + "id": "000c0082", + "label": "Mode Step" + }, + { + "id": "000c0083", + "label": "Recall Last" + }, + { + "id": "000c0084", + "label": "Enter Channel" + }, + { + "id": "000c0085", + "label": "Order Movie" + }, + { + "id": "000c0086", + "label": "Channel" + }, + { + "id": "000c0088", + "label": "Media Computer" + }, + { + "id": "000c0089", + "label": "Media TV" + }, + { + "id": "000c008a", + "label": "Media WWW" + }, + { + "id": "000c008b", + "label": "Media DVD" + }, + { + "id": "000c008c", + "label": "Media Telephone" + }, + { + "id": "000c008d", + "label": "Program Guide" + }, + { + "id": "000c008e", + "label": "Media Video Phone" + }, + { + "id": "000c008f", + "label": "Media Games" + }, + { + "id": "000c0090", + "label": "Media Messages" + }, + { + "id": "000c0091", + "label": "Media CD" + }, + { + "id": "000c0092", + "label": "Media VCR" + }, + { + "id": "000c0093", + "label": "Media Tuner" + }, + { + "id": "000c0094", + "label": "Quit" + }, + { + "id": "000c0095", + "label": "Help" + }, + { + "id": "000c0096", + "label": "Media Tape" + }, + { + "id": "000c0097", + "label": "Media Cable" + }, + { + "id": "000c0098", + "label": "Media Satellite" + }, + { + "id": "000c0099", + "label": "Media Security" + }, + { + "id": "000c009a", + "label": "Media Home" + }, + { + "id": "000c009b", + "label": "Media Call" + }, + { + "id": "000c009c", + "label": "Channel Up" + }, + { + "id": "000c009d", + "label": "Channel Down" + }, + { + "id": "000c009e", + "label": "Media SAP" + }, + { + "id": "000c00b0", + "label": "Play" + }, + { + "id": "000c00b1", + "label": "Pause" + }, + { + "id": "000c00b2", + "label": "Record" + }, + { + "id": "000c00b3", + "label": "Fast Forward" + }, + { + "id": "000c00b4", + "label": "Rewind" + }, + { + "id": "000c00b5", + "label": "Next Track" + }, + { + "id": "000c00b6", + "label": "Previous Track" + }, + { + "id": "000c00b7", + "label": "Stop" + }, + { + "id": "000c00b8", + "label": "Eject" + }, + { + "id": "000c00b9", + "label": "Random Play" + }, + { + "id": "000c00ba", + "label": "Select Disc" + }, + { + "id": "000c00bb", + "label": "Enter Disc" + }, + { + "id": "000c00bc", + "label": "Repeat" + }, + { + "id": "000c00c0", + "label": "Frame Forward" + }, + { + "id": "000c00c1", + "label": "Frame Back" + }, + { + "id": "000c00cc", + "label": "Stop/Eject" + }, + { + "id": "000c00cd", + "label": "Play/Pause" + }, + { + "id": "000c00ce", + "label": "Play/Skip" + }, + { + "id": "000c00cf", + "label": "Voice Command" + }, + { + "id": "000c00e0", + "label": "Volume" + }, + { + "id": "000c00e2", + "label": "Mute" + }, + { + "id": "000c00e5", + "label": "Bass Boost" + }, + { + "id": "000c00e6", + "label": "Surround Mode" + }, + { + "id": "000c00e7", + "label": "Loudness" + }, + { + "id": "000c00e9", + "label": "Volume Up" + }, + { + "id": "000c00ea", + "label": "Volume Down" + }, + { + "id": "000c0183", + "label": "Media Select" + }, + { + "id": "000c0184", + "label": "Launch Word Processor" + }, + { + "id": "000c0186", + "label": "Launch Spreadsheet" + }, + { + "id": "000c018a", + "label": "Launch Mail" + }, + { + "id": "000c018d", + "label": "Launch Contacts" + }, + { + "id": "000c018e", + "label": "Launch Calendar" + }, + { + "id": "000c0192", + "label": "Launch Calculator" + }, + { + "id": "000c0194", + "label": "Launch File Browser" + }, + { + "id": "000c0196", + "label": "Launch Browser" + }, + { + "id": "000c019c", + "label": "Log Off" + }, + { + "id": "000c019e", + "label": "Lock Screen" + }, + { + "id": "000c019f", + "label": "Launch Control Panel" + }, + { + "id": "000c01a2", + "label": "Select Task" + }, + { + "id": "000c01a7", + "label": "Launch Documents" + }, + { + "id": "000c01ab", + "label": "Spell Check" + }, + { + "id": "000c01ae", + "label": "Launch Keyboard Layout" + }, + { + "id": "000c01b1", + "label": "Launch Screen Saver" + }, + { + "id": "000c01b7", + "label": "Launch Audio Browser" + }, + { + "id": "000c01cb", + "label": "Launch Assistant" + }, + { + "id": "000c0201", + "label": "New" + }, + { + "id": "000c0203", + "label": "Close" + }, + { + "id": "000c0207", + "label": "Save" + }, + { + "id": "000c0208", + "label": "Print" + }, + { + "id": "000c0221", + "label": "Browser Search" + }, + { + "id": "000c0223", + "label": "Browser Home" + }, + { + "id": "000c0224", + "label": "Browser Back" + }, + { + "id": "000c0225", + "label": "Browser Forward" + }, + { + "id": "000c0226", + "label": "Browser Stop" + }, + { + "id": "000c0227", + "label": "Browser Refresh" + }, + { + "id": "000c022a", + "label": "Browser Favorites" + }, + { + "id": "000c022d", + "label": "Zoom In" + }, + { + "id": "000c022e", + "label": "Zoom Out" + }, + { + "id": "000c0232", + "label": "Zoom Toggle" + }, + { + "id": "000c0279", + "label": "Redo" + }, + { + "id": "000c0289", + "label": "Mail Reply" + }, + { + "id": "000c028b", + "label": "Mail Forward" + }, + { + "id": "000c028c", + "label": "Mail Send" + }, + { + "id": "000c029d", + "label": "Keyboard Layout Select" + }, + { + "id": "000c029f", + "label": "Show All Windows" + } + ] + } + ] +} diff --git a/scripts/data/iso_639_codes.json b/scripts/data/iso_639_codes.json new file mode 100644 index 00000000..8b5659b6 --- /dev/null +++ b/scripts/data/iso_639_codes.json @@ -0,0 +1,1110 @@ +{ + "schemaVersion": 1, + "description": "Plezy's curated ISO 639 compatibility catalog, preserved from the application's original generated table. This is not a complete ISO registry.", + "languages": { + "aa": { + "primary": "aa", + "terminology": "aar", + "bibliographic": null, + "name": "Afar" + }, + "ab": { + "primary": "ab", + "terminology": "abk", + "bibliographic": null, + "name": "Abkhaz" + }, + "ae": { + "primary": "ae", + "terminology": "ave", + "bibliographic": null, + "name": "Avestan" + }, + "af": { + "primary": "af", + "terminology": "afr", + "bibliographic": null, + "name": "Afrikaans" + }, + "ak": { + "primary": "ak", + "terminology": "aka", + "bibliographic": null, + "name": "Akan" + }, + "am": { + "primary": "am", + "terminology": "amh", + "bibliographic": null, + "name": "Amharic" + }, + "an": { + "primary": "an", + "terminology": "arg", + "bibliographic": null, + "name": "Aragonese" + }, + "ar": { + "primary": "ar", + "terminology": "ara", + "bibliographic": null, + "name": "Arabic" + }, + "as": { + "primary": "as", + "terminology": "asm", + "bibliographic": null, + "name": "Assamese" + }, + "av": { + "primary": "av", + "terminology": "ava", + "bibliographic": null, + "name": "Avaric" + }, + "ay": { + "primary": "ay", + "terminology": "aym", + "bibliographic": null, + "name": "Aymara" + }, + "az": { + "primary": "az", + "terminology": "aze", + "bibliographic": null, + "name": "Azerbaijani" + }, + "ba": { + "primary": "ba", + "terminology": "bak", + "bibliographic": null, + "name": "Bashkir" + }, + "be": { + "primary": "be", + "terminology": "bel", + "bibliographic": null, + "name": "Belarusian" + }, + "bg": { + "primary": "bg", + "terminology": "bul", + "bibliographic": null, + "name": "Bulgarian" + }, + "bh": { + "primary": "bh", + "terminology": "bih", + "bibliographic": null, + "name": "Bihari" + }, + "bi": { + "primary": "bi", + "terminology": "bis", + "bibliographic": null, + "name": "Bislama" + }, + "bm": { + "primary": "bm", + "terminology": "bam", + "bibliographic": null, + "name": "Bambara" + }, + "bn": { + "primary": "bn", + "terminology": "ben", + "bibliographic": null, + "name": "Bengali, Bangla" + }, + "bo": { + "primary": "bo", + "terminology": "bod", + "bibliographic": "tib", + "name": "Tibetan Standard, Tibetan, Central" + }, + "br": { + "primary": "br", + "terminology": "bre", + "bibliographic": null, + "name": "Breton" + }, + "bs": { + "primary": "bs", + "terminology": "bos", + "bibliographic": null, + "name": "Bosnian" + }, + "ca": { + "primary": "ca", + "terminology": "cat", + "bibliographic": null, + "name": "Catalan" + }, + "ce": { + "primary": "ce", + "terminology": "che", + "bibliographic": null, + "name": "Chechen" + }, + "ch": { + "primary": "ch", + "terminology": "cha", + "bibliographic": null, + "name": "Chamorro" + }, + "co": { + "primary": "co", + "terminology": "cos", + "bibliographic": null, + "name": "Corsican" + }, + "cr": { + "primary": "cr", + "terminology": "cre", + "bibliographic": null, + "name": "Cree" + }, + "cs": { + "primary": "cs", + "terminology": "ces", + "bibliographic": "cze", + "name": "Czech" + }, + "cu": { + "primary": "cu", + "terminology": "chu", + "bibliographic": null, + "name": "Old Church Slavonic, Church Slavonic, Old Bulgarian" + }, + "cv": { + "primary": "cv", + "terminology": "chv", + "bibliographic": null, + "name": "Chuvash" + }, + "cy": { + "primary": "cy", + "terminology": "cym", + "bibliographic": "wel", + "name": "Welsh" + }, + "da": { + "primary": "da", + "terminology": "dan", + "bibliographic": null, + "name": "Danish" + }, + "de": { + "primary": "de", + "terminology": "deu", + "bibliographic": "ger", + "name": "German" + }, + "dv": { + "primary": "dv", + "terminology": "div", + "bibliographic": null, + "name": "Divehi, Dhivehi, Maldivian" + }, + "dz": { + "primary": "dz", + "terminology": "dzo", + "bibliographic": null, + "name": "Dzongkha" + }, + "ee": { + "primary": "ee", + "terminology": "ewe", + "bibliographic": null, + "name": "Ewe" + }, + "el": { + "primary": "el", + "terminology": "ell", + "bibliographic": "gre", + "name": "Greek (modern)" + }, + "en": { + "primary": "en", + "terminology": "eng", + "bibliographic": null, + "name": "English" + }, + "eo": { + "primary": "eo", + "terminology": "epo", + "bibliographic": null, + "name": "Esperanto" + }, + "es": { + "primary": "es", + "terminology": "spa", + "bibliographic": null, + "name": "Spanish" + }, + "et": { + "primary": "et", + "terminology": "est", + "bibliographic": null, + "name": "Estonian" + }, + "eu": { + "primary": "eu", + "terminology": "eus", + "bibliographic": "baq", + "name": "Basque" + }, + "fa": { + "primary": "fa", + "terminology": "fas", + "bibliographic": "per", + "name": "Persian (Farsi)" + }, + "ff": { + "primary": "ff", + "terminology": "ful", + "bibliographic": null, + "name": "Fula, Fulah, Pulaar, Pular" + }, + "fi": { + "primary": "fi", + "terminology": "fin", + "bibliographic": null, + "name": "Finnish" + }, + "fj": { + "primary": "fj", + "terminology": "fij", + "bibliographic": null, + "name": "Fijian" + }, + "fo": { + "primary": "fo", + "terminology": "fao", + "bibliographic": null, + "name": "Faroese" + }, + "fr": { + "primary": "fr", + "terminology": "fra", + "bibliographic": "fre", + "name": "French" + }, + "fy": { + "primary": "fy", + "terminology": "fry", + "bibliographic": null, + "name": "Western Frisian" + }, + "ga": { + "primary": "ga", + "terminology": "gle", + "bibliographic": null, + "name": "Irish" + }, + "gd": { + "primary": "gd", + "terminology": "gla", + "bibliographic": null, + "name": "Scottish Gaelic, Gaelic" + }, + "gl": { + "primary": "gl", + "terminology": "glg", + "bibliographic": null, + "name": "Galician" + }, + "gn": { + "primary": "gn", + "terminology": "grn", + "bibliographic": null, + "name": "Guaranรญ" + }, + "gu": { + "primary": "gu", + "terminology": "guj", + "bibliographic": null, + "name": "Gujarati" + }, + "gv": { + "primary": "gv", + "terminology": "glv", + "bibliographic": null, + "name": "Manx" + }, + "ha": { + "primary": "ha", + "terminology": "hau", + "bibliographic": null, + "name": "Hausa" + }, + "he": { + "primary": "he", + "terminology": "heb", + "bibliographic": null, + "name": "Hebrew (modern)" + }, + "hi": { + "primary": "hi", + "terminology": "hin", + "bibliographic": null, + "name": "Hindi" + }, + "ho": { + "primary": "ho", + "terminology": "hmo", + "bibliographic": null, + "name": "Hiri Motu" + }, + "hr": { + "primary": "hr", + "terminology": "hrv", + "bibliographic": null, + "name": "Croatian" + }, + "ht": { + "primary": "ht", + "terminology": "hat", + "bibliographic": null, + "name": "Haitian, Haitian Creole" + }, + "hu": { + "primary": "hu", + "terminology": "hun", + "bibliographic": null, + "name": "Hungarian" + }, + "hy": { + "primary": "hy", + "terminology": "hye", + "bibliographic": "arm", + "name": "Armenian" + }, + "hz": { + "primary": "hz", + "terminology": "her", + "bibliographic": null, + "name": "Herero" + }, + "ia": { + "primary": "ia", + "terminology": "ina", + "bibliographic": null, + "name": "Interlingua" + }, + "id": { + "primary": "id", + "terminology": "ind", + "bibliographic": null, + "name": "Indonesian" + }, + "ie": { + "primary": "ie", + "terminology": "ile", + "bibliographic": null, + "name": "Interlingue" + }, + "ig": { + "primary": "ig", + "terminology": "ibo", + "bibliographic": null, + "name": "Igbo" + }, + "ii": { + "primary": "ii", + "terminology": "iii", + "bibliographic": null, + "name": "Nuosu" + }, + "ik": { + "primary": "ik", + "terminology": "ipk", + "bibliographic": null, + "name": "Inupiaq" + }, + "io": { + "primary": "io", + "terminology": "ido", + "bibliographic": null, + "name": "Ido" + }, + "is": { + "primary": "is", + "terminology": "isl", + "bibliographic": "ice", + "name": "Icelandic" + }, + "it": { + "primary": "it", + "terminology": "ita", + "bibliographic": null, + "name": "Italian" + }, + "iu": { + "primary": "iu", + "terminology": "iku", + "bibliographic": null, + "name": "Inuktitut" + }, + "ja": { + "primary": "ja", + "terminology": "jpn", + "bibliographic": null, + "name": "Japanese" + }, + "jv": { + "primary": "jv", + "terminology": "jav", + "bibliographic": null, + "name": "Javanese" + }, + "ka": { + "primary": "ka", + "terminology": "kat", + "bibliographic": "geo", + "name": "Georgian" + }, + "kg": { + "primary": "kg", + "terminology": "kon", + "bibliographic": null, + "name": "Kongo" + }, + "ki": { + "primary": "ki", + "terminology": "kik", + "bibliographic": null, + "name": "Kikuyu, Gikuyu" + }, + "kj": { + "primary": "kj", + "terminology": "kua", + "bibliographic": null, + "name": "Kwanyama, Kuanyama" + }, + "kk": { + "primary": "kk", + "terminology": "kaz", + "bibliographic": null, + "name": "Kazakh" + }, + "kl": { + "primary": "kl", + "terminology": "kal", + "bibliographic": null, + "name": "Kalaallisut, Greenlandic" + }, + "km": { + "primary": "km", + "terminology": "khm", + "bibliographic": null, + "name": "Khmer" + }, + "kn": { + "primary": "kn", + "terminology": "kan", + "bibliographic": null, + "name": "Kannada" + }, + "ko": { + "primary": "ko", + "terminology": "kor", + "bibliographic": null, + "name": "Korean" + }, + "kr": { + "primary": "kr", + "terminology": "kau", + "bibliographic": null, + "name": "Kanuri" + }, + "ks": { + "primary": "ks", + "terminology": "kas", + "bibliographic": null, + "name": "Kashmiri" + }, + "ku": { + "primary": "ku", + "terminology": "kur", + "bibliographic": null, + "name": "Kurdish" + }, + "kv": { + "primary": "kv", + "terminology": "kom", + "bibliographic": null, + "name": "Komi" + }, + "kw": { + "primary": "kw", + "terminology": "cor", + "bibliographic": null, + "name": "Cornish" + }, + "ky": { + "primary": "ky", + "terminology": "kir", + "bibliographic": null, + "name": "Kyrgyz" + }, + "la": { + "primary": "la", + "terminology": "lat", + "bibliographic": null, + "name": "Latin" + }, + "lb": { + "primary": "lb", + "terminology": "ltz", + "bibliographic": null, + "name": "Luxembourgish, Letzeburgesch" + }, + "lg": { + "primary": "lg", + "terminology": "lug", + "bibliographic": null, + "name": "Ganda" + }, + "li": { + "primary": "li", + "terminology": "lim", + "bibliographic": null, + "name": "Limburgish, Limburgan, Limburger" + }, + "ln": { + "primary": "ln", + "terminology": "lin", + "bibliographic": null, + "name": "Lingala" + }, + "lo": { + "primary": "lo", + "terminology": "lao", + "bibliographic": null, + "name": "Lao" + }, + "lt": { + "primary": "lt", + "terminology": "lit", + "bibliographic": null, + "name": "Lithuanian" + }, + "lu": { + "primary": "lu", + "terminology": "lub", + "bibliographic": null, + "name": "Luba-Katanga" + }, + "lv": { + "primary": "lv", + "terminology": "lav", + "bibliographic": null, + "name": "Latvian" + }, + "mg": { + "primary": "mg", + "terminology": "mlg", + "bibliographic": null, + "name": "Malagasy" + }, + "mh": { + "primary": "mh", + "terminology": "mah", + "bibliographic": null, + "name": "Marshallese" + }, + "mi": { + "primary": "mi", + "terminology": "mri", + "bibliographic": "mao", + "name": "Mฤori" + }, + "mk": { + "primary": "mk", + "terminology": "mkd", + "bibliographic": "mac", + "name": "Macedonian" + }, + "ml": { + "primary": "ml", + "terminology": "mal", + "bibliographic": null, + "name": "Malayalam" + }, + "mn": { + "primary": "mn", + "terminology": "mon", + "bibliographic": null, + "name": "Mongolian" + }, + "mr": { + "primary": "mr", + "terminology": "mar", + "bibliographic": null, + "name": "Marathi (Marฤแนญhฤซ)" + }, + "ms": { + "primary": "ms", + "terminology": "msa", + "bibliographic": "may", + "name": "Malay" + }, + "mt": { + "primary": "mt", + "terminology": "mlt", + "bibliographic": null, + "name": "Maltese" + }, + "my": { + "primary": "my", + "terminology": "mya", + "bibliographic": "bur", + "name": "Burmese" + }, + "na": { + "primary": "na", + "terminology": "nau", + "bibliographic": null, + "name": "Nauruan" + }, + "nb": { + "primary": "nb", + "terminology": "nob", + "bibliographic": null, + "name": "Norwegian Bokmรฅl" + }, + "nd": { + "primary": "nd", + "terminology": "nde", + "bibliographic": null, + "name": "Northern Ndebele" + }, + "ne": { + "primary": "ne", + "terminology": "nep", + "bibliographic": null, + "name": "Nepali" + }, + "ng": { + "primary": "ng", + "terminology": "ndo", + "bibliographic": null, + "name": "Ndonga" + }, + "nl": { + "primary": "nl", + "terminology": "nld", + "bibliographic": "dut", + "name": "Dutch" + }, + "nn": { + "primary": "nn", + "terminology": "nno", + "bibliographic": null, + "name": "Norwegian Nynorsk" + }, + "no": { + "primary": "no", + "terminology": "nor", + "bibliographic": null, + "name": "Norwegian" + }, + "nr": { + "primary": "nr", + "terminology": "nbl", + "bibliographic": null, + "name": "Southern Ndebele" + }, + "nv": { + "primary": "nv", + "terminology": "nav", + "bibliographic": null, + "name": "Navajo, Navaho" + }, + "ny": { + "primary": "ny", + "terminology": "nya", + "bibliographic": null, + "name": "Chichewa, Chewa, Nyanja" + }, + "oc": { + "primary": "oc", + "terminology": "oci", + "bibliographic": null, + "name": "Occitan" + }, + "oj": { + "primary": "oj", + "terminology": "oji", + "bibliographic": null, + "name": "Ojibwe, Ojibwa" + }, + "om": { + "primary": "om", + "terminology": "orm", + "bibliographic": null, + "name": "Oromo" + }, + "or": { + "primary": "or", + "terminology": "ori", + "bibliographic": null, + "name": "Oriya" + }, + "os": { + "primary": "os", + "terminology": "oss", + "bibliographic": null, + "name": "Ossetian, Ossetic" + }, + "pa": { + "primary": "pa", + "terminology": "pan", + "bibliographic": null, + "name": "(Eastern) Punjabi" + }, + "pi": { + "primary": "pi", + "terminology": "pli", + "bibliographic": null, + "name": "Pฤli" + }, + "pl": { + "primary": "pl", + "terminology": "pol", + "bibliographic": null, + "name": "Polish" + }, + "ps": { + "primary": "ps", + "terminology": "pus", + "bibliographic": null, + "name": "Pashto, Pushto" + }, + "pt": { + "primary": "pt", + "terminology": "por", + "bibliographic": null, + "name": "Portuguese" + }, + "qu": { + "primary": "qu", + "terminology": "que", + "bibliographic": null, + "name": "Quechua" + }, + "rm": { + "primary": "rm", + "terminology": "roh", + "bibliographic": null, + "name": "Romansh" + }, + "rn": { + "primary": "rn", + "terminology": "run", + "bibliographic": null, + "name": "Kirundi" + }, + "ro": { + "primary": "ro", + "terminology": "ron", + "bibliographic": "rum", + "name": "Romanian" + }, + "ru": { + "primary": "ru", + "terminology": "rus", + "bibliographic": null, + "name": "Russian" + }, + "rw": { + "primary": "rw", + "terminology": "kin", + "bibliographic": null, + "name": "Kinyarwanda" + }, + "sa": { + "primary": "sa", + "terminology": "san", + "bibliographic": null, + "name": "Sanskrit (Saแนskแน›ta)" + }, + "sc": { + "primary": "sc", + "terminology": "srd", + "bibliographic": null, + "name": "Sardinian" + }, + "sd": { + "primary": "sd", + "terminology": "snd", + "bibliographic": null, + "name": "Sindhi" + }, + "se": { + "primary": "se", + "terminology": "sme", + "bibliographic": null, + "name": "Northern Sami" + }, + "sg": { + "primary": "sg", + "terminology": "sag", + "bibliographic": null, + "name": "Sango" + }, + "si": { + "primary": "si", + "terminology": "sin", + "bibliographic": null, + "name": "Sinhalese, Sinhala" + }, + "sk": { + "primary": "sk", + "terminology": "slk", + "bibliographic": "slo", + "name": "Slovak" + }, + "sl": { + "primary": "sl", + "terminology": "slv", + "bibliographic": null, + "name": "Slovene" + }, + "sm": { + "primary": "sm", + "terminology": "smo", + "bibliographic": null, + "name": "Samoan" + }, + "sn": { + "primary": "sn", + "terminology": "sna", + "bibliographic": null, + "name": "Shona" + }, + "so": { + "primary": "so", + "terminology": "som", + "bibliographic": null, + "name": "Somali" + }, + "sq": { + "primary": "sq", + "terminology": "sqi", + "bibliographic": "alb", + "name": "Albanian" + }, + "sr": { + "primary": "sr", + "terminology": "srp", + "bibliographic": null, + "name": "Serbian" + }, + "ss": { + "primary": "ss", + "terminology": "ssw", + "bibliographic": null, + "name": "Swati" + }, + "st": { + "primary": "st", + "terminology": "sot", + "bibliographic": null, + "name": "Southern Sotho" + }, + "su": { + "primary": "su", + "terminology": "sun", + "bibliographic": null, + "name": "Sundanese" + }, + "sv": { + "primary": "sv", + "terminology": "swe", + "bibliographic": null, + "name": "Swedish" + }, + "sw": { + "primary": "sw", + "terminology": "swa", + "bibliographic": null, + "name": "Swahili" + }, + "ta": { + "primary": "ta", + "terminology": "tam", + "bibliographic": null, + "name": "Tamil" + }, + "te": { + "primary": "te", + "terminology": "tel", + "bibliographic": null, + "name": "Telugu" + }, + "tg": { + "primary": "tg", + "terminology": "tgk", + "bibliographic": null, + "name": "Tajik" + }, + "th": { + "primary": "th", + "terminology": "tha", + "bibliographic": null, + "name": "Thai" + }, + "ti": { + "primary": "ti", + "terminology": "tir", + "bibliographic": null, + "name": "Tigrinya" + }, + "tk": { + "primary": "tk", + "terminology": "tuk", + "bibliographic": null, + "name": "Turkmen" + }, + "tl": { + "primary": "tl", + "terminology": "tgl", + "bibliographic": null, + "name": "Tagalog" + }, + "tn": { + "primary": "tn", + "terminology": "tsn", + "bibliographic": null, + "name": "Tswana" + }, + "to": { + "primary": "to", + "terminology": "ton", + "bibliographic": null, + "name": "Tonga (Tonga Islands)" + }, + "tr": { + "primary": "tr", + "terminology": "tur", + "bibliographic": null, + "name": "Turkish" + }, + "ts": { + "primary": "ts", + "terminology": "tso", + "bibliographic": null, + "name": "Tsonga" + }, + "tt": { + "primary": "tt", + "terminology": "tat", + "bibliographic": null, + "name": "Tatar" + }, + "tw": { + "primary": "tw", + "terminology": "twi", + "bibliographic": null, + "name": "Twi" + }, + "ty": { + "primary": "ty", + "terminology": "tah", + "bibliographic": null, + "name": "Tahitian" + }, + "ug": { + "primary": "ug", + "terminology": "uig", + "bibliographic": null, + "name": "Uyghur" + }, + "uk": { + "primary": "uk", + "terminology": "ukr", + "bibliographic": null, + "name": "Ukrainian" + }, + "ur": { + "primary": "ur", + "terminology": "urd", + "bibliographic": null, + "name": "Urdu" + }, + "uz": { + "primary": "uz", + "terminology": "uzb", + "bibliographic": null, + "name": "Uzbek" + }, + "ve": { + "primary": "ve", + "terminology": "ven", + "bibliographic": null, + "name": "Venda" + }, + "vi": { + "primary": "vi", + "terminology": "vie", + "bibliographic": null, + "name": "Vietnamese" + }, + "vo": { + "primary": "vo", + "terminology": "vol", + "bibliographic": null, + "name": "Volapรผk" + }, + "wa": { + "primary": "wa", + "terminology": "wln", + "bibliographic": null, + "name": "Walloon" + }, + "wo": { + "primary": "wo", + "terminology": "wol", + "bibliographic": null, + "name": "Wolof" + }, + "xh": { + "primary": "xh", + "terminology": "xho", + "bibliographic": null, + "name": "Xhosa" + }, + "yi": { + "primary": "yi", + "terminology": "yid", + "bibliographic": null, + "name": "Yiddish" + }, + "yo": { + "primary": "yo", + "terminology": "yor", + "bibliographic": null, + "name": "Yoruba" + }, + "za": { + "primary": "za", + "terminology": "zha", + "bibliographic": null, + "name": "Zhuang, Chuang" + }, + "zh": { + "primary": "zh", + "terminology": "zho", + "bibliographic": "chi", + "name": "Chinese" + }, + "zu": { + "primary": "zu", + "terminology": "zul", + "bibliographic": null, + "name": "Zulu" + } + } +} diff --git a/scripts/generate_ducet_ranks.dart b/scripts/generate_ducet_ranks.dart index f3f852a1..620aa39c 100644 --- a/scripts/generate_ducet_ranks.dart +++ b/scripts/generate_ducet_ranks.dart @@ -1,27 +1,242 @@ -/// Generates lib/data/ducet_order.dart from Unicode allkeys.txt and CLDR FractionalUCA.txt. +/// Generates lib/data/ducet_order.dart from pinned Unicode and CLDR data. /// /// Usage: /// dart run scripts/generate_ducet_ranks.dart [allkeys.txt] [FractionalUCA.txt] /// -/// Downloads the files automatically if not provided. +/// Default generation uses tracked deterministic gzip copies of the pinned source bytes. +/// Unicode data is redistributed unmodified under the Unicode License v3. +/// Explicit/cache/download inputs remain available for integrity and HTTP failure testing. library; +import 'dart:async'; import 'dart:io'; -// --------------------------------------------------------------------------- -// Weight tuple for multi-level UCA comparison -// --------------------------------------------------------------------------- +import 'package:crypto/crypto.dart'; + +final class SourceDescriptor { + const SourceDescriptor({ + required this.name, + required this.url, + required this.bundledFileName, + required this.cacheFileName, + required this.sha256Digest, + required this.licenseUrl, + }); + + final String name; + final String url; + final String bundledFileName; + final String cacheFileName; + final String sha256Digest; + final String licenseUrl; +} + +const allKeysSource = SourceDescriptor( + name: 'Unicode 13.0 allkeys', + url: 'https://www.unicode.org/Public/UCA/13.0.0/allkeys.txt', + bundledFileName: 'allkeys-13.0.0.txt.gz', + cacheFileName: 'plezy-allkeys-13.0.0-a3255d45b7af97f4dc14fb8364d7573b434425e5c58cacf00d16901ce081c78d.txt', + sha256Digest: 'a3255d45b7af97f4dc14fb8364d7573b434425e5c58cacf00d16901ce081c78d', + licenseUrl: 'https://www.unicode.org/license.txt', +); + +const fractionalUcaSource = SourceDescriptor( + name: 'CLDR FractionalUCA at 651afecf9ccf1541a49306993e8210fa2209aa0b', + url: + 'https://raw.githubusercontent.com/unicode-org/cldr/651afecf9ccf1541a49306993e8210fa2209aa0b/common/uca/FractionalUCA.txt', + bundledFileName: 'FractionalUCA-651afecf9ccf1541a49306993e8210fa2209aa0b.txt.gz', + cacheFileName: + 'plezy-FractionalUCA-651afecf9ccf1541a49306993e8210fa2209aa0b-' + 'a6144d0c8c19cc899a5d2f48fbc14e3e31e819049a73aa86b67111f1f3f81637.txt', + sha256Digest: 'a6144d0c8c19cc899a5d2f48fbc14e3e31e819049a73aa86b67111f1f3f81637', + licenseUrl: 'https://github.com/unicode-org/cldr/blob/651afecf9ccf1541a49306993e8210fa2209aa0b/LICENSE', +); + +final class SourceDownloadResponse { + const SourceDownloadResponse({required this.statusCode, required this.bytes, this.close}); + + final int statusCode; + final Stream> bytes; + final void Function()? close; +} + +typedef SourceDownloader = Future Function(Uri uri); +typedef GeneratedFileWriter = void Function(File output, String contents); + +final class SourceIntegrityException implements Exception { + const SourceIntegrityException(this.message); + + final String message; + + @override + String toString() => 'SourceIntegrityException: $message'; +} + +final class VerifiedSourceBundle { + VerifiedSourceBundle({ + required this.allKeysFile, + required this.fractionalUcaFile, + required this._stagingDirectory, + required this._stagedCacheFiles, + }); + + final File allKeysFile; + final File fractionalUcaFile; + final Directory _stagingDirectory; + final Map _stagedCacheFiles; + + void commitCaches() { + for (final entry in _stagedCacheFiles.entries) { + final staged = entry.key; + final cache = entry.value; + cache.parent.createSync(recursive: true); + staged.renameSync(cache.path); + } + _stagedCacheFiles.clear(); + } + + void dispose() { + if (_stagingDirectory.existsSync()) { + _stagingDirectory.deleteSync(recursive: true); + } + } +} + +Future downloadSource(Uri uri) async { + final client = HttpClient(); + try { + final request = await client.getUrl(uri); + request.headers.set(HttpHeaders.userAgentHeader, 'Plezy DUCET generator'); + final response = await request.close(); + return SourceDownloadResponse( + statusCode: response.statusCode, + bytes: response, + close: () => client.close(force: true), + ); + } catch (_) { + client.close(force: true); + rethrow; + } +} + +Future sha256ForFile(File file) async => (await sha256.bind(file.openRead()).first).toString(); + +Future verifySourceFile(File file, SourceDescriptor descriptor) async { + if (!await file.exists()) { + throw SourceIntegrityException('${descriptor.name} is missing: ${file.path}'); + } + final actualDigest = await sha256ForFile(file); + if (actualDigest != descriptor.sha256Digest) { + throw SourceIntegrityException( + '${descriptor.name} SHA-256 mismatch: expected ${descriptor.sha256Digest}, got $actualDigest', + ); + } +} + +Future loadBundledSources({ + required Directory bundledSourceDirectory, + SourceDescriptor allKeysDescriptor = allKeysSource, + SourceDescriptor fractionalUcaDescriptor = fractionalUcaSource, +}) async { + final staging = await Directory.systemTemp.createTemp('plezy_ducet_bundled_'); + + Future decompressAndVerify(SourceDescriptor descriptor) async { + final compressed = File.fromUri(bundledSourceDirectory.uri.resolve(descriptor.bundledFileName)); + if (!await compressed.exists()) { + throw SourceIntegrityException('${descriptor.name} bundled source is missing: ${compressed.path}'); + } + final decompressed = File.fromUri(staging.uri.resolve('${descriptor.bundledFileName}.raw')); + try { + await compressed.openRead().transform(gzip.decoder).pipe(decompressed.openWrite()); + } catch (error) { + throw SourceIntegrityException('${descriptor.name} bundled gzip is invalid: $error'); + } + await verifySourceFile(decompressed, descriptor); + return decompressed; + } + + try { + final allKeys = await decompressAndVerify(allKeysDescriptor); + final fractionalUca = await decompressAndVerify(fractionalUcaDescriptor); + return VerifiedSourceBundle( + allKeysFile: allKeys, + fractionalUcaFile: fractionalUca, + stagingDirectory: staging, + stagedCacheFiles: {}, + ); + } catch (_) { + if (await staging.exists()) { + await staging.delete(recursive: true); + } + rethrow; + } +} + +Future loadVerifiedSources({ + File? explicitAllKeys, + File? explicitFractionalUca, + Directory? cacheDirectory, + SourceDownloader downloader = downloadSource, + SourceDescriptor allKeysDescriptor = allKeysSource, + SourceDescriptor fractionalUcaDescriptor = fractionalUcaSource, +}) async { + final cacheRoot = cacheDirectory ?? Directory.systemTemp; + final staging = await Directory.systemTemp.createTemp('plezy_ducet_sources_'); + final stagedCacheFiles = {}; + + Future resolve(SourceDescriptor descriptor, File? explicit) async { + if (explicit != null) { + await verifySourceFile(explicit, descriptor); + return explicit; + } + + final cache = File.fromUri(cacheRoot.uri.resolve(descriptor.cacheFileName)); + if (await cache.exists()) { + await verifySourceFile(cache, descriptor); + return cache; + } + + final staged = File.fromUri(staging.uri.resolve(descriptor.cacheFileName)); + final response = await downloader(Uri.parse(descriptor.url)); + try { + if (response.statusCode != HttpStatus.ok) { + throw SourceIntegrityException('${descriptor.name} download returned HTTP ${response.statusCode}'); + } + await response.bytes.pipe(staged.openWrite()); + } finally { + response.close?.call(); + } + await verifySourceFile(staged, descriptor); + stagedCacheFiles[staged] = cache; + return staged; + } + + try { + final allKeys = await resolve(allKeysDescriptor, explicitAllKeys); + final fractionalUca = await resolve(fractionalUcaDescriptor, explicitFractionalUca); + return VerifiedSourceBundle( + allKeysFile: allKeys, + fractionalUcaFile: fractionalUca, + stagingDirectory: staging, + stagedCacheFiles: stagedCacheFiles, + ); + } catch (_) { + if (await staging.exists()) { + await staging.delete(recursive: true); + } + rethrow; + } +} + class Weight implements Comparable { final List<(int, int, int)> levels; - final int codepoint; // tiebreaker + final int codepoint; const Weight(this.levels, this.codepoint); @override int compareTo(Weight other) { final len = levels.length < other.levels.length ? levels.length : other.levels.length; - - // Primary pass for (var i = 0; i < len; i++) { final cmp = levels[i].$1.compareTo(other.levels[i].$1); if (cmp != 0) return cmp; @@ -29,39 +244,21 @@ class Weight implements Comparable { if (levels.length != other.levels.length) { return levels.length.compareTo(other.levels.length); } - - // Secondary pass for (var i = 0; i < len; i++) { final cmp = levels[i].$2.compareTo(other.levels[i].$2); if (cmp != 0) return cmp; } - - // Tertiary pass for (var i = 0; i < len; i++) { final cmp = levels[i].$3.compareTo(other.levels[i].$3); if (cmp != 0) return cmp; } - - // Codepoint tiebreaker return codepoint.compareTo(other.codepoint); } } -// --------------------------------------------------------------------------- -// Katakana codepoint ranges (for CLDR kana tertiary reversal) -// --------------------------------------------------------------------------- bool _isKatakana(int cp) => - (cp >= 0x30A0 && cp <= 0x30FF) || // Katakana - (cp >= 0x31F0 && cp <= 0x31FF) || // Katakana Phonetic Extensions - (cp >= 0xFF65 && cp <= 0xFF9F); // Halfwidth Katakana + (cp >= 0x30A0 && cp <= 0x30FF) || (cp >= 0x31F0 && cp <= 0x31FF) || (cp >= 0xFF65 && cp <= 0xFF9F); -// --------------------------------------------------------------------------- -// Parse allkeys.txt โ†’ Map -// -// CLDR kana fix: ICU/CLDR root collation sorts katakana before hiragana -// (opposite of raw DUCET). We adjust by subtracting 6 from katakana tertiary -// weights, placing them below hiragana tertiaries (0x000D+). -// --------------------------------------------------------------------------- Map parseAllKeys(String text) { final result = {}; final weightRe = RegExp(r'\[([.*])([0-9A-Fa-f]{4})\.([0-9A-Fa-f]{4})\.([0-9A-Fa-f]{4})\]'); @@ -69,251 +266,239 @@ Map parseAllKeys(String text) { for (final line in text.split('\n')) { final trimmed = line.trim(); if (trimmed.isEmpty || trimmed.startsWith('#') || trimmed.startsWith('@')) continue; - final semiIdx = trimmed.indexOf(';'); if (semiIdx < 0) continue; - final cpPart = trimmed.substring(0, semiIdx).trim(); - if (cpPart.contains(' ')) continue; // skip multi-codepoint - + if (cpPart.contains(' ')) continue; final cp = int.tryParse(cpPart, radix: 16); - if (cp == null || cp > 0xFFFF) continue; // BMP only + if (cp == null || cp > 0xFFFF) continue; - final weightPart = trimmed.substring(semiIdx + 1); - final matches = weightRe.allMatches(weightPart); + final matches = weightRe.allMatches(trimmed.substring(semiIdx + 1)); if (matches.isEmpty) continue; - - final isKat = _isKatakana(cp); final levels = <(int, int, int)>[]; - for (final m in matches) { - final p = int.parse(m.group(2)!, radix: 16); - final s = int.parse(m.group(3)!, radix: 16); - var t = int.parse(m.group(4)!, radix: 16); - // CLDR kana fix: lower katakana tertiary below hiragana range - if (isKat && t >= 0x000F) t -= 6; - levels.add((p, s, t)); + for (final match in matches) { + final primary = int.parse(match.group(2)!, radix: 16); + final secondary = int.parse(match.group(3)!, radix: 16); + var tertiary = int.parse(match.group(4)!, radix: 16); + if (_isKatakana(cp) && tertiary >= 0x000F) tertiary -= 6; + levels.add((primary, secondary, tertiary)); + } + if (levels.every((level) => level.$1 == 0 && level.$2 == 0 && level.$3 == 0)) continue; + if (result.containsKey(cp)) { + throw FormatException('Duplicate allkeys entry for U+${cp.toRadixString(16).padLeft(4, '0')}'); } - - if (levels.every((l) => l.$1 == 0 && l.$2 == 0 && l.$3 == 0)) continue; - result[cp] = Weight(levels, cp); } - return result; } -// --------------------------------------------------------------------------- -// Parse FractionalUCA.txt radical lines โ†’ CJK codepoints in radical-stroke -// order, plus Kangxi radical โ†’ CJK decomposition map. -// --------------------------------------------------------------------------- (List, Map) parseRadicals(String text) { final order = []; - final kangxiDecomp = {}; // kangxi radical CP โ†’ CJK CP + final kangxiDecomp = {}; final radicalRe = RegExp(r'^\[radical \d+'); for (final line in text.split('\n')) { if (!radicalRe.hasMatch(line)) continue; - - // Parse header: [radical N=:] final eqIdx = line.indexOf('='); final colonIdx = line.indexOf(':'); if (eqIdx < 0 || colonIdx < 0 || colonIdx <= eqIdx) continue; final closeBracket = line.lastIndexOf(']'); if (closeBracket < 0) continue; - // Extract Kangxi radical โ†’ CJK mapping from header final headerChars = line.substring(eqIdx + 1, colonIdx).runes.toList(); if (headerChars.length >= 2) { final kangxi = headerChars.first; final cjk = headerChars[1]; - // Kangxi Radicals: U+2F00-U+2FD5 if (kangxi >= 0x2F00 && kangxi <= 0x2FD5 && cjk <= 0xFFFF) { kangxiDecomp[kangxi] = cjk; } } - // Parse character list after colon - final charList = line.substring(colonIdx + 1, closeBracket); - final runes = charList.runes.toList(); + final runes = line.substring(colonIdx + 1, closeBracket).runes.toList(); var i = 0; while (i < runes.length) { final cp = runes[i]; - if (cp == 0x20) { i++; continue; } - - // Check for range: - if (i + 2 < runes.length && runes[i + 1] == 0x2D) { final endCp = runes[i + 2]; - for (var c = cp; c <= endCp; c++) { - if (c <= 0xFFFF) order.add(c); + for (var value = cp; value <= endCp; value++) { + if (value <= 0xFFFF) order.add(value); } i += 3; continue; } - if (cp <= 0xFFFF) order.add(cp); i++; } } - // Deduplicate preserving order final seen = {}; - final deduped = order.where((cp) => seen.add(cp)).toList(); - return (deduped, kangxiDecomp); + return (order.where(seen.add).toList(), kangxiDecomp); } -// --------------------------------------------------------------------------- -// Build final ordered list -// --------------------------------------------------------------------------- List buildOrder(Map allKeys, List cjkRadicalOrder) { - final entries = allKeys.entries.toList(); - entries.sort((a, b) => a.value.compareTo(b.value)); - + final entries = allKeys.entries.toList()..sort((a, b) => a.value.compareTo(b.value)); final ordered = []; final cjkSet = cjkRadicalOrder.toSet(); - bool cjkInserted = false; + var cjkInserted = false; - for (final e in entries) { - if (cjkSet.contains(e.key)) continue; - - if (!cjkInserted && e.value.levels.isNotEmpty && e.value.levels.first.$1 >= 0xFB00) { - for (final cp in cjkRadicalOrder) { - if (cp <= 0xFFFF) ordered.add(cp); - } + for (final entry in entries) { + if (cjkSet.contains(entry.key)) continue; + if (!cjkInserted && entry.value.levels.isNotEmpty && entry.value.levels.first.$1 >= 0xFB00) { + ordered.addAll(cjkRadicalOrder); cjkInserted = true; } - - ordered.add(e.key); + ordered.add(entry.key); } - - if (!cjkInserted) { - for (final cp in cjkRadicalOrder) { - if (cp <= 0xFFFF) ordered.add(cp); - } - } - + if (!cjkInserted) ordered.addAll(cjkRadicalOrder); return ordered; } -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- -Future main(List args) async { - final allKeysPath = args.isNotEmpty ? args.first : '/tmp/allkeys.txt'; - final fracUcaPath = args.length > 1 ? args[1] : '/tmp/FractionalUCA.txt'; - - // Download if missing - if (!File(allKeysPath).existsSync()) { - stderr.writeln('Downloading allkeys.txt...'); - final result = await Process.run('curl', [ - '-sL', - 'https://www.unicode.org/Public/UCA/13.0.0/allkeys.txt', - '-o', - allKeysPath, - ]); - if (result.exitCode != 0) { - stderr.writeln('Failed to download allkeys.txt'); - exit(1); +String buildDenseRankLookup(List ordered) { + if (ordered.length > 0xFFFF) { + throw StateError('Rank table overflow: ${ordered.length} entries exceed 65535'); + } + final ranks = List.filled(0x10000, 0); + for (var rank = 0; rank < ordered.length; rank++) { + final codepoint = ordered[rank]; + if (codepoint < 0 || codepoint > 0xFFFF) { + throw RangeError.range(codepoint, 0, 0xFFFF, 'codepoint'); } - } - - if (!File(fracUcaPath).existsSync()) { - stderr.writeln('Downloading FractionalUCA.txt...'); - final result = await Process.run('curl', [ - '-sL', - 'https://raw.githubusercontent.com/unicode-org/cldr/release-39/common/uca/FractionalUCA.txt', - '-o', - fracUcaPath, - ]); - if (result.exitCode != 0) { - stderr.writeln('Failed to download FractionalUCA.txt'); - exit(1); + if (ranks[codepoint] != 0) { + throw StateError('Duplicate codepoint U+${codepoint.toRadixString(16).padLeft(4, '0')}'); } + ranks[codepoint] = rank + 1; + } + return String.fromCharCodes(ranks); +} + +String _escapeCodeUnit(int codeUnit) { + if (codeUnit == 0x27) return r"\'"; + if (codeUnit == 0x5C) return r'\\'; + if (codeUnit == 0x24) return r'\$'; + return '\\u${codeUnit.toRadixString(16).padLeft(4, '0')}'; +} + +String renderDucetOrder(List ordered, Map kangxiDecomp) { + final denseRanks = buildDenseRankLookup(ordered); + final buffer = StringBuffer() + ..writeln('/// Dense BMP ranks per DUCET (Unicode 13.0) + pinned CLDR CJK radical-stroke.') + ..writeln('/// Each code unit stores rank + 1; zero means absent.') + ..writeln('/// Katakana sorts before hiragana (CLDR root tailoring).') + ..writeln('/// Generated by scripts/generate_ducet_ranks.dart โ€” do not edit.') + ..writeln('library;') + ..writeln() + ..writeln('const String _ducetRanks ='); + const unitsPerLine = 128; + for (var start = 0; start < denseRanks.length; start += unitsPerLine) { + final end = start + unitsPerLine < denseRanks.length ? start + unitsPerLine : denseRanks.length; + buffer.write(" '"); + for (var i = start; i < end; i++) { + buffer.write(_escapeCodeUnit(denseRanks.codeUnitAt(i))); + } + buffer.writeln(start + unitsPerLine >= denseRanks.length ? "';" : "'"); + } + + buffer + ..writeln() + ..writeln('/// Kangxi Radicals (U+2F00-U+2FD5) โ†’ CJK Unified equivalents.') + ..writeln('/// ICU decomposes these via NFD before collation.') + ..writeln('const Map _kangxiDecomp = {'); + final sortedKangxi = kangxiDecomp.entries.toList()..sort((a, b) => a.key.compareTo(b.key)); + for (final entry in sortedKangxi) { + buffer.writeln( + ' 0x${entry.key.toRadixString(16).toUpperCase()}: ' + '0x${entry.value.toRadixString(16).toUpperCase()},', + ); + } + buffer + ..writeln('};') + ..writeln() + ..writeln('/// Compare two characters using DUCET + CLDR ordering.') + ..writeln('/// Decomposes Kangxi radicals to CJK equivalents before lookup.') + ..writeln('/// Falls back to codepoint order for characters not in the table.') + ..writeln('int ducetCompare(String a, String b) {') + ..writeln(' var cpA = a.runes.first;') + ..writeln(' var cpB = b.runes.first;') + ..writeln(' cpA = _kangxiDecomp[cpA] ?? cpA;') + ..writeln(' cpB = _kangxiDecomp[cpB] ?? cpB;') + ..writeln(' final storedRankA = cpA <= 0xFFFF ? _ducetRanks.codeUnitAt(cpA) : 0;') + ..writeln(' final storedRankB = cpB <= 0xFFFF ? _ducetRanks.codeUnitAt(cpB) : 0;') + ..writeln(' final rankA = storedRankA == 0 ? 0x100000 + cpA : storedRankA - 1;') + ..writeln(' final rankB = storedRankB == 0 ? 0x100000 + cpB : storedRankB - 1;') + ..writeln(' return rankA.compareTo(rankB);') + ..writeln('}'); + return buffer.toString(); +} + +void writeGeneratedFile(File output, String contents) { + output.parent.createSync(recursive: true); + final staged = File('${output.path}.$pid.tmp'); + try { + staged.writeAsStringSync(contents, flush: true); + staged.renameSync(output.path); + } finally { + if (staged.existsSync()) staged.deleteSync(); + } +} + +Future generateDucetRanks({ + bool useBundledSources = true, + Directory? bundledSourceDirectory, + File? explicitAllKeys, + File? explicitFractionalUca, + Directory? cacheDirectory, + File? output, + SourceDownloader downloader = downloadSource, + GeneratedFileWriter writer = writeGeneratedFile, + SourceDescriptor allKeysDescriptor = allKeysSource, + SourceDescriptor fractionalUcaDescriptor = fractionalUcaSource, +}) async { + final sources = useBundledSources + ? await loadBundledSources( + bundledSourceDirectory: + bundledSourceDirectory ?? Directory.fromUri(Directory.current.uri.resolve('scripts/data/')), + allKeysDescriptor: allKeysDescriptor, + fractionalUcaDescriptor: fractionalUcaDescriptor, + ) + : await loadVerifiedSources( + explicitAllKeys: explicitAllKeys, + explicitFractionalUca: explicitFractionalUca, + cacheDirectory: cacheDirectory, + downloader: downloader, + allKeysDescriptor: allKeysDescriptor, + fractionalUcaDescriptor: fractionalUcaDescriptor, + ); + try { + final allKeys = parseAllKeys(await sources.allKeysFile.readAsString()); + final (cjkOrder, kangxiDecomp) = parseRadicals(await sources.fractionalUcaFile.readAsString()); + final ordered = buildOrder(allKeys, cjkOrder); + final rendered = renderDucetOrder(ordered, kangxiDecomp); + sources.commitCaches(); + writer(output ?? File.fromUri(Directory.current.uri.resolve('lib/data/ducet_order.dart')), rendered); + } finally { + sources.dispose(); + } +} + +Future main(List args) async { + if (args.length > 2) { + stderr.writeln('Usage: dart run scripts/generate_ducet_ranks.dart [allkeys.txt] [FractionalUCA.txt]'); + exitCode = 64; + return; + } + try { + await generateDucetRanks( + explicitAllKeys: args.isNotEmpty ? File(args.first) : null, + explicitFractionalUca: args.length > 1 ? File(args[1]) : null, + useBundledSources: args.isEmpty, + ); + stderr.writeln('Written to ${File.fromUri(Directory.current.uri.resolve('lib/data/ducet_order.dart')).path}'); + } catch (error) { + stderr.writeln(error); + exitCode = 1; } - - stderr.writeln('Parsing allkeys.txt (with CLDR kana fix)...'); - final allKeysText = File(allKeysPath).readAsStringSync(); - final allKeys = parseAllKeys(allKeysText); - stderr.writeln(' ${allKeys.length} BMP entries'); - - stderr.writeln('Parsing FractionalUCA.txt...'); - final fracText = File(fracUcaPath).readAsStringSync(); - final (cjkOrder, kangxiDecomp) = parseRadicals(fracText); - stderr.writeln(' ${cjkOrder.length} CJK codepoints in radical-stroke order'); - stderr.writeln(' ${kangxiDecomp.length} Kangxi radical decompositions'); - final bmpCjk = cjkOrder.where((cp) => cp <= 0xFFFF).length; - stderr.writeln(' $bmpCjk BMP CJK codepoints'); - - stderr.writeln('Building total order...'); - final ordered = buildOrder(allKeys, cjkOrder); - stderr.writeln(' ${ordered.length} total BMP codepoints in order'); - - stderr.writeln(' ${ordered.length * 2} raw bytes'); - - // Generate Dart file โ€” store codepoints as a raw UTF-16 string constant. - // Each char IS the codepoint. Dart strings are UTF-16 natively, so the - // compiled snapshot stores the data as raw 2-byte values with zero decoding. - final outPath = '${Directory.current.path}/lib/data/ducet_order.dart'; - final buf = StringBuffer(); - buf.writeln('/// Sorted BMP codepoints per DUCET (Unicode 13.0) + CLDR 39 CJK radical-stroke.'); - buf.writeln('/// Katakana sorts before hiragana (CLDR root tailoring).'); - buf.writeln('/// Generated by scripts/generate_ducet_ranks.dart โ€” do not edit.'); - buf.writeln(); - - // Raw UTF-16 string โ€” each code unit is a codepoint in DUCET order. - // Use \uXXXX escapes so the source stays ASCII-safe. - buf.write("const String _ducetOrder = '"); - for (var i = 0; i < ordered.length; i++) { - final cp = ordered[i]; - if (cp == 0x27) { - buf.write(r"\'"); // escape single quote - } else if (cp == 0x5C) { - buf.write(r'\\'); // escape backslash - } else if (cp == 0x24) { - buf.write(r'\$'); // escape dollar - } else { - buf.write('\\u${cp.toRadixString(16).padLeft(4, '0')}'); - } - } - buf.writeln("';"); - - // Kangxi radical decomposition map - buf.writeln(); - buf.writeln('/// Kangxi Radicals (U+2F00-U+2FD5) โ†’ CJK Unified equivalents.'); - buf.writeln('/// ICU decomposes these via NFD before collation.'); - buf.writeln('const Map _kangxiDecomp = {'); - final sortedKangxi = kangxiDecomp.entries.toList()..sort((a, b) => a.key.compareTo(b.key)); - for (final e in sortedKangxi) { - buf.writeln(' 0x${e.key.toRadixString(16).toUpperCase()}: 0x${e.value.toRadixString(16).toUpperCase()},'); - } - buf.writeln('};'); - - buf.writeln(); - buf.writeln('late final Map _ranks = _buildRanks();'); - buf.writeln(); - buf.writeln('Map _buildRanks() {'); - buf.writeln(' return {'); - buf.writeln(' for (var i = 0; i < _ducetOrder.length; i++)'); - buf.writeln(' _ducetOrder.codeUnitAt(i): i,'); - buf.writeln(' };'); - buf.writeln('}'); - buf.writeln(); - buf.writeln('/// Compare two characters using DUCET + CLDR ordering.'); - buf.writeln('/// Decomposes Kangxi radicals to CJK equivalents before lookup.'); - buf.writeln('/// Falls back to codepoint order for characters not in the table.'); - buf.writeln('int ducetCompare(String a, String b) {'); - buf.writeln(' var cpA = a.runes.first;'); - buf.writeln(' var cpB = b.runes.first;'); - buf.writeln(' cpA = _kangxiDecomp[cpA] ?? cpA;'); - buf.writeln(' cpB = _kangxiDecomp[cpB] ?? cpB;'); - buf.writeln(' final rankA = _ranks[cpA] ?? (0x100000 + cpA);'); - buf.writeln(' final rankB = _ranks[cpB] ?? (0x100000 + cpB);'); - buf.writeln(' return rankA.compareTo(rankB);'); - buf.writeln('}'); - - File(outPath).writeAsStringSync(buf.toString()); - stderr.writeln('Written to $outPath'); } diff --git a/scripts/generate_hid_key_labels.dart b/scripts/generate_hid_key_labels.dart new file mode 100644 index 00000000..63ac589c --- /dev/null +++ b/scripts/generate_hid_key_labels.dart @@ -0,0 +1,208 @@ +/// Generates lib/data/hid_key_labels.dart from the curated offline HID catalog. +/// +/// Usage: +/// dart run scripts/generate_hid_key_labels.dart [input.json] [output.dart] +library; + +import 'dart:convert'; +import 'dart:io'; + +const defaultHidKeyLabelsInput = 'scripts/data/hid_key_labels.json'; +const defaultHidKeyLabelsOutput = 'lib/data/hid_key_labels.dart'; + +final class HidKeyLabel { + const HidKeyLabel({required this.id, required this.label}); + + final String id; + final String label; + + int get numericId => int.parse(id, radix: 16); +} + +final class HidKeyGroup { + const HidKeyGroup({required this.name, required this.keys}); + + final String name; + final List keys; +} + +final class HidKeyCatalog { + const HidKeyCatalog({required this.groups}); + + final List groups; +} + +typedef AtomicFileWriter = Future Function(String path, String contents); + +HidKeyCatalog parseHidKeyLabelsCatalog(String source) { + final Object? decoded; + try { + decoded = jsonDecode(source); + } on FormatException catch (error) { + throw FormatException('Invalid HID catalog JSON: ${error.message}'); + } + + final root = _expectMap(decoded, 'catalog'); + if (root['schemaVersion'] != 1) { + throw const FormatException('HID catalog schemaVersion must be 1'); + } + + final rawGroups = _expectList(root['groups'], 'groups'); + if (rawGroups.isEmpty) { + throw const FormatException('HID catalog groups must not be empty'); + } + + final groups = []; + final groupNames = {}; + final ids = {}; + var previousId = -1; + + for (var groupIndex = 0; groupIndex < rawGroups.length; groupIndex++) { + final rawGroup = _expectMap(rawGroups[groupIndex], 'groups[$groupIndex]'); + final name = _expectNonemptyString(rawGroup['name'], 'groups[$groupIndex].name'); + if (!groupNames.add(name)) { + throw FormatException('Duplicate HID group name: $name'); + } + + final rawKeys = _expectList(rawGroup['keys'], 'groups[$groupIndex].keys'); + if (rawKeys.isEmpty) { + throw FormatException('HID group "$name" must contain at least one key'); + } + + final keys = []; + for (var keyIndex = 0; keyIndex < rawKeys.length; keyIndex++) { + final path = 'groups[$groupIndex].keys[$keyIndex]'; + final rawKey = _expectMap(rawKeys[keyIndex], path); + final id = _expectString(rawKey['id'], '$path.id'); + if (!RegExp(r'^[0-9a-f]{8}$').hasMatch(id)) { + throw FormatException('$path.id must be exactly eight lowercase hexadecimal digits'); + } + if (!ids.add(id)) { + throw FormatException('Duplicate HID key ID: $id'); + } + + final numericId = int.parse(id, radix: 16); + if (numericId <= previousId) { + throw FormatException('HID key IDs must be in strictly increasing numeric order: $id'); + } + previousId = numericId; + + final label = _expectNonemptyString(rawKey['label'], '$path.label'); + keys.add(HidKeyLabel(id: id, label: label)); + } + groups.add(HidKeyGroup(name: name, keys: List.unmodifiable(keys))); + } + + return HidKeyCatalog(groups: List.unmodifiable(groups)); +} + +String renderHidKeyLabels(HidKeyCatalog catalog) { + final output = StringBuffer() + ..writeln( + '// Generated by dart run scripts/generate_hid_key_labels.dart from ' + 'scripts/data/hid_key_labels.json; do not edit by hand.', + ) + ..writeln() + ..writeln('/// Human-readable labels for physical keyboard keys, keyed by USB HID usage code.') + ..writeln('const hidKeyLabels = {'); + + for (final group in catalog.groups) { + output.writeln(' // ${group.name}'); + for (final key in group.keys) { + output.writeln(' 0x${key.id}: ${_dartString(key.label)},'); + } + } + + output.writeln('};'); + return output.toString(); +} + +Future writeFileAtomically(String path, String contents) async { + final output = File(path); + await output.parent.create(recursive: true); + final temporary = File('$path.tmp.$pid.${DateTime.now().microsecondsSinceEpoch}'); + try { + await temporary.writeAsString(contents, flush: true); + await temporary.rename(path); + } finally { + if (await temporary.exists()) { + await temporary.delete(); + } + } +} + +Future generateHidKeyLabels( + String inputPath, + String outputPath, { + AtomicFileWriter writer = writeFileAtomically, +}) async { + final source = await File(inputPath).readAsString(); + final catalog = parseHidKeyLabelsCatalog(source); + final rendered = renderHidKeyLabels(catalog); + await writer(outputPath, rendered); +} + +Future main(List arguments) async { + if (arguments.length > 2) { + stderr.writeln('Usage: dart run scripts/generate_hid_key_labels.dart [input.json] [output.dart]'); + exitCode = 64; + return; + } + + final inputPath = arguments.isEmpty ? defaultHidKeyLabelsInput : arguments[0]; + final outputPath = arguments.length < 2 ? defaultHidKeyLabelsOutput : arguments[1]; + await generateHidKeyLabels(inputPath, outputPath); +} + +Map _expectMap(Object? value, String path) { + if (value is! Map) { + throw FormatException('$path must be a JSON object'); + } + return value; +} + +List _expectList(Object? value, String path) { + if (value is! List) { + throw FormatException('$path must be a JSON array'); + } + return value; +} + +String _expectString(Object? value, String path) { + if (value is! String) { + throw FormatException('$path must be a string'); + } + return value; +} + +String _expectNonemptyString(Object? value, String path) { + final string = _expectString(value, path); + if (string.trim().isEmpty) { + throw FormatException('$path must not be empty'); + } + return string; +} + +String _dartString(String value) { + if (value == r'\') return r"r'\'"; + + if (value.contains("'")) { + final escaped = value + .replaceAll(r'\', r'\\') + .replaceAll('"', r'\"') + .replaceAll(r'$', r'\$') + .replaceAll('\r', r'\r') + .replaceAll('\n', r'\n') + .replaceAll('\t', r'\t'); + return '"$escaped"'; + } + + final escaped = value + .replaceAll(r'\', r'\\') + .replaceAll("'", r"\'") + .replaceAll(r'$', r'\$') + .replaceAll('\r', r'\r') + .replaceAll('\n', r'\n') + .replaceAll('\t', r'\t'); + return "'$escaped'"; +} diff --git a/scripts/generate_iso_639_data.dart b/scripts/generate_iso_639_data.dart new file mode 100644 index 00000000..30b48f75 --- /dev/null +++ b/scripts/generate_iso_639_data.dart @@ -0,0 +1,239 @@ +/// Generates lib/data/iso_639_data.dart from the curated offline ISO 639 catalog. +/// +/// Usage: +/// dart run scripts/generate_iso_639_data.dart [input.json] [output.dart] +library; + +import 'dart:convert'; +import 'dart:io'; + +const defaultIso639Input = 'scripts/data/iso_639_codes.json'; +const defaultIso639Output = 'lib/data/iso_639_data.dart'; + +final class Iso639CatalogEntry { + const Iso639CatalogEntry({ + required this.primary, + required this.terminology, + required this.bibliographic, + required this.name, + }); + + final String primary; + final String terminology; + final String? bibliographic; + final String name; +} + +final class Iso639Catalog { + const Iso639Catalog({required this.entries}); + + final List entries; +} + +typedef AtomicFileWriter = Future Function(String path, String contents); + +Iso639Catalog parseIso639Catalog(String source) { + final Object? decoded; + try { + decoded = jsonDecode(source); + } on FormatException catch (error) { + throw FormatException('Invalid ISO 639 catalog JSON: ${error.message}'); + } + + final root = _expectMap(decoded, 'catalog'); + if (root['schemaVersion'] != 1) { + throw const FormatException('ISO 639 catalog schemaVersion must be 1'); + } + + final languages = _expectMap(root['languages'], 'languages'); + if (languages.isEmpty) { + throw const FormatException('ISO 639 catalog languages must not be empty'); + } + + final entries = []; + final allCodes = {}; + String? previousPrimary; + + for (final catalogEntry in languages.entries) { + final key = catalogEntry.key; + if (!_isPrimaryCode(key)) { + throw FormatException('Language key must be exactly two lowercase letters: $key'); + } + if (previousPrimary != null && key.compareTo(previousPrimary) <= 0) { + throw FormatException('Language keys must be in strictly increasing order: $key'); + } + previousPrimary = key; + + final rawEntry = _expectMap(catalogEntry.value, 'languages.$key'); + final primary = _expectString(rawEntry['primary'], 'languages.$key.primary'); + if (primary != key) { + throw FormatException('Language key $key does not match primary code $primary'); + } + if (!_isPrimaryCode(primary)) { + throw FormatException('languages.$key.primary must be exactly two lowercase letters'); + } + if (!allCodes.add(primary)) { + throw FormatException('Duplicate ISO 639 code: $primary'); + } + + final terminology = _expectString(rawEntry['terminology'], 'languages.$key.terminology'); + if (!_isAliasCode(terminology)) { + throw FormatException('languages.$key.terminology must be exactly three lowercase letters'); + } + if (!allCodes.add(terminology)) { + throw FormatException('Duplicate ISO 639 code: $terminology'); + } + + final rawBibliographic = rawEntry['bibliographic']; + final String? bibliographic; + if (rawBibliographic == null) { + bibliographic = null; + } else { + bibliographic = _expectString(rawBibliographic, 'languages.$key.bibliographic'); + if (!_isAliasCode(bibliographic)) { + throw FormatException('languages.$key.bibliographic must be null or exactly three lowercase letters'); + } + if (!allCodes.add(bibliographic)) { + throw FormatException('Duplicate ISO 639 code: $bibliographic'); + } + } + + final name = _expectNonemptyString(rawEntry['name'], 'languages.$key.name'); + entries.add( + Iso639CatalogEntry(primary: primary, terminology: terminology, bibliographic: bibliographic, name: name), + ); + } + + return Iso639Catalog(entries: List.unmodifiable(entries)); +} + +String renderIso639Data(Iso639Catalog catalog) { + final output = StringBuffer() + ..writeln( + '// Generated by dart run scripts/generate_iso_639_data.dart from ' + 'scripts/data/iso_639_codes.json; do not edit by hand.', + ) + ..writeln() + ..writeln('class LanguageEntry {') + ..writeln(' final String code1;') + ..writeln(' final String code2;') + ..writeln(' final String? code2B;') + ..writeln(' final String name;') + ..writeln(' const LanguageEntry(this.code1, this.code2, this.code2B, this.name);') + ..writeln('}') + ..writeln() + ..writeln('const languageEntries = {'); + + for (final entry in catalog.entries) { + final bibliographic = entry.bibliographic == null ? 'null' : _dartString(entry.bibliographic!); + output.writeln( + ' ${_dartString(entry.primary)}: LanguageEntry(' + '${_dartString(entry.primary)}, ${_dartString(entry.terminology)}, $bibliographic, ${_dartString(entry.name)}),', + ); + } + + output + ..writeln('};') + ..writeln() + ..writeln('const code2ToCode1 = {'); + for (final entry in catalog.entries) { + output.writeln(' ${_dartString(entry.terminology)}: ${_dartString(entry.primary)},'); + } + + output + ..writeln('};') + ..writeln() + ..writeln('const code2BToCode1 = {'); + for (final entry in catalog.entries) { + final bibliographic = entry.bibliographic; + if (bibliographic != null) { + output.writeln(' ${_dartString(bibliographic)}: ${_dartString(entry.primary)},'); + } + } + output.writeln('};'); + return output.toString(); +} + +Future writeFileAtomically(String path, String contents) async { + final output = File(path); + await output.parent.create(recursive: true); + final temporary = File('$path.tmp.$pid.${DateTime.now().microsecondsSinceEpoch}'); + try { + await temporary.writeAsString(contents, flush: true); + await temporary.rename(path); + } finally { + if (await temporary.exists()) { + await temporary.delete(); + } + } +} + +Future generateIso639Data( + String inputPath, + String outputPath, { + AtomicFileWriter writer = writeFileAtomically, +}) async { + final source = await File(inputPath).readAsString(); + final catalog = parseIso639Catalog(source); + final rendered = renderIso639Data(catalog); + await writer(outputPath, rendered); +} + +Future main(List arguments) async { + if (arguments.length > 2) { + stderr.writeln('Usage: dart run scripts/generate_iso_639_data.dart [input.json] [output.dart]'); + exitCode = 64; + return; + } + + final inputPath = arguments.isEmpty ? defaultIso639Input : arguments[0]; + final outputPath = arguments.length < 2 ? defaultIso639Output : arguments[1]; + await generateIso639Data(inputPath, outputPath); +} + +Map _expectMap(Object? value, String path) { + if (value is! Map) { + throw FormatException('$path must be a JSON object'); + } + return value; +} + +String _expectString(Object? value, String path) { + if (value is! String) { + throw FormatException('$path must be a string'); + } + return value; +} + +String _expectNonemptyString(Object? value, String path) { + final string = _expectString(value, path); + if (string.trim().isEmpty) { + throw FormatException('$path must not be empty'); + } + return string; +} + +bool _isPrimaryCode(String code) => RegExp(r'^[a-z]{2}$').hasMatch(code); +bool _isAliasCode(String code) => RegExp(r'^[a-z]{3}$').hasMatch(code); + +String _dartString(String value) { + if (value.contains("'")) { + final escaped = value + .replaceAll(r'\', r'\\') + .replaceAll('"', r'\"') + .replaceAll(r'$', r'\$') + .replaceAll('\r', r'\r') + .replaceAll('\n', r'\n') + .replaceAll('\t', r'\t'); + return '"$escaped"'; + } + + final escaped = value + .replaceAll(r'\', r'\\') + .replaceAll("'", r"\'") + .replaceAll(r'$', r'\$') + .replaceAll('\r', r'\r') + .replaceAll('\n', r'\n') + .replaceAll('\t', r'\t'); + return "'$escaped'"; +} diff --git a/scripts/test_check_codegen.py b/scripts/test_check_codegen.py index 52c8d00a..0481d122 100644 --- a/scripts/test_check_codegen.py +++ b/scripts/test_check_codegen.py @@ -10,6 +10,9 @@ from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent GENERATED_PATHS = ( + "lib/data/ducet_order.dart", + "lib/data/hid_key_labels.dart", + "lib/data/iso_639_data.dart", "lib/i18n/strings.g.dart", "lib/models/model.freezed.dart", "lib/models/model.g.dart", @@ -58,6 +61,18 @@ cp source.txt server/relay_protocol_gen.go """#!/usr/bin/env bash if [ "${FAIL_DART:-0}" -ne 0 ]; then exit "$FAIL_DART"; fi case "$*" in + *"generate_ducet_ranks.dart") + mkdir -p lib/data + cp source.txt lib/data/ducet_order.dart + ;; + *"generate_hid_key_labels.dart") + mkdir -p lib/data + cp source.txt lib/data/hid_key_labels.dart + ;; + *"generate_iso_639_data.dart") + mkdir -p lib/data + cp source.txt lib/data/iso_639_data.dart + ;; "run slang") mkdir -p lib/i18n cp source.txt lib/i18n/strings.g.dart @@ -194,6 +209,18 @@ esac self.assertEqual(untracked.read_text(encoding="utf-8"), "caller sentinel\n") self.assertEqual(self.git_status(), status_before) + def test_deleted_explicit_dart_output_is_reported_without_repair(self) -> None: + deleted = self.root / "lib" / "data" / "hid_key_labels.dart" + deleted.unlink() + status_before = self.git_status() + + result = self.run_codegen("--check") + + self.assertEqual(result.returncode, 1) + self.assertIn("lib/data/hid_key_labels.dart", result.stderr) + self.assertFalse(deleted.exists()) + self.assertEqual(self.git_status(), status_before) + def test_write_mode_updates_outputs_and_forwards_build_runner_arguments(self) -> None: (self.root / "source.txt").write_text("version two\n", encoding="utf-8") diff --git a/slang.yaml b/slang.yaml index 4df90738..dcb04c60 100644 --- a/slang.yaml +++ b/slang.yaml @@ -2,8 +2,8 @@ input_directory: lib/i18n input_file_pattern: .i18n.json output_directory: lib/i18n timestamp: false -# Fall back to base (en) for any key missing in a non-base locale. -# Without this, every locale would need every key declared up-front and -# the generator would emit `class is missing implementations forโ€ฆ` -# errors when a new English string lands. -fallback_strategy: base_locale +# Fall back to base (en) when a non-base key is missing or still has the +# translation cleanup placeholder (`""`). Without this, every locale would +# need every key translated up-front and empty placeholders would render +# without meaningful text. +fallback_strategy: base_locale_empty_string diff --git a/test/data/ducet_order_test.dart b/test/data/ducet_order_test.dart new file mode 100644 index 00000000..36cb21c6 --- /dev/null +++ b/test/data/ducet_order_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/data/ducet_order.dart'; + +void main() { + test('applies CLDR kana ordering', () { + expect(ducetCompare('ใ‚ข', 'ใ‚'), isNegative); + expect(ducetCompare('ใ‚ซ', 'ใ‹'), isNegative); + }); + + test('orders representative scripts by DUCET rank', () { + expect(ducetCompare('A', 'ฮฑ'), isNegative); + expect(ducetCompare('ฮฑ', 'ะ–'), isNegative); + expect(ducetCompare('ะ–', 'ไธ€'), isNegative); + }); + + test('decomposes Kangxi radicals to their unified equivalents', () { + expect(ducetCompare('โผ€', 'ไธ€'), 0); + expect(ducetCompare('โฟ•', '้พ '), 0); + }); + + test('uses codepoint fallback for missing BMP and supplementary characters', () { + expect(ducetCompare('\u0378', '\u0379'), isNegative); + expect(ducetCompare('\u0378', 'A'), isPositive); + expect(ducetCompare('๐Ÿ˜€', '๐Ÿ˜'), isNegative); + expect(ducetCompare('๐Ÿ˜€', 'A'), isPositive); + }); + + test('preserves apostrophe, backslash, and dollar ranks in generated literal', () { + expect(ducetCompare("'", r'$'), isNegative); + expect(ducetCompare(r'$', r'\'), isPositive); + }); + + test('compares only the first rune', () { + expect(ducetCompare('A trailing text', 'A different text'), 0); + expect(ducetCompare('๐Ÿ˜€ trailing text', '๐Ÿ˜€ different text'), 0); + }); + + test('preserves empty-input failure', () { + expect(() => ducetCompare('', 'A'), throwsStateError); + expect(() => ducetCompare('A', ''), throwsStateError); + }); +} diff --git a/test/data/hid_key_labels_test.dart b/test/data/hid_key_labels_test.dart new file mode 100644 index 00000000..a08e32fa --- /dev/null +++ b/test/data/hid_key_labels_test.dart @@ -0,0 +1,31 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/data/hid_key_labels.dart'; +import 'package:plezy/models/hotkey_model.dart'; + +void main() { + test('catalog preserves all 325 unique keys in canonical numeric order', () { + final ids = hidKeyLabels.keys.toList(); + final sortedIds = [...ids]..sort(); + + expect(hidKeyLabels, hasLength(325)); + expect(ids.toSet(), hasLength(ids.length)); + expect(ids, sortedIds); + expect(ids.every((id) => id >= 0 && id <= 0xffffffff), isTrue); + expect(hidKeyLabels.values.every((label) => label.trim().isNotEmpty), isTrue); + }); + + test('catalog preserves representative labels across usage groups', () { + expect(hidKeyLabels[0x00000012], 'Fn'); + expect(hidKeyLabels[0x000100b5], 'Display Toggle'); + expect(hidKeyLabels[0x0005ff1f], 'Game Button Z'); + expect(hidKeyLabels[0x00070031], r'\'); + expect(hidKeyLabels[0x000c00cd], 'Play/Pause'); + expect(hidKeyLabels[0x000c029f], 'Show All Windows'); + }); + + test('physicalKeyLabel uses the catalog and formats an unknown HID fallback', () { + expect(physicalKeyLabel(PhysicalKeyboardKey.keyA), 'A'); + expect(physicalKeyLabel(const PhysicalKeyboardKey(0xffffffff)), 'Key 0xffffffff'); + }); +} diff --git a/test/data/iso_639_data_test.dart b/test/data/iso_639_data_test.dart new file mode 100644 index 00000000..4e7273bc --- /dev/null +++ b/test/data/iso_639_data_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/data/iso_639_data.dart'; +import 'package:plezy/utils/language_codes.dart'; + +void main() { + test('catalog preserves 184 canonical entries and globally unique codes', () { + final primaryCodes = languageEntries.keys.toList(); + final sortedPrimaryCodes = [...primaryCodes]..sort(); + final allCodes = {}; + + expect(languageEntries, hasLength(184)); + expect(primaryCodes, sortedPrimaryCodes); + for (final mapEntry in languageEntries.entries) { + final entry = mapEntry.value; + expect(entry.code1, mapEntry.key); + expect(entry.code1, matches(RegExp(r'^[a-z]{2}$'))); + expect(entry.code2, matches(RegExp(r'^[a-z]{3}$'))); + expect(entry.name.trim(), isNotEmpty); + expect(allCodes.add(entry.code1), isTrue); + expect(allCodes.add(entry.code2), isTrue); + if (entry.code2B case final bibliographic?) { + expect(bibliographic, matches(RegExp(r'^[a-z]{3}$'))); + expect(allCodes.add(bibliographic), isTrue); + } + } + }); + + test('reverse maps are derived completely from terminology and bibliographic aliases', () { + expect(code2ToCode1, hasLength(184)); + expect(code2BToCode1, hasLength(20)); + + for (final entry in languageEntries.values) { + expect(code2ToCode1[entry.code2], entry.code1); + if (entry.code2B case final bibliographic?) { + expect(code2BToCode1[bibliographic], entry.code1); + } + } + }); + + test('representative terminology and bibliographic aliases preserve behavior', () { + final german = languageEntries['de']!; + expect(german.code2, 'deu'); + expect(german.code2B, 'ger'); + expect(german.name, 'German'); + expect(languageEntries['bn']!.name, 'Bengali, Bangla'); + expect(languageEntries['mi']!.name, 'Mฤori'); + + expect(LanguageCodes.getIso6391Code(' DEU '), 'de'); + expect(LanguageCodes.getIso6391Code('ger'), 'de'); + expect(LanguageCodes.getLanguageName('GER'), 'German'); + expect(LanguageCodes.getVariations('ger'), unorderedEquals(['ger', 'de', 'deu'])); + expect(LanguageCodes.getLanguageName('zzz'), isNull); + }); +} diff --git a/test/focus/focusable_chip_long_press_test.dart b/test/focus/focusable_chip_long_press_test.dart index dad25dcc..f476cde8 100644 --- a/test/focus/focusable_chip_long_press_test.dart +++ b/test/focus/focusable_chip_long_press_test.dart @@ -1,3 +1,5 @@ +import 'dart:ui' show SemanticsAction, Tristate; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -39,4 +41,22 @@ void main() { expect(longPressed, 0); }); + + testWidgets('tab chips expose one operable node with selected state', (tester) async { + final semantics = tester.ensureSemantics(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FocusableTabChip(label: 'Selected tab', isSelected: true, onSelect: () {}), + ), + ), + ); + + final node = tester.getSemantics(find.bySemanticsLabel('Selected tab')).getSemanticsData(); + expect(node.flagsCollection.isButton, isTrue); + expect(node.flagsCollection.isSelected, Tristate.isTrue); + expect(node.hasAction(SemanticsAction.tap), isTrue); + semantics.dispose(); + }); } diff --git a/test/focus/grid_focus_node_mixin_test.dart b/test/focus/grid_focus_node_mixin_test.dart new file mode 100644 index 00000000..7de64b99 --- /dev/null +++ b/test/focus/grid_focus_node_mixin_test.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/mixins/grid_focus_node_mixin.dart'; + +void main() { + testWidgets('focused distant nodes retain ownership until a later eviction', (tester) async { + final key = GlobalKey<_GridFocusHarnessState>(); + + await tester.pumpWidget(MaterialApp(home: _GridFocusHarness(key: key))); + final state = key.currentState!; + final retained = state.getGridItemFocusNode(0); + state.getGridItemFocusNode(10); + final center = state.getGridItemFocusNode(20); + state.refresh(); + await tester.pump(); + + retained.requestFocus(); + await tester.pump(); + expect(retained.hasFocus, isTrue); + + state.evictDistantFocusNodes(20, keepCount: 1); + + expect(state.gridItemFocusNodes[0], same(retained)); + expect(state.gridItemFocusNodes.containsKey(10), isFalse); + expect(state.getGridItemFocusNode(0), same(retained)); + state.refresh(); + await tester.pump(); + + center.requestFocus(); + await tester.pump(); + expect(retained.hasFocus, isFalse); + + state.evictDistantFocusNodes(20, keepCount: 1); + + expect(state.gridItemFocusNodes.containsKey(0), isFalse); + expect(state.gridItemFocusNodes[20], same(center)); + state.refresh(); + await tester.pump(); + }); +} + +class _GridFocusHarness extends StatefulWidget { + const _GridFocusHarness({super.key}); + + @override + State<_GridFocusHarness> createState() => _GridFocusHarnessState(); +} + +class _GridFocusHarnessState extends State<_GridFocusHarness> with GridFocusNodeMixin<_GridFocusHarness> { + void refresh() => setState(() {}); + @override + void dispose() { + disposeGridFocusNodes(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => Row( + children: [ + for (final node in gridItemFocusNodes.values) + Focus(focusNode: node, child: const SizedBox(width: 10, height: 10)), + ], + ); +} diff --git a/test/focus/input_mode_tracker_test.dart b/test/focus/input_mode_tracker_test.dart index 5a0e81c8..67ff213f 100644 --- a/test/focus/input_mode_tracker_test.dart +++ b/test/focus/input_mode_tracker_test.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/focus/input_mode_tracker.dart'; import 'package:plezy/services/gamepad_service.dart'; +import 'package:plezy/utils/platform_detector.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -51,4 +52,65 @@ void main() { expect(oneShotMode, InputMode.pointer); expect(oneShotBuilds, 1); }); + + testWidgets('keyboard-mode cursor shield preserves pointer activation', (tester) async { + var taps = 0; + + await tester.pumpWidget( + InputModeTracker( + child: Directionality( + textDirection: TextDirection.ltr, + child: GestureDetector( + key: const Key('target'), + behavior: HitTestBehavior.opaque, + onTap: () => taps++, + child: const SizedBox(width: 120, height: 80), + ), + ), + ), + ); + + GamepadService.onGamepadInput!.call(); + await tester.pump(); + + expect(tester.widget(find.byType(MouseRegion)).cursor, SystemMouseCursors.none); + + await tester.tap(find.byKey(const Key('target'))); + await tester.pump(); + + expect(taps, 1); + expect(find.byType(MouseRegion), findsNothing); + }); + + testWidgets('non-desktop TV path has no cursor shield and remains pointer-reachable', (tester) async { + TvDetectionService.debugSetAppleTVOverride(true); + PlatformDetector.debugSetIsDesktopOSOverride(false); + addTearDown(() { + TvDetectionService.debugSetAppleTVOverride(null); + PlatformDetector.debugSetIsDesktopOSOverride(null); + }); + var taps = 0; + + await tester.pumpWidget( + InputModeTracker( + child: Directionality( + textDirection: TextDirection.ltr, + child: GestureDetector( + key: const Key('tv-target'), + behavior: HitTestBehavior.opaque, + onTap: () => taps++, + child: const SizedBox(width: 120, height: 80), + ), + ), + ), + ); + + GamepadService.onGamepadInput!.call(); + await tester.pump(); + + expect(find.byType(MouseRegion), findsNothing); + await tester.tap(find.byKey(const Key('tv-target'))); + await tester.pump(); + expect(taps, 1); + }); } diff --git a/test/focus/key_event_utils_test.dart b/test/focus/key_event_utils_test.dart index 4e8b56d4..e10a9855 100644 --- a/test/focus/key_event_utils_test.dart +++ b/test/focus/key_event_utils_test.dart @@ -75,6 +75,35 @@ void main() { await tester.pump(); }); + test('one-shot select consumes every phase and activates only on key down', () { + var activations = 0; + const down = KeyDownEvent( + physicalKey: PhysicalKeyboardKey.select, + logicalKey: LogicalKeyboardKey.select, + timeStamp: Duration.zero, + ); + const repeat = KeyRepeatEvent( + physicalKey: PhysicalKeyboardKey.select, + logicalKey: LogicalKeyboardKey.select, + timeStamp: Duration(milliseconds: 100), + ); + const up = KeyUpEvent( + physicalKey: PhysicalKeyboardKey.select, + logicalKey: LogicalKeyboardKey.select, + timeStamp: Duration(milliseconds: 200), + ); + const unrelated = KeyDownEvent( + physicalKey: PhysicalKeyboardKey.f12, + logicalKey: LogicalKeyboardKey.f12, + timeStamp: Duration.zero, + ); + + expect(handleOneShotSelect(down, () => activations++), KeyEventResult.handled); + expect(handleOneShotSelect(repeat, () => activations++), KeyEventResult.handled); + expect(handleOneShotSelect(up, () => activations++), KeyEventResult.handled); + expect(handleOneShotSelect(unrelated, () => activations++), KeyEventResult.ignored); + expect(activations, 1); + }); group('BackKeyCoordinator', () { testWidgets('suppresses one parallel back dispatch in the current frame', (tester) async { BackKeyCoordinator.markHandled(); @@ -214,6 +243,77 @@ void main() { expect(FocusManager.instance.primaryFocus?.debugLabel, 'ActionBar[0]'); }); + testWidgets('skips disabled actions for entry and horizontal traversal', (tester) async { + final key = GlobalKey(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FocusableActionBar( + key: key, + actions: [ + const FocusableAction(icon: Icons.block), + FocusableAction(icon: Icons.add, onPressed: () {}), + const FocusableAction(icon: Icons.block), + FocusableAction(icon: Icons.remove, onPressed: () {}), + ], + ), + ), + ), + ); + await tester.pump(); + + key.currentState!.requestFocusOnFirst(); + await tester.pump(); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'ActionBar[1]'); + + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); + await tester.pump(); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'ActionBar[3]'); + + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); + await tester.pump(); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'ActionBar[1]'); + }); + + testWidgets('dynamic callback changes remove a disabled action from focus', (tester) async { + final key = GlobalKey(); + late StateSetter rebuild; + var firstEnabled = true; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (context, setState) { + rebuild = setState; + return FocusableActionBar( + key: key, + actions: [ + FocusableAction(icon: Icons.add, onPressed: firstEnabled ? () {} : null), + FocusableAction(icon: Icons.remove, onPressed: () {}), + ], + ); + }, + ), + ), + ), + ); + await tester.pump(); + + key.currentState!.requestFocusOnFirst(); + await tester.pump(); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'ActionBar[0]'); + + rebuild(() => firstEnabled = false); + await tester.pump(); + expect(key.currentState!.getFocusNode(0)!.canRequestFocus, isFalse); + expect(key.currentState!.getFocusNode(0)!.hasFocus, isFalse); + + key.currentState!.requestFocusOnFirst(); + await tester.pump(); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'ActionBar[1]'); + }); testWidgets('still invokes onNavigateLeft at the left edge when wired', (tester) async { final key = GlobalKey(); final leftTarget = FocusNode(debugLabel: 'left-target'); @@ -279,4 +379,25 @@ void main() { expect(activations, 1); }); }); + + group('expandToGraphemeRange', () { + for (final grapheme in ['๐Ÿ˜€', 'e\u0301', '๐Ÿ‡ฏ๐Ÿ‡ต', '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ']) { + test('expands partial ${grapheme.runes.length}-scalar ranges', () { + final text = 'A${grapheme}B'; + final graphemeEnd = 1 + grapheme.length; + final expected = TextRange(start: 1, end: graphemeEnd); + + expect(expandToGraphemeRange(text, const TextRange(start: 1, end: 2)), expected); + expect(expandToGraphemeRange(text, TextRange(start: graphemeEnd - 1, end: graphemeEnd)), expected); + expect(expandToGraphemeRange(text, TextSelection(baseOffset: graphemeEnd - 1, extentOffset: 1)), expected); + expect(expandToGraphemeRange(text, TextRange(start: graphemeEnd - 1, end: 1)), expected); + }); + } + + test('preserves document edges and empty ranges', () { + expect(expandToGraphemeRange('abc', const TextRange(start: 0, end: 1)), const TextRange(start: 0, end: 1)); + expect(expandToGraphemeRange('abc', const TextRange(start: 3, end: 3)), const TextRange.collapsed(3)); + expect(expandToGraphemeRange('', const TextRange(start: 0, end: 1)), TextRange.empty); + }); + }); } diff --git a/test/focus/locked_hub_controller_test.dart b/test/focus/locked_hub_controller_test.dart new file mode 100644 index 00000000..f81cfaf2 --- /dev/null +++ b/test/focus/locked_hub_controller_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/focus/locked_hub_controller.dart'; + +void main() { + test('focus memory and last-column hint are isolated per browse owner', () { + final ownerA = HubFocusMemory(); + final ownerB = HubFocusMemory(); + + ownerA.setForHub('detail_episodes', 7); + + expect(ownerA.getForHubOnly('detail_episodes', 5), 4); + expect(ownerA.getForHub('detail_episodes', 3), 2); + expect(ownerA.getForHub('detail_extras', 4), 3); + + expect(ownerB.getForHubOnly('detail_episodes', 5), 0); + expect(ownerB.getForHub('detail_episodes', 5), 0); + expect(ownerB.getForHub('detail_extras', 5), 0); + }); + + test('per-hub fallback and empty rows keep their existing clamping behavior', () { + final memory = HubFocusMemory(); + + expect(memory.getForHubOnly('unseen', 3, fallback: 9), 2); + expect(memory.getForHubOnly('unseen', 0, fallback: 9), 0); + expect(memory.getForHub('unseen', 0), 0); + }); +} diff --git a/test/i18n/empty_string_fallback_test.dart b/test/i18n/empty_string_fallback_test.dart new file mode 100644 index 00000000..d20aa4a5 --- /dev/null +++ b/test/i18n/empty_string_fallback_test.dart @@ -0,0 +1,73 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/i18n/strings.g.dart'; + +void main() { + test('empty overrides fall back in every affected locale', () async { + final english = await AppLocale.en.build(); + final commonEnglishValues = [ + english.videoControls.showPlaybackControls, + english.videoControls.hidePlaybackControls, + ]; + final spotlightEnglishValues = [ + english.settings.tvCornerSpotlightBackdrop, + english.settings.tvCornerSpotlightBackdropDescription, + ]; + + expect([...commonEnglishValues, ...spotlightEnglishValues], everyElement(isNotEmpty)); + + const commonFallbackLocales = [ + AppLocale.bg, + AppLocale.da, + AppLocale.de, + AppLocale.es, + AppLocale.fr, + AppLocale.it, + AppLocale.ja, + AppLocale.ko, + AppLocale.nb, + AppLocale.nl, + AppLocale.pl, + AppLocale.pt, + AppLocale.ru, + AppLocale.sv, + AppLocale.zh, + ]; + const spotlightFallbackLocales = [ + AppLocale.bg, + AppLocale.da, + AppLocale.es, + AppLocale.fr, + AppLocale.it, + AppLocale.ja, + AppLocale.ko, + AppLocale.nb, + AppLocale.nl, + AppLocale.pl, + AppLocale.pt, + AppLocale.ru, + AppLocale.sv, + AppLocale.zh, + ]; + + for (final locale in commonFallbackLocales) { + final translations = await locale.build(); + expect( + [translations.videoControls.showPlaybackControls, translations.videoControls.hidePlaybackControls], + commonEnglishValues, + reason: '${locale.languageCode} must inherit the common English values', + ); + } + + for (final locale in spotlightFallbackLocales) { + final translations = await locale.build(); + expect( + [ + translations.settings.tvCornerSpotlightBackdrop, + translations.settings.tvCornerSpotlightBackdropDescription, + ], + spotlightEnglishValues, + reason: '${locale.languageCode} must inherit the English spotlight values', + ); + } + }); +} diff --git a/test/i18n/locale_fallback_source_test.dart b/test/i18n/locale_fallback_source_test.dart new file mode 100644 index 00000000..99950460 --- /dev/null +++ b/test/i18n/locale_fallback_source_test.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('recovery and error locale entries are absent or non-empty', () { + const recoveryPaths = >[ + ['auth', 'localDataRecoveryRequired'], + ['settings', 'watchTogetherRelayInvalid'], + ['settings', 'saveFailed'], + ['messages', 'playbackAuthenticationRequired'], + ['messages', 'playbackServerUnavailable'], + ['messages', 'playbackDataInvalid'], + ['messages', 'playbackCancelled'], + ['messages', 'playbackFailed'], + ['profiles', 'borrowLoadFailed'], + ['liveTv', 'favoritesUpdateFailed'], + ['settings', 'downloadLocationPickerUnavailable'], + ]; + final sourceDirectory = Directory('lib/i18n'); + final english = _decodeLocale(File('${sourceDirectory.path}/en.i18n.json')); + final localeFiles = sourceDirectory.listSync().whereType().where( + (file) => file.path.endsWith('.i18n.json') && !file.path.endsWith('en.i18n.json'), + ); + + for (final path in recoveryPaths) { + expect(_valueAt(english, path), isNotEmpty, reason: 'English base value ${path.join('.')} must be usable'); + for (final localeFile in localeFiles) { + final locale = _decodeLocale(localeFile); + final value = _valueAt(locale, path); + expect(value, anyOf(isNull, isNotEmpty), reason: '${localeFile.path} must omit or translate ${path.join('.')}'); + } + } + }); +} + +Map _decodeLocale(File file) => jsonDecode(file.readAsStringSync()) as Map; + +String? _valueAt(Map locale, List path) { + Object? value = locale; + for (final segment in path) { + if (value is! Map || !value.containsKey(segment)) return null; + value = value[segment]; + } + return value as String?; +} diff --git a/test/models/shader_preset_test.dart b/test/models/shader_preset_test.dart index 05b9d22b..4ad7ea28 100644 --- a/test/models/shader_preset_test.dart +++ b/test/models/shader_preset_test.dart @@ -3,21 +3,88 @@ import 'package:plezy/models/shader_preset.dart'; void main() { group('ShaderPreset ArtCNN presets', () { - test('exposes stable built-in preset ids in the expected order', () { - final ids = ShaderPreset.allPresets.map((preset) => preset.id).toList(); - - expect(ids.take(8), [ - ShaderPreset.none.id, - ShaderPreset.nvscalerDefault.id, - 'artcnn_c4f16_neutral', - 'artcnn_c4f16_dn', - 'artcnn_c4f16_ds', - 'artcnn_c4f32_neutral', - 'artcnn_c4f32_dn', - 'artcnn_c4f32_ds', + test('exposes the complete built-in catalog in stable order', () { + expect(ShaderPreset.allPresets.map((preset) => preset.toJson()).toList(), [ + {'id': 'none', 'name': 'Off', 'type': 'none'}, + { + 'id': 'nvscaler', + 'name': 'NVScaler', + 'type': 'nvscaler', + 'nvscalerConfig': {'autoHdrSkip': true}, + }, + { + 'id': 'artcnn_c4f16_neutral', + 'name': 'ArtCNN C4F16', + 'type': 'artcnn', + 'artcnnConfig': {'model': 'c4f16', 'variant': 'neutral'}, + }, + { + 'id': 'artcnn_c4f16_dn', + 'name': 'ArtCNN C4F16 Denoise', + 'type': 'artcnn', + 'artcnnConfig': {'model': 'c4f16', 'variant': 'denoise'}, + }, + { + 'id': 'artcnn_c4f16_ds', + 'name': 'ArtCNN C4F16 Denoise + Sharpen', + 'type': 'artcnn', + 'artcnnConfig': {'model': 'c4f16', 'variant': 'denoiseSharpen'}, + }, + { + 'id': 'artcnn_c4f32_neutral', + 'name': 'ArtCNN C4F32', + 'type': 'artcnn', + 'artcnnConfig': {'model': 'c4f32', 'variant': 'neutral'}, + }, + { + 'id': 'artcnn_c4f32_dn', + 'name': 'ArtCNN C4F32 Denoise', + 'type': 'artcnn', + 'artcnnConfig': {'model': 'c4f32', 'variant': 'denoise'}, + }, + { + 'id': 'artcnn_c4f32_ds', + 'name': 'ArtCNN C4F32 Denoise + Sharpen', + 'type': 'artcnn', + 'artcnnConfig': {'model': 'c4f32', 'variant': 'denoiseSharpen'}, + }, + for (final entry in const [ + ('fast', 'modeA', 'Anime4K Fast A'), + ('fast', 'modeB', 'Anime4K Fast B'), + ('fast', 'modeC', 'Anime4K Fast C'), + ('fast', 'modeAA', 'Anime4K Fast A+A'), + ('fast', 'modeBB', 'Anime4K Fast B+B'), + ('fast', 'modeCA', 'Anime4K Fast C+A'), + ('hq', 'modeA', 'Anime4K HQ A'), + ('hq', 'modeB', 'Anime4K HQ B'), + ('hq', 'modeC', 'Anime4K HQ C'), + ('hq', 'modeAA', 'Anime4K HQ A+A'), + ('hq', 'modeBB', 'Anime4K HQ B+B'), + ('hq', 'modeCA', 'Anime4K HQ C+A'), + ]) + { + 'id': 'anime4k_${entry.$1}_${entry.$2}', + 'name': entry.$3, + 'type': 'anime4k', + 'anime4kConfig': {'quality': entry.$1, 'mode': entry.$2}, + }, ]); }); + test('shares an unmodifiable catalog and canonical id lookup', () { + final first = ShaderPreset.allPresets; + final second = ShaderPreset.allPresets; + + expect(identical(first, second), isTrue); + expect(() => first.add(ShaderPreset.none), throwsUnsupportedError); + expect(() => first.removeLast(), throwsUnsupportedError); + for (final preset in first) { + expect(identical(ShaderPreset.fromId(preset.id), preset), isTrue); + expect(identical(ShaderPreset.fromJson(preset.toJson()), preset), isTrue); + } + expect(ShaderPreset.fromId('unknown'), isNull); + }); + test('creates ArtCNN presets with names, type, and config', () { final neutral = ShaderPreset.artcnnPreset(ArtCNNModel.c4f16, ArtCNNVariant.neutral); final denoise = ShaderPreset.artcnnPreset(ArtCNNModel.c4f32, ArtCNNVariant.denoise); diff --git a/test/providers/shader_provider_test.dart b/test/providers/shader_provider_test.dart index 31d70d4c..9cf7ee72 100644 --- a/test/providers/shader_provider_test.dart +++ b/test/providers/shader_provider_test.dart @@ -1,9 +1,17 @@ +import 'dart:io'; + +import 'dart:convert'; +import 'package:path/path.dart' as path; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/models/shader_preset.dart'; import 'package:plezy/providers/shader_provider.dart'; import 'package:plezy/services/base_shared_preferences_service.dart'; import 'package:plezy/services/settings_service.dart'; +import 'package:plezy/services/settings_export_service.dart'; +import '../test_helpers/io_fakes.dart'; import '../test_helpers/prefs.dart'; void main() { @@ -163,6 +171,117 @@ void main() { p.dispose(); }); + test('reuses the unmodifiable built-in catalog when no custom presets exist', () async { + final p = ShaderProvider(); + await Future.delayed(Duration.zero); + + expect(identical(p.allPresets, ShaderPreset.allPresets), isTrue); + expect(identical(p.allPresets, p.allPresets), isTrue); + expect(() => p.allPresets.add(ShaderPreset.none), throwsUnsupportedError); + p.dispose(); + }); + + test('refreshes a stable merged catalog when custom presets change', () async { + final originalPathProvider = PathProviderPlatform.instance; + final root = await Directory.systemTemp.createTemp('plezy_shader_provider_test_'); + PathProviderPlatform.instance = FakePathProvider(root); + addTearDown(() async { + PathProviderPlatform.instance = originalPathProvider; + if (await root.exists()) await root.delete(recursive: true); + }); + final source = File(path.join(root.path, 'custom.glsl'))..writeAsStringSync('shader'); + final p = ShaderProvider(); + addTearDown(p.dispose); + await Future.delayed(Duration.zero); + final builtIns = p.allPresets; + + final custom = await p.importCustomShader(source.path, 'Custom'); + final merged = p.allPresets; + expect(identical(merged, builtIns), isFalse); + expect(identical(merged, p.allPresets), isTrue); + expect(merged.last, custom); + expect(() => merged.removeLast(), throwsUnsupportedError); + + await p.deleteCustomShader(custom); + expect(p.findPresetById(custom.id), isNull); + expect(identical(p.allPresets, ShaderPreset.allPresets), isTrue); + }); + + test('filters unsafe persisted custom shader rows and invalid saved selection', () async { + const valid = ShaderPreset( + id: 'custom_ks9p7.glsl', + name: 'Legacy', + type: ShaderPresetType.custom, + fileName: 'ks9p7.glsl', + ); + const unsafeRows = [ + ShaderPreset( + id: 'custom_traversal', + name: 'Traversal', + type: ShaderPresetType.custom, + fileName: '../sentinel.glsl', + ), + ShaderPreset( + id: 'custom_nested', + name: 'Nested', + type: ShaderPresetType.custom, + fileName: 'nested/shader.glsl', + ), + ShaderPreset( + id: 'custom_wrong_type', + name: 'Wrong type', + type: ShaderPresetType.custom, + fileName: 'shader.txt', + ), + ]; + final svc = await SettingsService.getInstance(); + await svc.write(SettingsService.customShaderPresets, [valid.toJson(), ...unsafeRows.map((row) => row.toJson())]); + await svc.write(SettingsService.globalShaderPreset, unsafeRows.first.id); + + final p = ShaderProvider(); + await Future.delayed(Duration.zero); + + expect(p.customPresets, [valid]); + expect(p.findPresetById(unsafeRows.first.id), isNull); + expect(p.savedPreset, ShaderPreset.none); + expect(p.currentPreset, ShaderPreset.none); + p.dispose(); + }); + + test('portable settings import cannot expose an unsafe custom shader row', () async { + const unsafe = ShaderPreset( + id: 'custom_imported_traversal', + name: 'Imported traversal', + type: ShaderPresetType.custom, + fileName: '../sentinel.glsl', + ); + final prefs = await BaseSharedPreferencesService.sharedCache(); + final result = await SettingsExportService.applyImportMap( + { + 'formatVersion': SettingsExportService.formatVersion, + 'prefs': { + 'custom_shader_presets': { + 'type': 'string', + 'value': jsonEncode([unsafe.toJson()]), + }, + }, + }, + prefs, + currentUserUuid: 'profile', + ); + expect(result.keysImported, 0); + final svc = await SettingsService.getInstance(); + await svc.write(SettingsService.globalShaderPreset, unsafe.id); + + final p = ShaderProvider(); + await Future.delayed(Duration.zero); + + expect(p.customPresets, isEmpty); + expect(p.findPresetById(unsafe.id), isNull); + expect(p.savedPreset, ShaderPreset.none); + p.dispose(); + }); + test('safeNotifyListeners no-ops after dispose', () async { final p = ShaderProvider(); await Future.delayed(Duration.zero); diff --git a/test/screens/catalog_item_detail_screen_test.dart b/test/screens/catalog_item_detail_screen_test.dart index 539b2a61..4e43dad2 100644 --- a/test/screens/catalog_item_detail_screen_test.dart +++ b/test/screens/catalog_item_detail_screen_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -26,6 +28,13 @@ import '../test_helpers/prefs.dart'; class _FakeCatalogSource implements CatalogSource { final WatchlistChangeNotifier _watchlistChanges = WatchlistChangeNotifier(); + _FakeCatalogSource({bool watchlistLoading = false}) + : _watchlistValue = watchlistLoading ? null : false, + _watchlistLoad = watchlistLoading ? Completer() : null; + + bool? _watchlistValue; + final Completer? _watchlistLoad; + int addToWatchlistCalls = 0; @override CatalogSourceId get id => CatalogSourceId.trakt; @@ -56,7 +65,26 @@ class _FakeCatalogSource implements CatalogSource { ]; @override - bool? isOnWatchlist(MediaKind kind, CatalogItemIds ids) => false; + Future ensureWatchlistLoaded() async { + final load = _watchlistLoad; + if (load != null) await load.future; + } + + void completeWatchlistLoad() { + _watchlistValue = false; + _watchlistChanges.notify(); + _watchlistLoad!.complete(); + } + + @override + Future addToWatchlist(MediaKind kind, CatalogItemIds ids) async { + addToWatchlistCalls++; + _watchlistValue = true; + _watchlistChanges.notify(); + } + + @override + bool? isOnWatchlist(MediaKind kind, CatalogItemIds ids) => _watchlistValue; @override void dispose() => _watchlistChanges.dispose(); @@ -216,6 +244,35 @@ void main() { expect(FocusManager.instance.primaryFocus?.debugLabel, 'catalog_library_match_1'); }); + testWidgets('loading watchlist action cannot receive focus or activate', (tester) async { + final source = _FakeCatalogSource(watchlistLoading: true); + await _pumpDetail(tester, source); + + final actionBar = tester.widget(find.byType(FocusableActionBar)); + expect(actionBar.actions.single.onPressed, isNull); + final actionNode = tester + .widgetList(find.descendant(of: find.byType(FocusableActionBar), matching: find.byType(Focus))) + .map((widget) => widget.focusNode) + .whereType() + .singleWhere((node) => node.debugLabel == 'ActionBar[0]'); + expect(actionNode.canRequestFocus, isFalse); + + actionNode.requestFocus(); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.select); + expect(actionNode.hasFocus, isFalse); + expect(source.addToWatchlistCalls, 0); + + source.completeWatchlistLoad(); + await tester.pump(); + final loadedActionBar = tester.widget(find.byType(FocusableActionBar)); + expect(loadedActionBar.actions.single.onPressed, isNotNull); + actionNode.requestFocus(); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.select); + await tester.pump(); + expect(source.addToWatchlistCalls, 1); + }); testWidgets('TV Back closes a hosted sheet without popping the catalog route', (tester) async { await _pumpDetail(tester, _FakeCatalogSource(), pushedRoute: true); diff --git a/test/screens/collection_detail_screen_test.dart b/test/screens/collection_detail_screen_test.dart index ff6aae5e..4da96f39 100644 --- a/test/screens/collection_detail_screen_test.dart +++ b/test/screens/collection_detail_screen_test.dart @@ -24,6 +24,7 @@ import 'package:plezy/theme/mono_theme.dart'; import 'package:plezy/utils/media_server_http_client.dart'; import 'package:plezy/utils/platform_detector.dart'; import 'package:plezy/widgets/focusable_media_card.dart'; +import 'package:plezy/widgets/media_card.dart'; import 'package:plezy/widgets/media_card_sliver_layout.dart'; import 'package:provider/provider.dart'; @@ -66,6 +67,42 @@ void main() { expect(layout.fullBleedImage, isFalse); expect(tester.widget(find.byType(FocusableMediaCard)).cardShapeOverride, CardShape.square); }); + + testWidgets('collection cards announce list position without duplicate actions', (tester) async { + final semantics = tester.ensureSemantics(); + final items = [ + testMediaItem( + id: 'movie_1', + backend: MediaBackend.plex, + kind: MediaKind.movie, + title: 'First movie', + serverId: 'server_1', + serverName: 'Server', + ), + testMediaItem( + id: 'movie_2', + backend: MediaBackend.plex, + kind: MediaKind.movie, + title: 'Second movie', + serverId: 'server_1', + serverName: 'Server', + ), + ]; + final harness = await _createHarness(items); + await SettingsService.instance.write(SettingsService.viewMode, ViewMode.list); + + await tester.pumpWidget( + harness.wrap(SizedBox(width: 1280, height: 720, child: CollectionDetailScreen(collection: _collection))), + ); + await tester.pumpAndSettle(); + + final cards = tester.widgetList(find.byType(FocusableMediaCard)).toList(); + expect(cards.map((card) => card.semanticValue), ['Row 1 of 2', 'Row 2 of 2']); + + final first = tester.getSemantics(find.bySemanticsLabel(mediaCardSemanticLabel(items.first))).getSemanticsData(); + expect(first.value, 'Row 1 of 2'); + semantics.dispose(); + }); } final _collection = MediaItem( diff --git a/test/screens/libraries/alpha_jump_bar_test.dart b/test/screens/libraries/alpha_jump_bar_test.dart index e52be905..ca6219c0 100644 --- a/test/screens/libraries/alpha_jump_bar_test.dart +++ b/test/screens/libraries/alpha_jump_bar_test.dart @@ -1,8 +1,11 @@ +import 'dart:ui' show SemanticsAction, Tristate; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/focus/dpad_navigator.dart'; import 'package:plezy/focus/key_event_utils.dart'; +import 'package:plezy/i18n/strings.g.dart'; import 'package:plezy/media/library_first_character.dart'; import 'package:plezy/screens/libraries/alpha_jump_bar.dart'; import 'package:plezy/utils/platform_detector.dart'; @@ -215,6 +218,43 @@ void main() { expect(jumpedTo, 2); }); + testWidgets('exposes operable letter buttons with selected state', (tester) async { + final semantics = tester.ensureSemantics(); + final focusNode = FocusNode(debugLabel: 'test_alpha_jump_semantics'); + addTearDown(focusNode.dispose); + int? jumpedTo; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox( + height: 300, + child: AlphaJumpBar( + firstCharacters: const [ + LibraryFirstCharacter(key: 'A', title: 'A', size: 3), + LibraryFirstCharacter(key: 'B', title: 'B', size: 4), + LibraryFirstCharacter(key: 'C', title: 'C', size: 2), + ], + currentLetter: 'B', + focusNode: focusNode, + onJump: (index) => jumpedTo = index, + ), + ), + ), + ), + ); + + expect(find.bySemanticsLabel(t.accessibility.alphabetNavigation), findsOneWidget); + final selected = tester.getSemantics(find.bySemanticsLabel('B')).getSemanticsData(); + expect(selected.flagsCollection.isButton, isTrue); + expect(selected.flagsCollection.isSelected, Tristate.isTrue); + expect(selected.hasAction(SemanticsAction.tap), isTrue); + + final cNode = tester.getSemantics(find.bySemanticsLabel('C')); + cNode.owner!.performAction(cNode.id, SemanticsAction.tap); + expect(jumpedTo, 7); + semantics.dispose(); + }); } class _BackKeyCase { diff --git a/test/screens/libraries/alpha_scroll_handle_test.dart b/test/screens/libraries/alpha_scroll_handle_test.dart new file mode 100644 index 00000000..2a647264 --- /dev/null +++ b/test/screens/libraries/alpha_scroll_handle_test.dart @@ -0,0 +1,78 @@ +import 'dart:ui' show SemanticsAction; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/i18n/strings.g.dart'; +import 'package:plezy/media/library_first_character.dart'; +import 'package:plezy/screens/libraries/alpha_scroll_handle.dart'; + +void main() { + const characters = [ + LibraryFirstCharacter(key: 'A', title: 'A', size: 2), + LibraryFirstCharacter(key: 'B', title: 'B', size: 3), + LibraryFirstCharacter(key: 'C', title: 'C', size: 4), + ]; + + Widget buildHandle({required bool isScrolling, required ValueChanged onJump}) { + return MaterialApp( + home: Scaffold( + body: Align( + alignment: Alignment.centerRight, + child: SizedBox( + height: 300, + child: AlphaScrollHandle( + firstCharacters: characters, + currentLetter: 'B', + isScrolling: isScrolling, + onJump: onJump, + ), + ), + ), + ), + ); + } + + testWidgets('keeps adjustable semantics mounted while fully hidden', (tester) async { + final semantics = tester.ensureSemantics(); + final jumps = []; + + await tester.pumpWidget(buildHandle(isScrolling: false, onJump: jumps.add)); + + final finder = find.bySemanticsLabel(t.accessibility.alphabetNavigation); + expect(finder, findsOneWidget); + final node = tester.getSemantics(finder); + final data = node.getSemanticsData(); + expect(data.value, 'B'); + expect(data.hasAction(SemanticsAction.increase), isTrue); + expect(data.hasAction(SemanticsAction.decrease), isTrue); + expect(tester.widget(find.byType(IgnorePointer).last).ignoring, isTrue); + + node.owner!.performAction(node.id, SemanticsAction.increase); + expect(jumps, [5]); + semantics.dispose(); + }); + + testWidgets('announces the current letter and exposes bounded letter steps', (tester) async { + final semantics = tester.ensureSemantics(); + final jumps = []; + + await tester.pumpWidget(buildHandle(isScrolling: false, onJump: jumps.add)); + await tester.pumpWidget(buildHandle(isScrolling: true, onJump: jumps.add)); + await tester.pump(const Duration(milliseconds: 250)); + + final finder = find.bySemanticsLabel(t.accessibility.alphabetNavigation); + expect(finder, findsOneWidget); + final node = tester.getSemantics(finder); + final data = node.getSemanticsData(); + expect(data.value, 'B'); + expect(data.hint, t.accessibility.alphabetScrollHint); + expect(data.hasAction(SemanticsAction.increase), isTrue); + expect(data.hasAction(SemanticsAction.decrease), isTrue); + + node.owner!.performAction(node.id, SemanticsAction.increase); + expect(jumps, [5]); + node.owner!.performAction(node.id, SemanticsAction.decrease); + expect(jumps, [5, 0]); + semantics.dispose(); + }); +} diff --git a/test/screens/profile/add_local_profile_screen_test.dart b/test/screens/profile/add_local_profile_screen_test.dart index 00f39bd9..2f80e234 100644 --- a/test/screens/profile/add_local_profile_screen_test.dart +++ b/test/screens/profile/add_local_profile_screen_test.dart @@ -38,6 +38,10 @@ void main() { expect(FocusManager.instance.primaryFocus?.debugLabel, 'TvVirtualKeyboard'); expect(find.byKey(const Key('tv_virtual_keyboard_panel')), findsOneWidget); + await tester.sendKeyEvent(LogicalKeyboardKey.select); + await tester.pump(); + expect(tester.widget(find.byType(TextField)).controller!.text, isNotEmpty); + await tester.sendKeyEvent(LogicalKeyboardKey.gameButtonB); await tester.pumpAndSettle(); diff --git a/test/screens/profile/borrow_connection_screen_test.dart b/test/screens/profile/borrow_connection_screen_test.dart new file mode 100644 index 00000000..a3956508 --- /dev/null +++ b/test/screens/profile/borrow_connection_screen_test.dart @@ -0,0 +1,103 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:drift/native.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/connection/connection_registry.dart'; +import 'package:plezy/database/app_database.dart'; +import 'package:plezy/focus/input_mode_tracker.dart'; +import 'package:plezy/i18n/strings.g.dart'; +import 'package:plezy/profiles/plex_home_service.dart'; +import 'package:plezy/profiles/profile.dart'; +import 'package:plezy/profiles/profile_connection_registry.dart'; +import 'package:plezy/profiles/profile_registry.dart'; +import 'package:plezy/screens/profile/borrow_connection_screen.dart'; +import 'package:plezy/services/storage_service.dart'; +import 'package:plezy/theme/mono_theme.dart'; +import 'package:provider/provider.dart'; + +import '../../test_helpers/prefs.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + resetSharedPreferencesForTest(); + LocaleSettings.setLocaleSync(AppLocale.en); + }); + + testWidgets('load failure has retry and remains distinct from successful empty state', (tester) async { + final db = AppDatabase.forTesting(NativeDatabase.memory()); + final connections = ConnectionRegistry(db); + final profileConnections = ProfileConnectionRegistry(db); + final profiles = ProfileRegistry(db); + final storage = await StorageService.getInstance(); + final plexHome = _ControlledPlexHomeService( + connections: connections, + profileConnections: profileConnections, + storage: storage, + ); + addTearDown(() async { + await plexHome.dispose(); + await db.close(); + }); + + final target = Profile.local(id: 'target', displayName: 'Target', createdAt: DateTime(2026)); + + await tester.pumpWidget( + TranslationProvider( + child: MultiProvider( + providers: [ + Provider.value(value: profileConnections), + Provider.value(value: connections), + Provider.value(value: profiles), + Provider.value(value: plexHome), + ], + child: InputModeTracker( + child: MaterialApp( + theme: monoTheme(dark: true), + home: BorrowConnectionScreen(targetProfile: target), + ), + ), + ), + ), + ); + + expect(plexHome.starts, hasLength(1)); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + plexHome.starts.first.completeError(StateError('load failed')); + await tester.pumpAndSettle(); + + expect(find.text(t.profiles.borrowLoadFailed), findsOneWidget); + expect(find.text(t.common.retry), findsOneWidget); + expect(find.text(t.profiles.borrowEmpty), findsNothing); + + await tester.tap(find.text(t.common.retry)); + await tester.pump(); + + expect(plexHome.starts, hasLength(2)); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + expect(find.text(t.profiles.borrowLoadFailed), findsNothing); + + plexHome.starts.last.complete(); + await tester.pumpAndSettle(); + + expect(find.text(t.profiles.borrowEmpty), findsOneWidget); + expect(find.text(t.profiles.borrowLoadFailed), findsNothing); + }); +} + +class _ControlledPlexHomeService extends PlexHomeService { + _ControlledPlexHomeService({required super.connections, required super.profileConnections, required super.storage}); + + final Queue> starts = Queue(); + + @override + Future start() { + final completer = Completer(); + starts.add(completer); + return completer.future; + } +} diff --git a/test/screens/settings/keyboard_shortcuts_screen_test.dart b/test/screens/settings/keyboard_shortcuts_screen_test.dart new file mode 100644 index 00000000..850f82d2 --- /dev/null +++ b/test/screens/settings/keyboard_shortcuts_screen_test.dart @@ -0,0 +1,167 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/i18n/strings.g.dart'; +import 'package:plezy/screens/settings/keyboard_shortcuts_screen.dart'; +import 'package:plezy/services/base_shared_preferences_service.dart'; +import 'package:plezy/services/keyboard_shortcuts_service.dart'; +import 'package:plezy/services/settings_service.dart'; +import 'package:plezy/theme/mono_theme.dart'; +import 'package:plezy/widgets/dialog_action_button.dart'; +import 'package:plezy/widgets/focusable_list_tile.dart'; +import 'package:plezy/widgets/hotkey_recorder.dart'; +import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; + +import '../../test_helpers/prefs.dart'; + +void main() { + setUpAll(() => LocaleSettings.setLocaleSync(AppLocale.en)); + + setUp(() { + resetSharedPreferencesForTest(); + SettingsService.resetForTesting(); + }); + + testWidgets('clear persists an unassigned row that can be rebound', (tester) async { + final service = await KeyboardShortcutsService.getInstance(); + addTearDown(service.dispose); + await _pumpScreen(tester, service); + + await _openAction(tester, service, 'play_pause'); + await tester.tap(find.byTooltip(t.hotkeys.clearShortcut)); + await tester.pump(); + await tester.tap(_saveButton()); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + expect(service.getHotkey('play_pause'), isNull); + expect(find.text(t.hotkeys.noShortcutSet), findsOneWidget); + final stored = + json.decode(SettingsService.instance.prefs.getString(SettingsService.keyboardHotkeys.key)!) + as Map; + expect(stored['play_pause'], {'disabled': true}); + + await _openAction(tester, service, 'play_pause'); + expect(tester.widget(find.byType(HotKeyRecorder)).initalHotKey, isNull); + await tester.tap(_recorderSurface()); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.keyK, physicalKey: PhysicalKeyboardKey.keyK); + await tester.pump(); + await tester.pump(); + await tester.tap(_saveButton()); + await tester.pumpAndSettle(); + + expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.keyK); + expect(find.text(service.formatHotkey(service.getHotkey('play_pause'))), findsOneWidget); + }); + + testWidgets('clear then cancel keeps the persisted assignment', (tester) async { + final service = await KeyboardShortcutsService.getInstance(); + addTearDown(service.dispose); + await _pumpScreen(tester, service); + + await _openAction(tester, service, 'play_pause'); + await tester.tap(find.byTooltip(t.hotkeys.clearShortcut)); + await tester.pump(); + await tester.tap(find.widgetWithText(TextButton, t.common.cancel)); + await tester.pumpAndSettle(); + + expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.space); + expect(find.text(service.formatHotkey(service.getHotkey('play_pause'))), findsOneWidget); + }); + + testWidgets('conflicting recording is rejected without changing either action', (tester) async { + final service = await KeyboardShortcutsService.getInstance(); + addTearDown(service.dispose); + await _pumpScreen(tester, service); + + await _openAction(tester, service, 'play_pause'); + await tester.tap(_recorderSurface()); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp, physicalKey: PhysicalKeyboardKey.arrowUp); + await tester.pump(); + await tester.pump(); + await tester.tap(_saveButton()); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.space); + expect(service.getHotkey('volume_up')?.key, PhysicalKeyboardKey.arrowUp); + expect( + find.text(t.settings.shortcutAlreadyAssigned(action: service.getActionDisplayName('volume_up'))), + findsOneWidget, + ); + }); + + testWidgets('persistence failure keeps the dialog retryable and the row unchanged', (tester) async { + final preferences = _FailingHotkeyPreferences(const {}); + SharedPreferencesAsyncPlatform.instance = preferences; + SettingsService.resetForTesting(); + BaseSharedPreferencesService.resetForTesting(); + final service = await KeyboardShortcutsService.getInstance(); + addTearDown(service.dispose); + await _pumpScreen(tester, service); + + await _openAction(tester, service, 'play_pause'); + await tester.tap(find.byTooltip(t.hotkeys.clearShortcut)); + await tester.pump(); + preferences.failNextHotkeyWrite = true; + await tester.tap(_saveButton()); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.space); + expect(find.text(t.common.error), findsOneWidget); + expect( + tester.widget(find.widgetWithText(DialogActionButton, t.common.save)).onPressed, + isNotNull, + ); + + await tester.tap(_saveButton()); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsNothing); + expect(service.getHotkey('play_pause'), isNull); + }); +} + +Future _pumpScreen(WidgetTester tester, KeyboardShortcutsService service) async { + await tester.pumpWidget( + TranslationProvider( + child: MaterialApp( + theme: monoTheme(dark: true), + home: KeyboardShortcutsScreen(keyboardService: service), + ), + ), + ); + await tester.pumpAndSettle(); +} + +Future _openAction(WidgetTester tester, KeyboardShortcutsService service, String action) async { + await tester.tap(find.widgetWithText(FocusableListTile, service.getActionDisplayName(action))); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); +} + +Finder _recorderSurface() => + find.ancestor(of: find.byType(HotKeyRecorder), matching: find.byType(GestureDetector)).first; + +Finder _saveButton() => find.widgetWithText(FilledButton, t.common.save); + +final class _FailingHotkeyPreferences extends InMemorySharedPreferencesAsync { + _FailingHotkeyPreferences(super.data) : super.withData(); + + bool failNextHotkeyWrite = false; + + @override + Future setString(String key, String value, SharedPreferencesOptions options) { + if (key == SettingsService.keyboardHotkeys.key && failNextHotkeyWrite) { + failNextHotkeyWrite = false; + throw PlatformException(code: 'write_failed'); + } + return super.setString(key, value, options); + } +} diff --git a/test/screens/settings/mpv_config_screen_test.dart b/test/screens/settings/mpv_config_screen_test.dart new file mode 100644 index 00000000..58a64881 --- /dev/null +++ b/test/screens/settings/mpv_config_screen_test.dart @@ -0,0 +1,330 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/i18n/strings.g.dart'; +import 'package:plezy/models/mpv_config_models.dart'; +import 'package:plezy/screens/settings/mpv_config_screen.dart'; +import 'package:plezy/services/base_shared_preferences_service.dart'; +import 'package:plezy/services/settings_service.dart'; +import 'package:plezy/theme/mono_theme.dart'; +import 'package:plezy/widgets/dialog_action_button.dart'; +import 'package:plezy/widgets/focusable_list_tile.dart'; +import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; + +import '../../test_helpers/prefs.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUpAll(() => LocaleSettings.setLocaleSync(AppLocale.en)); + + testWidgets('typing burst coalesces to one full-document write', (tester) async { + final backend = await _pumpEditor(tester, holdConfigWrites: true); + + await tester.enterText(find.byType(TextField), 'vo'); + await tester.enterText(find.byType(TextField), 'vol'); + await tester.enterText(find.byType(TextField), 'volume=80'); + + await tester.pump(const Duration(milliseconds: 399)); + expect(backend.configWrites, isEmpty); + + await tester.pump(const Duration(milliseconds: 1)); + expect(backend.configWrites, ['volume=80']); + expect(backend.maxActiveConfigWrites, 1); + + backend.completeNextConfigWrite(); + await tester.pump(); + + expect(backend.durableConfig, 'volume=80'); + }); + + testWidgets('new revisions wait for the active write and only the latest follows', (tester) async { + final backend = await _pumpEditor(tester, holdConfigWrites: true); + + await tester.enterText(find.byType(TextField), 'first=1'); + await tester.pump(const Duration(milliseconds: 400)); + expect(backend.configWrites, ['first=1']); + + await tester.enterText(find.byType(TextField), 'second=2'); + await tester.enterText(find.byType(TextField), 'final=3'); + await tester.pump(const Duration(milliseconds: 400)); + + expect(backend.configWrites, ['first=1']); + expect(backend.activeConfigWrites, 1); + + backend.completeNextConfigWrite(); + await tester.pump(); + + expect(backend.configWrites, ['first=1', 'final=3']); + expect(backend.maxActiveConfigWrites, 1); + expect(tester.widget(find.byType(TextField)).controller!.text, 'final=3'); + + backend.completeNextConfigWrite(); + await tester.pump(); + + expect(backend.durableConfig, 'final=3'); + }); + + testWidgets('manual Enter insertion joins the debounced writer', (tester) async { + final backend = await _pumpEditor(tester, holdConfigWrites: true); + + await tester.enterText(find.byType(TextField), 'alpha=1'); + await tester.pump(const Duration(milliseconds: 400)); + backend.completeNextConfigWrite(); + await tester.pump(); + backend.configWrites.clear(); + + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + expect(tester.widget(find.byType(TextField)).controller!.text, 'alpha=1\n'); + + await tester.pump(const Duration(milliseconds: 399)); + expect(backend.configWrites, isEmpty); + await tester.pump(const Duration(milliseconds: 1)); + expect(backend.configWrites, ['alpha=1\n']); + + backend.completeNextConfigWrite(); + await tester.pump(); + expect(backend.durableConfig, 'alpha=1\n'); + }); + + testWidgets('observed external replacement wins after an in-flight local write', (tester) async { + final backend = await _pumpEditor(tester, holdConfigWrites: true); + final settings = SettingsService.instance; + + await tester.enterText(find.byType(TextField), 'local=old'); + await tester.pump(const Duration(milliseconds: 400)); + expect(backend.configWrites, ['local=old']); + + unawaited(settings.write(SettingsService.mpvConfigText, 'external=new')); + await tester.pump(); + expect(backend.configWrites, ['local=old', 'external=new']); + + backend.completeConfigWriteAt(1); + await tester.pump(); + expect(tester.widget(find.byType(TextField)).controller!.text, 'external=new'); + + backend.completeNextConfigWrite(); + await tester.pump(); + expect(backend.configWrites, ['local=old', 'external=new', 'external=new']); + + backend.completeNextConfigWrite(); + await tester.pump(); + expect(backend.durableConfig, 'external=new'); + }); + + testWidgets('focus loss flushes immediately and route pop waits for persistence', (tester) async { + final backend = await _pumpEditor(tester, holdConfigWrites: true); + + await tester.enterText(find.byType(TextField), 'profile=gpu-hq'); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pump(); + + expect(backend.configWrites, ['profile=gpu-hq']); + + unawaited(tester.binding.handlePopRoute()); + await tester.pump(); + expect(find.byType(MpvConfigScreen), findsOneWidget); + + backend.completeNextConfigWrite(); + await tester.pumpAndSettle(); + + expect(find.byType(MpvConfigScreen), findsNothing); + expect(find.text('home'), findsOneWidget); + expect(backend.durableConfig, 'profile=gpu-hq'); + }); + + testWidgets('dispose flushes its captured edit without using disposed UI', (tester) async { + final backend = await _pumpEditor(tester, holdConfigWrites: true); + + await tester.enterText(find.byType(TextField), 'video-sync=display-resample'); + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pump(); + + expect(backend.configWrites, ['video-sync=display-resample']); + + backend.completeNextConfigWrite(); + await tester.pump(); + + expect(tester.takeException(), isNull); + expect(backend.durableConfig, 'video-sync=display-resample'); + }); + + testWidgets('failed flush stays retryable and does not pop until retry succeeds', (tester) async { + final backend = await _pumpEditor(tester, holdConfigWrites: true); + const config = 'gpu-api=secret-test-value'; + + await tester.enterText(find.byType(TextField), config); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pump(); + + backend.completeNextConfigWrite(error: PlatformException(code: 'write_failed')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + + expect(find.text(t.settings.saveFailed), findsOneWidget); + expect(find.textContaining('write_failed'), findsNothing); + expect(backend.durableConfig, isNull); + + unawaited(tester.binding.handlePopRoute()); + await tester.pump(); + expect(backend.configWrites, [config, config]); + expect(find.byType(MpvConfigScreen), findsOneWidget); + + backend.completeNextConfigWrite(); + await tester.pumpAndSettle(); + + expect(find.byType(MpvConfigScreen), findsNothing); + expect(backend.durableConfig, config); + }); + + testWidgets('saving a preset flushes the matching active text before success', (tester) async { + final backend = await _pumpEditor(tester, holdConfigWrites: true); + final settings = SettingsService.instance; + + await tester.enterText(find.byType(TextField), 'preset-source=yes'); + await tester.pump(); + final saveTile = tester.widget(find.widgetWithText(FocusableListTile, t.mpvConfig.saveAsPreset)); + expect(saveTile.enabled, isTrue); + saveTile.onTap!(); + await tester.pumpAndSettle(); + + final dialog = find.byType(AlertDialog); + expect(dialog, findsOneWidget); + final nameField = find.descendant(of: dialog, matching: find.byType(TextField)); + expect(nameField, findsOneWidget); + await tester.enterText(nameField, 'Saved'); + await tester.tap(find.widgetWithText(DialogActionButton, t.common.save)); + await tester.pump(); + + expect(settings.read(SettingsService.mpvPresets), isEmpty); + expect(find.text(t.mpvConfig.presetSaved), findsNothing); + + backend.completeNextConfigWrite(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + + final presets = settings.read(SettingsService.mpvPresets); + expect(presets, hasLength(1)); + expect(presets.single.name, 'Saved'); + expect(presets.single.text, 'preset-source=yes'); + expect(find.text(t.mpvConfig.presetSaved), findsOneWidget); + }); + + testWidgets('preset replacement waits behind an active edit and wins last', (tester) async { + final preset = MpvPreset(name: 'Cinema', text: 'profile=gpu-hq', createdAt: DateTime(2026)); + final backend = await _pumpEditor(tester, holdConfigWrites: true, presets: [preset]); + + await tester.enterText(find.byType(TextField), 'old-edit=yes'); + await tester.pump(const Duration(milliseconds: 400)); + expect(backend.configWrites, ['old-edit=yes']); + + await tester.tap(find.text('Cinema')); + await tester.pump(); + expect(backend.configWrites, ['old-edit=yes']); + expect(find.text(t.mpvConfig.presetLoaded), findsNothing); + + backend.completeNextConfigWrite(); + await tester.pump(); + expect(backend.configWrites, ['old-edit=yes', 'profile=gpu-hq']); + expect(backend.maxActiveConfigWrites, 1); + + backend.completeNextConfigWrite(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + + expect(find.text(t.mpvConfig.presetLoaded), findsOneWidget); + expect(backend.durableConfig, 'profile=gpu-hq'); + }); + + testWidgets('clean external settings update replaces the editor', (tester) async { + final backend = await _pumpEditor(tester); + final settings = SettingsService.instance; + + await settings.write(SettingsService.mpvConfigText, 'external=yes'); + await tester.pump(); + + expect(tester.widget(find.byType(TextField)).controller!.text, 'external=yes'); + expect(backend.durableConfig, 'external=yes'); + }); +} + +Future<_ControlledPreferences> _pumpEditor( + WidgetTester tester, { + bool holdConfigWrites = false, + List presets = const [], +}) async { + resetSharedPreferencesForTest(); + final initial = { + if (presets.isNotEmpty) + SettingsService.mpvPresets.key: jsonEncode(presets.map((preset) => preset.toJson()).toList()), + }; + final backend = _ControlledPreferences(initial)..holdConfigWrites = holdConfigWrites; + SharedPreferencesAsyncPlatform.instance = backend; + BaseSharedPreferencesService.resetForTesting(); + SettingsService.resetForTesting(); + await SettingsService.getInstance(); + + final navigatorKey = GlobalKey(); + await tester.pumpWidget( + TranslationProvider( + child: MaterialApp( + navigatorKey: navigatorKey, + theme: monoTheme(dark: true), + home: const Scaffold(body: Text('home')), + ), + ), + ); + unawaited(navigatorKey.currentState!.push(MaterialPageRoute(builder: (_) => const MpvConfigScreen()))); + await tester.pumpAndSettle(); + return backend; +} + +base class _ControlledPreferences extends InMemorySharedPreferencesAsync { + _ControlledPreferences(super.data) : super.withData(); + + bool holdConfigWrites = false; + final List configWrites = []; + final List> _configWriteGates = []; + int activeConfigWrites = 0; + int maxActiveConfigWrites = 0; + String? durableConfig; + + @override + Future setString(String key, String value, SharedPreferencesOptions options) async { + if (key != SettingsService.mpvConfigText.key) return super.setString(key, value, options); + + configWrites.add(value); + activeConfigWrites++; + if (activeConfigWrites > maxActiveConfigWrites) maxActiveConfigWrites = activeConfigWrites; + try { + if (holdConfigWrites) { + final gate = Completer(); + _configWriteGates.add(gate); + await gate.future; + } + final result = await super.setString(key, value, options); + durableConfig = value; + return result; + } finally { + activeConfigWrites--; + } + } + + void completeNextConfigWrite({Object? error}) { + completeConfigWriteAt(0, error: error); + } + + void completeConfigWriteAt(int index, {Object? error}) { + final gate = _configWriteGates.removeAt(index); + if (error == null) { + gate.complete(); + } else { + gate.completeError(error); + } + } +} diff --git a/test/screens/settings/settings_screen_test.dart b/test/screens/settings/settings_screen_test.dart index d34e7d3a..3f7fc86b 100644 --- a/test/screens/settings/settings_screen_test.dart +++ b/test/screens/settings/settings_screen_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; @@ -24,11 +25,13 @@ import 'package:plezy/screens/settings/settings_screen.dart'; import 'package:plezy/services/donation_service.dart'; import 'package:plezy/services/download_storage_service.dart'; import 'package:plezy/services/download_manager_service.dart'; +import 'package:plezy/services/settings_export_service.dart'; import 'package:plezy/services/settings_service.dart'; import 'package:plezy/services/update_service.dart'; import 'package:plezy/theme/mono_theme.dart'; import 'package:plezy/utils/platform_detector.dart'; import 'package:plezy/widgets/app_icon.dart'; +import 'package:plezy/widgets/dialog_action_button.dart'; import 'package:plezy/widgets/focusable_list_tile.dart'; import 'package:plezy/widgets/loading_indicator_box.dart'; import 'package:plezy/widgets/setting_tile.dart'; @@ -310,6 +313,148 @@ void main() { expect(SettingsService.instance.read(SettingsService.customDownloadPath), isNull); expect(find.text(t.settings.resetSettingsSuccess), findsOneWidget); }); + + testWidgets('cancelled directory picker remains silent and leaves the dialog retryable', (tester) async { + final harness = await _pumpSettingsScreen(tester); + addTearDown(() => harness.dispose(tester)); + + await tester.tap(find.text(t.settings.downloadLocationDefault)); + await _pumpUi(tester); + await tester.tap(find.text(t.settings.selectFolder)); + await _pumpUi(tester); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(t.settings.saveFailed), findsNothing); + expect(find.text(t.settings.downloadLocationChanged), findsNothing); + expect(harness.locationEvents, isEmpty); + }); + + testWidgets('directory picker platform failure uses shared settings feedback', (tester) async { + directoryPicker.directoryError = PlatformException(code: 'picker_failed'); + final harness = await _pumpSettingsScreen(tester); + addTearDown(() => harness.dispose(tester)); + + await tester.tap(find.text(t.settings.downloadLocationDefault)); + await _pumpUi(tester); + await tester.tap(find.text(t.settings.selectFolder)); + await _pumpUi(tester); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(t.settings.saveFailed), findsOneWidget); + expect(harness.locationEvents, isEmpty); + }); + + testWidgets('directory filesystem failure uses shared settings feedback', (tester) async { + directoryPicker.directoryPath = '${temporaryDirectory.path}/selected-downloads'; + final harness = await _pumpSettingsScreen( + tester, + writableChecker: (_) async => throw const FileSystemException('writable check failed'), + ); + addTearDown(() => harness.dispose(tester)); + + await tester.tap(find.text(t.settings.downloadLocationDefault)); + await _pumpUi(tester); + await tester.tap(find.text(t.settings.selectFolder)); + await _pumpUi(tester); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(t.settings.saveFailed), findsOneWidget); + expect(harness.locationEvents, isEmpty); + }); + + testWidgets('late directory picker completion does not use a disposed context', (tester) async { + directoryPicker.directoryGate = Completer(); + final harness = await _pumpSettingsScreen(tester); + + await tester.tap(find.text(t.settings.downloadLocationDefault)); + await _pumpUi(tester); + await tester.tap(find.text(t.settings.selectFolder)); + await tester.pump(); + await harness.dispose(tester); + + directoryPicker.directoryGate!.complete('${temporaryDirectory.path}/late-downloads'); + await tester.pump(); + + expect(tester.takeException(), isNull); + expect(harness.locationEvents, isEmpty); + }); + + testWidgets('late directory picker failure does not use a disposed context', (tester) async { + directoryPicker.directoryGate = Completer(); + final harness = await _pumpSettingsScreen(tester); + + await tester.tap(find.text(t.settings.downloadLocationDefault)); + await _pumpUi(tester); + await tester.tap(find.text(t.settings.selectFolder)); + await tester.pump(); + await harness.dispose(tester); + + directoryPicker.directoryGate!.completeError(PlatformException(code: 'late_picker_failure')); + await tester.pump(); + + expect(tester.takeException(), isNull); + expect(harness.locationEvents, isEmpty); + }); + + testWidgets('late settings export failure does not use a disposed context', (tester) async { + final exportGate = Completer(); + final harness = await _pumpSettingsScreen(tester, settingsExporter: () => exportGate.future); + + await tester.tap(find.text(t.settings.exportSettings)); + await tester.pump(); + await harness.dispose(tester); + + exportGate.completeError(PlatformException(code: 'late_export_failure')); + await tester.pump(); + + expect(tester.takeException(), isNull); + }); + + testWidgets('late settings import failure does not use a disposed context', (tester) async { + final importGate = Completer(); + final harness = await _pumpSettingsScreen(tester, settingsImporter: () => importGate.future); + + await tester.tap(find.text(t.settings.importSettings)); + await _pumpUi(tester); + await tester.tap(find.widgetWithText(DialogActionButton, t.settings.importSettings)); + await tester.pump(); + await harness.dispose(tester); + + importGate.completeError(PlatformException(code: 'late_import_failure')); + await tester.pump(); + + expect(tester.takeException(), isNull); + }); + + testWidgets('settings export platform failure uses shared settings feedback', (tester) async { + final harness = await _pumpSettingsScreen( + tester, + settingsExporter: () async => throw PlatformException(code: 'save_failed'), + ); + addTearDown(() => harness.dispose(tester)); + + await tester.tap(find.text(t.settings.exportSettings)); + await _pumpUi(tester); + + expect(find.text(t.settings.saveFailed), findsOneWidget); + expect(find.text(t.settings.exportSettingsSuccess), findsNothing); + }); + + testWidgets('settings import platform failure uses shared settings feedback', (tester) async { + final harness = await _pumpSettingsScreen( + tester, + settingsImporter: () async => throw PlatformException(code: 'pick_failed'), + ); + addTearDown(() => harness.dispose(tester)); + + await tester.tap(find.text(t.settings.importSettings)); + await _pumpUi(tester); + await tester.tap(find.widgetWithText(DialogActionButton, t.settings.importSettings)); + await _pumpUi(tester); + + expect(find.text(t.settings.saveFailed), findsOneWidget); + expect(find.text(t.settings.importSettingsSuccess), findsNothing); + }); } Finder _navigationTileFor(String title) => @@ -376,7 +521,12 @@ class _SettingsHarness { } } -Future<_SettingsHarness> _pumpSettingsScreen(WidgetTester tester) async { +Future<_SettingsHarness> _pumpSettingsScreen( + WidgetTester tester, { + Future Function(Directory directory)? writableChecker, + Future Function()? settingsExporter, + Future Function()? settingsImporter, +}) async { tester.view.physicalSize = const Size(1800, 3200); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); @@ -451,7 +601,11 @@ Future<_SettingsHarness> _pumpSettingsScreen(WidgetTester tester) async { ], child: MaterialApp( theme: monoTheme(dark: true).copyWith(platform: TargetPlatform.android), - home: SettingsScreen(downloadDirectoryWritableChecker: (_) async => true), + home: SettingsScreen( + downloadDirectoryWritableChecker: writableChecker ?? (_) async => true, + settingsExporter: settingsExporter, + settingsImporter: settingsImporter, + ), ), ), ), @@ -463,6 +617,8 @@ Future<_SettingsHarness> _pumpSettingsScreen(WidgetTester tester) async { class _FakeDirectoryPicker extends FilePicker { String? directoryPath; + Object? directoryError; + Completer? directoryGate; @override Future getDirectoryPath({ @@ -470,6 +626,10 @@ class _FakeDirectoryPicker extends FilePicker { String? initialDirectory, bool lockParentWindow = false, }) async { + final gate = directoryGate; + if (gate != null) return gate.future; + final error = directoryError; + if (error != null) throw error; return directoryPath; } } diff --git a/test/screens/settings/settings_utils_test.dart b/test/screens/settings/settings_utils_test.dart index daf35933..a202cd55 100644 --- a/test/screens/settings/settings_utils_test.dart +++ b/test/screens/settings/settings_utils_test.dart @@ -1,8 +1,13 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/i18n/strings.g.dart'; import 'package:plezy/screens/settings/settings_utils.dart'; import 'package:plezy/utils/platform_detector.dart'; +import 'package:plezy/widgets/dialog_action_button.dart'; void main() { tearDown(() { @@ -60,4 +65,105 @@ void main() { expect(saved, isEmpty); expect(tester.takeException(), isNull); }); + + testWidgets('settings save helper reports platform failures and remains retryable', (tester) async { + final context = await _pumpHost(tester); + + showRegexInputDialog( + context: context, + title: 'Regex', + currentValue: 'abc', + defaultValue: '.*', + onSave: (_) async => throw PlatformException(code: 'write_failed'), + ); + await tester.pumpAndSettle(); + await tester.tap(_saveButton()); + await tester.pump(); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(t.settings.saveFailed), findsOneWidget); + }); + + testWidgets('settings save helper reports filesystem failures and remains retryable', (tester) async { + final context = await _pumpHost(tester); + + showRegexInputDialog( + context: context, + title: 'Regex', + currentValue: 'abc', + defaultValue: '.*', + onSave: (_) async => throw const FileSystemException('write failed'), + ); + await tester.pumpAndSettle(); + await tester.tap(_saveButton()); + await tester.pump(); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(t.settings.saveFailed), findsOneWidget); + }); + + testWidgets('settings save helper closes only after a successful save', (tester) async { + final context = await _pumpHost(tester); + var saved = false; + + showRegexInputDialog( + context: context, + title: 'Regex', + currentValue: 'abc', + defaultValue: '.*', + onSave: (_) async => saved = true, + ); + await tester.pumpAndSettle(); + await tester.tap(_saveButton()); + await tester.pumpAndSettle(); + + expect(saved, isTrue); + expect(find.byType(AlertDialog), findsNothing); + }); + + testWidgets('disposed settings save context does not present late feedback', (tester) async { + final context = await _pumpHost(tester); + final saveGate = Completer(); + + showRegexInputDialog( + context: context, + title: 'Regex', + currentValue: 'abc', + defaultValue: '.*', + onSave: (_) async { + await saveGate.future; + throw PlatformException(code: 'late_write_failure'); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(_saveButton()); + await tester.pump(); + await tester.pumpWidget(const SizedBox.shrink()); + saveGate.complete(); + await tester.pump(); + + expect(find.text(t.settings.saveFailed), findsNothing); + expect(tester.takeException(), isNull); + }); } + +Future _pumpHost(WidgetTester tester) async { + late BuildContext context; + await tester.pumpWidget( + TranslationProvider( + child: MaterialApp( + home: Scaffold( + body: Builder( + builder: (hostContext) { + context = hostContext; + return const SizedBox.shrink(); + }, + ), + ), + ), + ), + ); + return context; +} + +Finder _saveButton() => find.widgetWithText(DialogActionButton, t.common.save); diff --git a/test/scripts/generate_ducet_ranks_test.dart b/test/scripts/generate_ducet_ranks_test.dart new file mode 100644 index 00000000..eb93d51d --- /dev/null +++ b/test/scripts/generate_ducet_ranks_test.dart @@ -0,0 +1,329 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:crypto/crypto.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../scripts/generate_ducet_ranks.dart'; + +void main() { + late Directory temporaryDirectory; + + setUp(() async { + temporaryDirectory = await Directory.systemTemp.createTemp('plezy_ducet_test_'); + }); + + tearDown(() async { + if (await temporaryDirectory.exists()) { + await temporaryDirectory.delete(recursive: true); + } + }); + + test('pins immutable Unicode and CLDR source descriptors', () { + expect(allKeysSource.url, 'https://www.unicode.org/Public/UCA/13.0.0/allkeys.txt'); + expect(allKeysSource.sha256Digest, 'a3255d45b7af97f4dc14fb8364d7573b434425e5c58cacf00d16901ce081c78d'); + expect(allKeysSource.bundledFileName, 'allkeys-13.0.0.txt.gz'); + expect(allKeysSource.licenseUrl, 'https://www.unicode.org/license.txt'); + expect( + fractionalUcaSource.url, + 'https://raw.githubusercontent.com/unicode-org/cldr/' + '651afecf9ccf1541a49306993e8210fa2209aa0b/common/uca/FractionalUCA.txt', + ); + expect(fractionalUcaSource.sha256Digest, 'a6144d0c8c19cc899a5d2f48fbc14e3e31e819049a73aa86b67111f1f3f81637'); + expect(fractionalUcaSource.bundledFileName, 'FractionalUCA-651afecf9ccf1541a49306993e8210fa2209aa0b.txt.gz'); + expect(fractionalUcaSource.licenseUrl, contains('/651afecf9ccf1541a49306993e8210fa2209aa0b/LICENSE')); + }); + + test('bundled gzip inputs have deterministic normalized headers and bytes', () { + final allKeys = File('scripts/data/${allKeysSource.bundledFileName}').readAsBytesSync(); + final fractional = File('scripts/data/${fractionalUcaSource.bundledFileName}').readAsBytesSync(); + + expect(allKeys.take(8), [0x1F, 0x8B, 0x08, 0, 0, 0, 0, 0]); + expect(fractional.take(8), [0x1F, 0x8B, 0x08, 0, 0, 0, 0, 0]); + expect(sha256.convert(allKeys).toString(), '744040b99e266f9ec599744e267091c159369a36f6ca7558c12a9eb3e039575f'); + expect(sha256.convert(fractional).toString(), 'edc486bae663abf83d18c9006d08c1e8bb19986810cf8ab663fa33bee6d22275'); + }); + + test('dense lookup stores rank plus one with a zero sentinel', () { + final ranks = buildDenseRankLookup([0x41, 0x24, 0x5C]); + + expect(ranks.length, 0x10000); + expect(ranks.codeUnitAt(0x41), 1); + expect(ranks.codeUnitAt(0x24), 2); + expect(ranks.codeUnitAt(0x5C), 3); + expect(ranks.codeUnitAt(0x42), 0); + }); + + test('dense lookup rejects duplicate, out-of-BMP, and overflowing orders', () { + expect(() => buildDenseRankLookup([0x41, 0x41]), throwsStateError); + expect(() => buildDenseRankLookup([-1]), throwsRangeError); + expect(() => buildDenseRankLookup([0x10000]), throwsRangeError); + expect(() => buildDenseRankLookup(List.generate(0x10000, (index) => index)), throwsStateError); + }); + + test('parse and render seams are deterministic and escape Dart interpolation characters', () { + const allKeysText = ''' +0041 ; [.0100.0020.0002] +30A2 ; [.0200.0020.000F] +3042 ; [.0200.0020.000D] +'''; + const fractionalText = '[radical 1=โผ€ไธ€:ไธ€ไธ]'; + + final firstAllKeys = parseAllKeys(allKeysText); + final secondAllKeys = parseAllKeys(allKeysText); + final (firstCjk, firstKangxi) = parseRadicals(fractionalText); + final (secondCjk, secondKangxi) = parseRadicals(fractionalText); + final firstOrder = buildOrder(firstAllKeys, firstCjk); + final secondOrder = buildOrder(secondAllKeys, secondCjk); + + expect(firstOrder, secondOrder); + expect(renderDucetOrder(firstOrder, firstKangxi), renderDucetOrder(secondOrder, secondKangxi)); + + final ranksWithSpecialCodeUnits = List.generate(0x5C, (index) => 0x1000 + index); + final rendered = renderDucetOrder(ranksWithSpecialCodeUnits, const {}); + expect(rendered, contains(r"\'")); + expect(rendered, contains(r'\\')); + expect(rendered, contains(r'\$')); + }); + + test('explicit inputs are both verified before parsing and writing', () async { + const allKeysText = '0041 ; [.0100.0020.0002]\n'; + const fractionalText = '[radical 1=โผ€ไธ€:ไธ€]\n'; + final allKeysFile = File('${temporaryDirectory.path}/allkeys.txt')..writeAsStringSync(allKeysText); + final fractionalFile = File('${temporaryDirectory.path}/FractionalUCA.txt')..writeAsStringSync(fractionalText); + final output = File('${temporaryDirectory.path}/ducet_order.dart')..writeAsStringSync('old output'); + var downloads = 0; + + await generateDucetRanks( + useBundledSources: false, + explicitAllKeys: allKeysFile, + explicitFractionalUca: fractionalFile, + output: output, + allKeysDescriptor: descriptorFor('allkeys', allKeysText), + fractionalUcaDescriptor: descriptorFor('fractional', fractionalText), + downloader: (_) async { + downloads++; + throw StateError('network must not be used'); + }, + ); + + expect(downloads, 0); + expect(output.readAsStringSync(), contains('const String _ducetRanks =')); + expect(output.readAsStringSync(), isNot(contains('_buildRanks'))); + }); + + test('rejects an explicit digest mismatch without replacing output', () async { + final allKeysFile = File('${temporaryDirectory.path}/allkeys.txt')..writeAsStringSync('tampered'); + final fractionalFile = File('${temporaryDirectory.path}/FractionalUCA.txt')..writeAsStringSync('fractional'); + final output = File('${temporaryDirectory.path}/ducet_order.dart')..writeAsStringSync('old output'); + var writerCalled = false; + + await expectLater( + generateDucetRanks( + useBundledSources: false, + explicitAllKeys: allKeysFile, + explicitFractionalUca: fractionalFile, + output: output, + allKeysDescriptor: descriptorFor('allkeys', 'expected'), + fractionalUcaDescriptor: descriptorFor('fractional', 'fractional'), + writer: (_, _) => writerCalled = true, + ), + throwsA(isA()), + ); + + expect(writerCalled, isFalse); + expect(output.readAsStringSync(), 'old output'); + expect(allKeysFile.readAsStringSync(), 'tampered'); + }); + + test('rejects an invalid cached source without downloading or replacing files', () async { + final cache = Directory('${temporaryDirectory.path}/cache')..createSync(); + final allKeysDescriptor = descriptorFor('allkeys', 'expected allkeys'); + final fractionalDescriptor = descriptorFor('fractional', 'fractional'); + final cachedAllKeys = File('${cache.path}/${allKeysDescriptor.cacheFileName}')..writeAsStringSync('tampered cache'); + final output = File('${temporaryDirectory.path}/ducet_order.dart')..writeAsStringSync('old output'); + var downloads = 0; + + await expectLater( + generateDucetRanks( + useBundledSources: false, + cacheDirectory: cache, + output: output, + allKeysDescriptor: allKeysDescriptor, + fractionalUcaDescriptor: fractionalDescriptor, + downloader: (_) async { + downloads++; + return response(HttpStatus.ok, utf8.encode('unused')); + }, + ), + throwsA(isA()), + ); + + expect(downloads, 0); + expect(cachedAllKeys.readAsStringSync(), 'tampered cache'); + expect(output.readAsStringSync(), 'old output'); + }); + + test('rejects HTTP status before parsing, caching, or replacing output', () async { + final cache = Directory('${temporaryDirectory.path}/cache')..createSync(); + final output = File('${temporaryDirectory.path}/ducet_order.dart')..writeAsStringSync('old output'); + + await expectLater( + generateDucetRanks( + useBundledSources: false, + cacheDirectory: cache, + output: output, + allKeysDescriptor: descriptorFor('allkeys', 'allkeys'), + fractionalUcaDescriptor: descriptorFor('fractional', 'fractional'), + downloader: (_) async => response(HttpStatus.notFound, utf8.encode('not found')), + ), + throwsA(isA().having((error) => error.message, 'message', contains('HTTP 404'))), + ); + + expect(cache.listSync(), isEmpty); + expect(output.readAsStringSync(), 'old output'); + }); + + test('a rejected second download preserves the first cache and output', () async { + const allKeysText = '0041 ; [.0100.0020.0002]\n'; + const fractionalText = '[radical 1=โผ€ไธ€:ไธ€]\n'; + final cache = Directory('${temporaryDirectory.path}/cache')..createSync(); + final marker = File('${cache.path}/marker')..writeAsStringSync('keep'); + final output = File('${temporaryDirectory.path}/ducet_order.dart')..writeAsStringSync('old output'); + var request = 0; + + await expectLater( + generateDucetRanks( + useBundledSources: false, + cacheDirectory: cache, + output: output, + allKeysDescriptor: descriptorFor('allkeys', allKeysText), + fractionalUcaDescriptor: descriptorFor('fractional', fractionalText), + downloader: (_) async { + request++; + return response(HttpStatus.ok, utf8.encode(request == 1 ? allKeysText : 'tampered fractional')); + }, + ), + throwsA(isA()), + ); + + expect(request, 2); + expect(cache.listSync().map((entity) => entity.path).toList(), [marker.path]); + expect(marker.readAsStringSync(), 'keep'); + expect(output.readAsStringSync(), 'old output'); + }); + + test('parse rejection leaves verified inputs, caches, and output untouched', () async { + const duplicateAllKeys = ''' +0041 ; [.0100.0020.0002] +0041 ; [.0101.0020.0002] +'''; + const fractionalText = '[radical 1=โผ€ไธ€:ไธ€]\n'; + final allKeysFile = File('${temporaryDirectory.path}/allkeys.txt')..writeAsStringSync(duplicateAllKeys); + final fractionalFile = File('${temporaryDirectory.path}/FractionalUCA.txt')..writeAsStringSync(fractionalText); + final cache = Directory('${temporaryDirectory.path}/cache')..createSync(); + final marker = File('${cache.path}/marker')..writeAsStringSync('keep'); + final output = File('${temporaryDirectory.path}/ducet_order.dart')..writeAsStringSync('old output'); + + await expectLater( + generateDucetRanks( + useBundledSources: false, + explicitAllKeys: allKeysFile, + explicitFractionalUca: fractionalFile, + cacheDirectory: cache, + output: output, + allKeysDescriptor: descriptorFor('allkeys', duplicateAllKeys), + fractionalUcaDescriptor: descriptorFor('fractional', fractionalText), + ), + throwsFormatException, + ); + + expect(allKeysFile.readAsStringSync(), duplicateAllKeys); + expect(fractionalFile.readAsStringSync(), fractionalText); + expect(cache.listSync().map((entity) => entity.path).toList(), [marker.path]); + expect(output.readAsStringSync(), 'old output'); + }); + + test('default bundled generation is deterministic and never downloads', () async { + final output = File('${temporaryDirectory.path}/ducet_order.dart'); + var downloads = 0; + String? first; + String? second; + Future failDownloader(Uri _) async { + downloads++; + throw StateError('bundled generation must not use the network'); + } + + await generateDucetRanks(output: output, downloader: failDownloader, writer: (_, contents) => first = contents); + await generateDucetRanks(output: output, downloader: failDownloader, writer: (_, contents) => second = contents); + + expect(downloads, 0); + expect(first, isNotNull); + expect(second, first); + expect(first, File('lib/data/ducet_order.dart').readAsStringSync()); + }); + + test('tampered bundled gzip fails before replacing output', () async { + const allKeysText = '0041 ; [.0100.0020.0002]\n'; + const fractionalText = '[radical 1=โผ€ไธ€:ไธ€]\n'; + final bundled = Directory('${temporaryDirectory.path}/bundled')..createSync(); + final allKeysDescriptor = descriptorFor('allkeys', allKeysText); + final fractionalDescriptor = descriptorFor('fractional', fractionalText); + File('${bundled.path}/${allKeysDescriptor.bundledFileName}').writeAsBytesSync(const [0x1F, 0x8B, 0x08, 0x00, 0x00]); + writeBundledSource(bundled, fractionalDescriptor, fractionalText); + final output = File('${temporaryDirectory.path}/ducet_order.dart')..writeAsStringSync('old output'); + + await expectLater( + generateDucetRanks( + bundledSourceDirectory: bundled, + output: output, + allKeysDescriptor: allKeysDescriptor, + fractionalUcaDescriptor: fractionalDescriptor, + ), + throwsA(isA()), + ); + + expect(output.readAsStringSync(), 'old output'); + }); + + test('tampered decompressed bundled bytes fail digest before replacing output', () async { + const allKeysText = '0041 ; [.0100.0020.0002]\n'; + const fractionalText = '[radical 1=โผ€ไธ€:ไธ€]\n'; + final bundled = Directory('${temporaryDirectory.path}/bundled')..createSync(); + final allKeysDescriptor = descriptorFor('allkeys', allKeysText); + final fractionalDescriptor = descriptorFor('fractional', fractionalText); + writeBundledSource(bundled, allKeysDescriptor, 'tampered raw bytes'); + writeBundledSource(bundled, fractionalDescriptor, fractionalText); + final output = File('${temporaryDirectory.path}/ducet_order.dart')..writeAsStringSync('old output'); + + await expectLater( + generateDucetRanks( + bundledSourceDirectory: bundled, + output: output, + allKeysDescriptor: allKeysDescriptor, + fractionalUcaDescriptor: fractionalDescriptor, + ), + throwsA( + isA().having((error) => error.message, 'message', contains('SHA-256 mismatch')), + ), + ); + + expect(output.readAsStringSync(), 'old output'); + }); +} + +SourceDescriptor descriptorFor(String name, String contents) => SourceDescriptor( + name: name, + url: 'https://example.test/$name', + bundledFileName: '$name.txt.gz', + cacheFileName: '$name.txt', + sha256Digest: sha256.convert(utf8.encode(contents)).toString(), + licenseUrl: 'https://example.test/license', +); + +void writeBundledSource(Directory directory, SourceDescriptor descriptor, String contents) { + File('${directory.path}/${descriptor.bundledFileName}').writeAsBytesSync(gzip.encode(utf8.encode(contents))); +} + +SourceDownloadResponse response(int statusCode, List bytes) => + SourceDownloadResponse(statusCode: statusCode, bytes: Stream>.value(bytes)); diff --git a/test/scripts/generate_hid_key_labels_test.dart b/test/scripts/generate_hid_key_labels_test.dart new file mode 100644 index 00000000..1031f5d4 --- /dev/null +++ b/test/scripts/generate_hid_key_labels_test.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +import '../../scripts/generate_hid_key_labels.dart'; + +void main() { + late String validSource; + + setUpAll(() async { + validSource = await File(defaultHidKeyLabelsInput).readAsString(); + }); + + test('parse and render are deterministic and reproduce the checked-in artifact', () async { + final first = renderHidKeyLabels(parseHidKeyLabelsCatalog(validSource)); + final second = renderHidKeyLabels(parseHidKeyLabelsCatalog(validSource)); + + expect(second, first); + expect(first, await File(defaultHidKeyLabelsOutput).readAsString()); + }); + + test('rejects invalid schema, empty groups, group names, keys, labels, and IDs', () { + _expectInvalid(validSource, (catalog) => catalog['schemaVersion'] = 2); + _expectInvalid(validSource, (catalog) => catalog['groups'] = []); + _expectInvalid(validSource, (catalog) => _group(catalog, 0)['name'] = ' '); + _expectInvalid(validSource, (catalog) => _group(catalog, 0)['keys'] = []); + _expectInvalid(validSource, (catalog) => _key(catalog, 0, 0)['label'] = ''); + _expectInvalid(validSource, (catalog) => _key(catalog, 0, 0)['id'] = '0000001'); + _expectInvalid(validSource, (catalog) => _key(catalog, 0, 0)['id'] = '0000001A'); + }); + + test('rejects globally duplicate or noncanonical numeric IDs', () { + _expectInvalid(validSource, (catalog) => _key(catalog, 0, 1)['id'] = _key(catalog, 0, 0)['id']); + _expectInvalid(validSource, (catalog) => _key(catalog, 0, 1)['id'] = '0000000f'); + _expectInvalid(validSource, (catalog) => _key(catalog, 1, 0)['id'] = '0000000f'); + }); + + test('escapes controls in labels that require double-quoted Dart literals', () { + const catalog = HidKeyCatalog( + groups: [ + HidKeyGroup( + name: 'Test', + keys: [HidKeyLabel(id: '00000001', label: "Player's\nKey")], + ), + ], + ); + + expect(renderHidKeyLabels(catalog), contains("\"Player's\\nKey\"")); + }); + + test('invalid input fails before the existing output is replaced', () async { + final directory = await Directory.systemTemp.createTemp('plezy_hid_generator_test.'); + addTearDown(() => directory.delete(recursive: true)); + final input = File('${directory.path}/invalid.json'); + final output = File('${directory.path}/output.dart'); + await input.writeAsString('{"schemaVersion":1,"groups":[]}'); + await output.writeAsString('sentinel'); + + await expectLater(generateHidKeyLabels(input.path, output.path), throwsA(isA())); + expect(await output.readAsString(), 'sentinel'); + }); + + test('atomic writer replaces the complete destination', () async { + final directory = await Directory.systemTemp.createTemp('plezy_hid_atomic_test.'); + addTearDown(() => directory.delete(recursive: true)); + final output = File('${directory.path}/output.dart'); + await output.writeAsString('old'); + + await writeFileAtomically(output.path, 'new bytes\n'); + + expect(await output.readAsString(), 'new bytes\n'); + expect(directory.listSync().where((entry) => entry.path.contains('.tmp.')), isEmpty); + }); +} + +void _expectInvalid(String source, void Function(Map catalog) mutate) { + final catalog = (jsonDecode(source) as Map).cast(); + mutate(catalog); + expect(() => parseHidKeyLabelsCatalog(jsonEncode(catalog)), throwsA(isA())); +} + +Map _group(Map catalog, int index) { + final groups = catalog['groups']! as List; + return (groups[index]! as Map).cast(); +} + +Map _key(Map catalog, int groupIndex, int keyIndex) { + final keys = _group(catalog, groupIndex)['keys']! as List; + return (keys[keyIndex]! as Map).cast(); +} diff --git a/test/scripts/generate_iso_639_data_test.dart b/test/scripts/generate_iso_639_data_test.dart new file mode 100644 index 00000000..a6a90b22 --- /dev/null +++ b/test/scripts/generate_iso_639_data_test.dart @@ -0,0 +1,98 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +import '../../scripts/generate_iso_639_data.dart'; + +void main() { + late String validSource; + + setUpAll(() async { + validSource = await File(defaultIso639Input).readAsString(); + }); + + test('parse and render are deterministic and reproduce the checked-in artifact', () async { + final first = renderIso639Data(parseIso639Catalog(validSource)); + final second = renderIso639Data(parseIso639Catalog(validSource)); + + expect(second, first); + expect(first, await File(defaultIso639Output).readAsString()); + }); + + test('rejects invalid schema, keys, primary consistency, names, and field shapes', () { + _expectInvalid(validSource, (catalog) => catalog['schemaVersion'] = 2); + _expectInvalid(validSource, (catalog) => catalog['languages'] = {}); + _expectInvalid(validSource, (catalog) => _entry(catalog, 'aa')['primary'] = 'ab'); + _expectInvalid(validSource, (catalog) => _entry(catalog, 'aa')['terminology'] = 'AAR'); + _expectInvalid(validSource, (catalog) => _entry(catalog, 'bo')['bibliographic'] = 'TIB'); + _expectInvalid(validSource, (catalog) => _entry(catalog, 'aa')['name'] = ' '); + + _expectInvalid(validSource, (catalog) { + final languages = _languages(catalog); + languages['a'] = languages.remove('aa'); + }); + }); + + test('rejects duplicate aliases and noncanonical primary ordering', () { + _expectInvalid( + validSource, + (catalog) => _entry(catalog, 'ab')['terminology'] = _entry(catalog, 'aa')['terminology'], + ); + _expectInvalid( + validSource, + (catalog) => _entry(catalog, 'bo')['bibliographic'] = _entry(catalog, 'aa')['terminology'], + ); + _expectInvalid(validSource, (catalog) { + final languages = _languages(catalog); + final first = languages.remove('aa'); + languages['aa'] = first; + }); + }); + + test('escapes controls in names that require double-quoted Dart literals', () { + const catalog = Iso639Catalog( + entries: [Iso639CatalogEntry(primary: 'aa', terminology: 'aaa', bibliographic: null, name: "People's\nLanguage")], + ); + + expect(renderIso639Data(catalog), contains("\"People's\\nLanguage\"")); + }); + + test('invalid input fails before the existing output is replaced', () async { + final directory = await Directory.systemTemp.createTemp('plezy_iso_generator_test.'); + addTearDown(() => directory.delete(recursive: true)); + final input = File('${directory.path}/invalid.json'); + final output = File('${directory.path}/output.dart'); + await input.writeAsString('{"schemaVersion":1,"languages":{}}'); + await output.writeAsString('sentinel'); + + await expectLater(generateIso639Data(input.path, output.path), throwsA(isA())); + expect(await output.readAsString(), 'sentinel'); + }); + + test('atomic writer replaces the complete destination', () async { + final directory = await Directory.systemTemp.createTemp('plezy_iso_atomic_test.'); + addTearDown(() => directory.delete(recursive: true)); + final output = File('${directory.path}/output.dart'); + await output.writeAsString('old'); + + await writeFileAtomically(output.path, 'new bytes\n'); + + expect(await output.readAsString(), 'new bytes\n'); + expect(directory.listSync().where((entry) => entry.path.contains('.tmp.')), isEmpty); + }); +} + +void _expectInvalid(String source, void Function(Map catalog) mutate) { + final catalog = (jsonDecode(source) as Map).cast(); + mutate(catalog); + expect(() => parseIso639Catalog(jsonEncode(catalog)), throwsA(isA())); +} + +Map _languages(Map catalog) { + return (catalog['languages']! as Map).cast(); +} + +Map _entry(Map catalog, String code) { + return (_languages(catalog)[code]! as Map).cast(); +} diff --git a/test/services/data_aggregation_bridge_test.dart b/test/services/data_aggregation_bridge_test.dart index 780951fd..9e4a4567 100644 --- a/test/services/data_aggregation_bridge_test.dart +++ b/test/services/data_aggregation_bridge_test.dart @@ -150,7 +150,7 @@ void main() { 'SearchResult': [ { 'score': 100, - 'Metadata': {'ratingKey': 'plex-movie', 'type': 'movie', 'title': 'The Boys in the Boat'}, + 'Metadata': {'ratingKey': 'plex-movie', 'type': 'movie', 'title': 'Spider Man Returns'}, }, ], }, @@ -169,7 +169,7 @@ void main() { if (req.url.path == '/Items') { return _json({ 'Items': [ - {'Id': 'jf-show', 'Type': 'Series', 'Name': 'The Boys'}, + {'Id': 'jf-show', 'Type': 'Series', 'Name': 'Spiderโ€‘Man'}, ], }); } @@ -179,17 +179,19 @@ void main() { addTearDown(jellyfinClient.close); manager.debugRegisterJellyfinClientForTesting(jellyfinClient); - final results = await service.searchAcrossServers('The Boys', limit: 1); + final results = await service.searchAcrossServers('Spider Man', limit: 1); expect(results.map((item) => item.id), ['jf-show']); + expect(plexRequests.single.queryParameters['query'], 'Spider Man'); expect(plexRequests.single.queryParameters['limit'], '100'); expect(plexRequests.single.queryParameters['searchTypes'], 'movies,tv,music'); // Jellyfin search fans out to /Items plus a best-effort /Artists call // (500 above โ†’ treated as empty). final jfItemsRequest = jellyfinRequests.singleWhere((url) => url.path == '/Items'); expect(jfItemsRequest.queryParameters['Limit'], '100'); + expect(jfItemsRequest.queryParameters['SearchTerm'], 'Spider Man'); final jfArtistsRequest = jellyfinRequests.singleWhere((url) => url.path == '/Artists'); - expect(jfArtistsRequest.queryParameters['searchTerm'], 'The Boys'); + expect(jfArtistsRequest.queryParameters['searchTerm'], 'Spider Man'); }); test('getOnDeckFromAllServers forwards preview limit to clients', () async { diff --git a/test/services/keyboard_shortcuts_service_test.dart b/test/services/keyboard_shortcuts_service_test.dart index 67097747..dcaf6a0e 100644 --- a/test/services/keyboard_shortcuts_service_test.dart +++ b/test/services/keyboard_shortcuts_service_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/services.dart'; @@ -6,8 +7,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/models/hotkey_model.dart'; import 'package:plezy/mpv/mpv.dart'; import 'package:plezy/services/keyboard_shortcuts_service.dart'; +import 'package:plezy/services/base_shared_preferences_service.dart'; import 'package:plezy/services/settings_service.dart'; import 'package:plezy/services/video_filter_manager.dart'; +import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; import '../test_helpers/prefs.dart'; @@ -87,6 +92,97 @@ void main() { expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.space); expect(SettingsService.instance.prefs.containsKey(SettingsService.keyboardHotkeys.key), isFalse); }); + + test('explicit unassignment survives restart and reset restores dispatch', () async { + final service = await KeyboardShortcutsService.getInstance(); + await service.setHotkey('play_pause', null); + await service.setHotkey('volume_up', const HotKey(key: PhysicalKeyboardKey.keyQ)); + + final stored = + json.decode(SettingsService.instance.prefs.getString(SettingsService.keyboardHotkeys.key)!) + as Map; + expect(stored['play_pause'], {'disabled': true}); + expect(service.getHotkey('play_pause'), isNull); + expect(service.getActionForHotkey(const HotKey(key: PhysicalKeyboardKey.space)), isNull); + + service.dispose(); + SettingsService.resetForTesting(); + BaseSharedPreferencesService.resetForTesting(); + + final reloaded = await KeyboardShortcutsService.getInstance(); + addTearDown(reloaded.dispose); + expect(reloaded.getHotkey('play_pause'), isNull); + expect(reloaded.getHotkey('volume_up')?.key, PhysicalKeyboardKey.keyQ); + expect(reloaded.getHotkey('volume_down')?.key, PhysicalKeyboardKey.arrowDown); + + var playPauseCalls = 0; + final player = _FakePlayer(); + KeyEventResult dispatch() { + return reloaded.handleVideoPlayerKeyEvent( + const KeyDownEvent( + physicalKey: PhysicalKeyboardKey.space, + logicalKey: LogicalKeyboardKey.space, + timeStamp: Duration.zero, + ), + player, + null, + null, + null, + null, + null, + null, + canControlPlayback: true, + canNavigateMediaItems: true, + onPlayPause: () => playPauseCalls++, + ); + } + + expect(dispatch(), KeyEventResult.ignored); + expect(playPauseCalls, 0); + await reloaded.resetToDefaults(); + expect(dispatch(), KeyEventResult.handled); + expect(playPauseCalls, 1); + + final resetStored = + json.decode(SettingsService.instance.prefs.getString(SettingsService.keyboardHotkeys.key)!) + as Map; + expect(resetStored['play_pause'], {'key': '0007002c', 'modifiers': []}); + }); + + test('serialized writes preserve rapid edits and recover after a failure', () async { + final preferences = _HotkeyPreferences(const {}); + SharedPreferencesAsyncPlatform.instance = preferences; + SettingsService.resetForTesting(); + BaseSharedPreferencesService.resetForTesting(); + final service = await KeyboardShortcutsService.getInstance(); + addTearDown(service.dispose); + + preferences.blockNextHotkeyWrite(); + final first = service.setHotkey('play_pause', const HotKey(key: PhysicalKeyboardKey.keyQ)); + await preferences.blocked; + final second = service.setHotkey('volume_up', const HotKey(key: PhysicalKeyboardKey.keyW)); + await Future.delayed(Duration.zero); + + expect(preferences.hotkeyWriteCount, 1); + expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.space); + preferences.release(); + await Future.wait([first, second]); + + expect(preferences.hotkeyWriteCount, 2); + expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.keyQ); + expect(service.getHotkey('volume_up')?.key, PhysicalKeyboardKey.keyW); + + preferences.failNextHotkeyWrite = true; + await expectLater( + service.setHotkey('play_pause', const HotKey(key: PhysicalKeyboardKey.keyE)), + throwsA(isA()), + ); + expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.keyQ); + + await service.setHotkey('volume_down', const HotKey(key: PhysicalKeyboardKey.keyR)); + expect(service.getHotkey('play_pause')?.key, PhysicalKeyboardKey.keyQ); + expect(service.getHotkey('volume_down')?.key, PhysicalKeyboardKey.keyR); + }); }); testWidgets('Ctrl+S takes a screenshot once while held', (tester) async { @@ -597,3 +693,41 @@ class _FakePlayer implements Player { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } + +final class _HotkeyPreferences extends InMemorySharedPreferencesAsync { + _HotkeyPreferences(super.data) : super.withData(); + + Completer? _entered; + Completer? _release; + int hotkeyWriteCount = 0; + bool failNextHotkeyWrite = false; + + Future get blocked => _entered!.future; + + void blockNextHotkeyWrite() { + _entered = Completer(); + _release = Completer(); + } + + void release() { + final release = _release; + if (release != null && !release.isCompleted) release.complete(); + } + + @override + Future setString(String key, String value, SharedPreferencesOptions options) async { + if (key == SettingsService.keyboardHotkeys.key) { + hotkeyWriteCount++; + if (failNextHotkeyWrite) { + failNextHotkeyWrite = false; + throw PlatformException(code: 'write_failed'); + } + final entered = _entered; + if (entered != null && !entered.isCompleted) { + entered.complete(); + await _release!.future; + } + } + return super.setString(key, value, options); + } +} diff --git a/test/services/plex_client_http_contract_test.dart b/test/services/plex_client_http_contract_test.dart index ee304a8c..2e080e6c 100644 --- a/test/services/plex_client_http_contract_test.dart +++ b/test/services/plex_client_http_contract_test.dart @@ -13,6 +13,7 @@ import 'package:plezy/media/media_kind.dart'; import 'package:plezy/services/plex_api_cache.dart'; import 'package:plezy/services/plex_client.dart'; import 'package:plezy/utils/active_client_scope.dart'; +import 'package:plezy/utils/media_server_http_client.dart'; import '../test_helpers/backend_client_fixtures.dart'; import '../test_helpers/media_items.dart'; @@ -268,6 +269,29 @@ void main() { expect(activities.last.cancellable, isTrue); }); + test('activity fetch abort reaches transport and preserves cancellation identity', () async { + final transport = _AbortAwareActivitiesClient(); + final client = testPlexClient( + serverId: publicServerId, + profileScopeId: defaultProfileScopeId, + httpClient: transport, + prioritizedEndpoints: const ['https://plex.example.com', 'https://plex-fallback.example.com'], + ); + addTearDown(client.close); + final abort = AbortController(); + + final activities = client.getActivities(abort: abort); + await transport.requestStarted.future; + abort.abort(); + + await expectLater( + activities, + throwsA(isA().having((error) => error.isCancellation, 'isCancellation', isTrue)), + ); + await expectLater(transport.abortObserved.future, completes); + expect(transport.requestCount, 1); + }); + test('metadata edit preserves locked fields and removed tag wire format', () async { http.Request? captured; final client = makeClient((request) async { diff --git a/test/services/settings_export_service_test.dart b/test/services/settings_export_service_test.dart index b24fc8b4..41920af9 100644 --- a/test/services/settings_export_service_test.dart +++ b/test/services/settings_export_service_test.dart @@ -5,8 +5,11 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:plezy/models/shader_preset.dart'; import 'package:plezy/services/base_shared_preferences_service.dart'; import 'package:plezy/services/settings_export_service.dart'; +import 'package:plezy/services/settings_service.dart'; +import 'package:plezy/services/trackers/tracker_constants.dart'; import '../test_helpers/prefs.dart'; @@ -34,14 +37,16 @@ void main() { }); group('portable settings registry', () { - test('exports every supported storage type and strips only active-user library scope', () async { + test('exports scalar and JSON-backed values and strips only the active-user library scope', () async { final prefs = await BaseSharedPreferencesService.sharedCache(); await prefs.setBool('enable_hardware_decoding', true); await prefs.setInt('seek_time_small', 42); await prefs.setDouble('volume', 75.5); await prefs.setString('preferred_video_codec', 'h264'); - await prefs.setStringList('user_alice_library_order', const ['movies', 'shows']); - await prefs.setStringList('user_bob_library_order', const ['private']); + await prefs.setString('user_alice_hidden_libraries', jsonEncode(['server-a:hidden'])); + await prefs.setString('user_alice_library_order', jsonEncode(['movies', 'shows'])); + await prefs.setString('user_bob_hidden_libraries', jsonEncode(['private-hidden'])); + await prefs.setString('user_bob_library_order', jsonEncode(['private-order'])); final out = SettingsExportService.buildExportMap(prefs, currentUserUuid: 'alice', appVersion: '1.2.3'); final exported = out['prefs'] as Map; @@ -53,11 +58,16 @@ void main() { expect(exported['seek_time_small'], {'type': 'int', 'value': 42}); expect(exported['volume'], {'type': 'double', 'value': 75.5}); expect(exported['preferred_video_codec'], {'type': 'string', 'value': 'h264'}); - expect(exported['library_order'], { - 'type': 'stringList', - 'value': ['movies', 'shows'], + expect(exported['hidden_libraries'], { + 'type': 'string', + 'value': jsonEncode(['server-a:hidden']), }); - expect(jsonEncode(out), isNot(contains('private'))); + expect(exported['library_order'], { + 'type': 'string', + 'value': jsonEncode(['movies', 'shows']), + }); + expect(jsonEncode(out), isNot(contains('private-hidden'))); + expect(jsonEncode(out), isNot(contains('private-order'))); }); test('excludes device-local download roots while preserving portable download controls', () async { @@ -78,6 +88,32 @@ void main() { expect(encoded, isNot(contains(sourcePath))); }); + test('round-trips a custom shader selection as a portable disabled selection', () async { + const custom = ShaderPreset( + id: 'custom_local-only.glsl', + name: 'Local only', + type: ShaderPresetType.custom, + fileName: 'local-only.glsl', + ); + final prefs = await BaseSharedPreferencesService.sharedCache(); + await prefs.setString('custom_shader_presets', jsonEncode([custom.toJson()])); + await prefs.setString('global_shader_preset', custom.id); + + final export = SettingsExportService.buildExportMap(prefs, currentUserUuid: 'alice'); + final exported = export['prefs'] as Map; + expect(exported['global_shader_preset'], {'type': 'string', 'value': ShaderPreset.none.id}); + expect(exported, isNot(contains('custom_shader_presets'))); + expect(jsonEncode(export), isNot(contains(custom.fileName!))); + + await prefs.clear(); + final result = await SettingsExportService.applyImportMap(export, prefs, currentUserUuid: 'bob'); + + expect(result.keysImported, 1); + expect(result.keysSkipped, 0); + expect(prefs.getString('global_shader_preset'), ShaderPreset.none.id); + expect(prefs.getString('custom_shader_presets'), isNull); + }); + test('fails closed for unknown, credential, account, path, history, and runtime keys', () async { const canaries = ['SEERR-BEARER-CANARY', 'ACCOUNT-ID-CANARY', 'DEVICE-PATH-CANARY', 'RUNTIME-TIME-CANARY']; final prefs = await BaseSharedPreferencesService.sharedCache(); @@ -115,7 +151,7 @@ void main() { test('does not export any user-scoped value without an active user', () async { final prefs = await BaseSharedPreferencesService.sharedCache(); - await prefs.setStringList('user_alice_library_order', const ['movies']); + await prefs.setString('user_alice_library_order', jsonEncode(['movies'])); await prefs.setBool('enable_hdr', true); final exported = SettingsExportService.buildExportMap(prefs)['prefs'] as Map; @@ -169,6 +205,130 @@ void main() { expect(prefs.getBool('enable_hdr'), isNull); }); + test('round-trips both filter modes and IDs for every tracker service', () async { + final prefs = await BaseSharedPreferencesService.sharedCache(); + + for (final mode in TrackerLibraryFilterMode.values) { + await prefs.clear(); + for (final service in TrackerService.values) { + await prefs.setString(SettingsService.trackerFilterModePref(service).key, mode.name); + await prefs.setStringList(SettingsService.trackerFilterIdsPref(service).key, [ + '${service.name}:library-a', + '${service.name}:library-b', + ]); + } + + final export = SettingsExportService.buildExportMap(prefs, currentUserUuid: 'source-user'); + final exported = export['prefs'] as Map; + for (final service in TrackerService.values) { + final modeKey = SettingsService.trackerFilterModePref(service).key; + final idsKey = SettingsService.trackerFilterIdsPref(service).key; + expect(exported[modeKey], {'type': 'string', 'value': mode.name}); + expect(exported[idsKey], { + 'type': 'stringList', + 'value': ['${service.name}:library-a', '${service.name}:library-b'], + }); + } + + await prefs.clear(); + final result = await SettingsExportService.applyImportMap(export, prefs, currentUserUuid: 'target-user'); + + expect(result.keysImported, TrackerService.values.length * 2); + expect(result.keysSkipped, 0); + for (final service in TrackerService.values) { + expect(prefs.getString(SettingsService.trackerFilterModePref(service).key), mode.name); + expect(prefs.getStringList(SettingsService.trackerFilterIdsPref(service).key), [ + '${service.name}:library-a', + '${service.name}:library-b', + ]); + } + } + }); + + test('preserves an empty whitelist so import cannot broaden tracker access', () async { + final prefs = await BaseSharedPreferencesService.sharedCache(); + for (final service in TrackerService.values) { + await prefs.setString( + SettingsService.trackerFilterModePref(service).key, + TrackerLibraryFilterMode.whitelist.name, + ); + await prefs.setStringList(SettingsService.trackerFilterIdsPref(service).key, const []); + } + + final export = SettingsExportService.buildExportMap(prefs, currentUserUuid: 'source-user'); + await prefs.clear(); + final result = await SettingsExportService.applyImportMap(export, prefs, currentUserUuid: 'target-user'); + final settings = await SettingsService.getInstance(); + + expect(result.keysImported, TrackerService.values.length * 2); + expect(result.keysSkipped, 0); + for (final service in TrackerService.values) { + expect(settings.read(SettingsService.trackerFilterModePref(service)), TrackerLibraryFilterMode.whitelist); + expect(settings.read(SettingsService.trackerFilterIdsPref(service)), isEmpty); + expect(settings.isLibraryAllowedForTracker(service, '${service.name}:unlisted'), isFalse); + expect(settings.isLibraryAllowedForTracker(service, null), isFalse); + } + }); + + test('rejects tracker preference keys with unknown service suffixes', () async { + const modeKey = 'tracker_library_filter_mode_future'; + const idsKey = 'tracker_library_filter_ids_future'; + final prefs = await BaseSharedPreferencesService.sharedCache(); + await prefs.setString(modeKey, 'local-mode'); + await prefs.setStringList(idsKey, const ['local-id']); + + final exported = SettingsExportService.buildExportMap(prefs, currentUserUuid: 'alice')['prefs'] as Map; + expect(exported, isNot(contains(modeKey))); + expect(exported, isNot(contains(idsKey))); + + final result = await SettingsExportService.applyImportMap( + { + 'formatVersion': SettingsExportService.formatVersion, + 'prefs': { + modeKey: {'type': 'string', 'value': TrackerLibraryFilterMode.whitelist.name}, + idsKey: { + 'type': 'stringList', + 'value': ['crafted-id'], + }, + }, + }, + prefs, + currentUserUuid: 'alice', + ); + + expect(result.keysImported, 0); + expect(result.keysSkipped, 2); + expect(prefs.getString(modeKey), 'local-mode'); + expect(prefs.getStringList(idsKey), ['local-id']); + }); + + test('normalizes format-v1 hidden and order string lists into JSON string storage', () async { + final prefs = await BaseSharedPreferencesService.sharedCache(); + + final result = await SettingsExportService.applyImportMap( + { + 'formatVersion': 1, + 'prefs': { + 'hidden_libraries': { + 'type': 'stringList', + 'value': ['server-a:hidden'], + }, + 'library_order': { + 'type': 'stringList', + 'value': ['server-b:movies', 'server-a:shows'], + }, + }, + }, + prefs, + currentUserUuid: 'target-user', + ); + + expect(result.keysImported, 2); + expect(result.keysSkipped, 0); + expect(prefs.getString('user_target-user_hidden_libraries'), jsonEncode(['server-a:hidden'])); + expect(prefs.getString('user_target-user_library_order'), jsonEncode(['server-b:movies', 'server-a:shows'])); + }); + test('imports allowlisted values, re-scopes library settings, and skips unsafe entries', () async { const seerrCanary = 'SEERR-IMPORT-CANARY'; final prefs = await BaseSharedPreferencesService.sharedCache(); @@ -184,8 +344,8 @@ void main() { 'enable_hardware_decoding': {'type': 'bool', 'value': true}, 'default_playback_speed': {'type': 'double', 'value': 1}, 'library_order': { - 'type': 'stringList', - 'value': ['movies'], + 'type': 'string', + 'value': jsonEncode(['movies']), }, 'library_sort_movies': {'type': 'string', 'value': '{"key":"titleSort"}'}, 'seerr_session': {'type': 'string', 'value': seerrCanary}, @@ -206,7 +366,7 @@ void main() { expect(result.keysSkipped, 8); expect(prefs.getBool('enable_hardware_decoding'), isTrue); expect(prefs.getDouble('default_playback_speed'), 1.0); - expect(prefs.getStringList('user_alice_library_order'), ['movies']); + expect(prefs.getString('user_alice_library_order'), jsonEncode(['movies'])); expect(prefs.getString('user_alice_library_sort_movies'), '{"key":"titleSort"}'); expect(prefs.getString('seerr_session'), isNull); expect(prefs.getString('user_alice_seerr_session'), isNull); @@ -248,6 +408,26 @@ void main() { expect(prefs.getString('custom_download_path'), isNot(contains(sourcePath))); }); + test('reports an unresolved custom shader selection as skipped', () async { + final prefs = await BaseSharedPreferencesService.sharedCache(); + await prefs.setString('global_shader_preset', ShaderPreset.nvscalerDefault.id); + + final result = await SettingsExportService.applyImportMap( + { + 'formatVersion': SettingsExportService.formatVersion, + 'prefs': { + 'global_shader_preset': {'type': 'string', 'value': 'custom_missing.glsl'}, + }, + }, + prefs, + currentUserUuid: 'alice', + ); + + expect(result.keysImported, 0); + expect(result.keysSkipped, 1); + expect(prefs.getString('global_shader_preset'), ShaderPreset.nvscalerDefault.id); + }); + test('skips malformed or mismatched entries before applying valid mutations', () async { final prefs = await BaseSharedPreferencesService.sharedCache(); @@ -294,7 +474,7 @@ void main() { prefs, currentUserUuid: 'alice', ), - throwsA(isA()), + throwsA(isA()), ); expect(prefs.getBool('enable_hdr'), isFalse); @@ -304,7 +484,8 @@ void main() { test('round-trips portable values across user scopes without account identifiers', () async { final prefs = await BaseSharedPreferencesService.sharedCache(); await prefs.setBool('enable_hardware_decoding', true); - await prefs.setStringList('user_alice_library_order', const ['movies']); + await prefs.setString('user_alice_hidden_libraries', jsonEncode(['server-a:hidden'])); + await prefs.setString('user_alice_library_order', jsonEncode(['server-b:movies'])); final export = SettingsExportService.buildExportMap(prefs, currentUserUuid: 'alice'); expect(jsonEncode(export), isNot(contains('alice'))); @@ -312,11 +493,13 @@ void main() { final result = await SettingsExportService.applyImportMap(export, prefs, currentUserUuid: 'bob'); - expect(result.keysImported, 2); + expect(result.keysImported, 3); expect(result.keysSkipped, 0); expect(prefs.getBool('enable_hardware_decoding'), isTrue); - expect(prefs.getStringList('user_bob_library_order'), ['movies']); - expect(prefs.getStringList('user_alice_library_order'), isNull); + expect(prefs.getString('user_bob_hidden_libraries'), jsonEncode(['server-a:hidden'])); + expect(prefs.getString('user_bob_library_order'), jsonEncode(['server-b:movies'])); + expect(prefs.getString('user_alice_hidden_libraries'), isNull); + expect(prefs.getString('user_alice_library_order'), isNull); }); test('malicious import cannot replace any tvOS database recovery key', () async { @@ -389,7 +572,7 @@ void main() { expect(await SettingsExportService.exportToFile(), isNull); picker.saveError = PlatformException(code: 'save_failed'); - await expectLater(SettingsExportService.exportToFile(), throwsA(isA())); + await expectLater(SettingsExportService.exportToFile(), throwsA(isA())); picker.saveError = null; picker.saveResult = '/tmp/recovered.json'; @@ -432,11 +615,14 @@ void main() { picker.pickResult = FilePickerResult([ PlatformFile(name: 'missing.json', size: 1, path: '/path/that/does/not/exist.json'), ]); - await expectLater(SettingsExportService.importFromFile(), throwsA(isA())); + await expectLater(SettingsExportService.importFromFile(), throwsA(isA())); picker.pickResult = FilePickerResult([PlatformFile(name: 'settings.json', size: 1, bytes: importBytes())]); expect((await SettingsExportService.importFromFile())?.keysImported, 1); expect(picker.pickCalls, 4); + + picker.pickError = PlatformException(code: 'pick_failed'); + await expectLater(SettingsExportService.importFromFile(), throwsA(isA())); }); test('missing active profile rejects before opening the picker', () async { diff --git a/test/services/shader_asset_loader_test.dart b/test/services/shader_asset_loader_test.dart new file mode 100644 index 00000000..1dfa1ffa --- /dev/null +++ b/test/services/shader_asset_loader_test.dart @@ -0,0 +1,193 @@ +import 'dart:io'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:plezy/models/shader_preset.dart'; +import 'package:plezy/services/shader_asset_loader.dart'; + +import '../test_helpers/io_fakes.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late PathProviderPlatform originalPathProvider; + late Directory root; + late Directory supportDirectory; + late File sentinel; + + setUp(() async { + originalPathProvider = PathProviderPlatform.instance; + root = await Directory.systemTemp.createTemp('plezy_shader_asset_loader_test_'); + PathProviderPlatform.instance = FakePathProvider(root); + supportDirectory = Directory(path.join(root.path, 'support'))..createSync(recursive: true); + sentinel = File(path.join(supportDirectory.path, 'sentinel.glsl'))..writeAsStringSync('sentinel'); + ShaderAssetLoader.clearCache(); + }); + + tearDown(() async { + PathProviderPlatform.instance = originalPathProvider; + ShaderAssetLoader.clearCache(); + if (await root.exists()) await root.delete(recursive: true); + }); + + ShaderPreset customPreset(String fileName) { + return ShaderPreset(id: 'custom_$fileName', name: fileName, type: ShaderPresetType.custom, fileName: fileName); + } + + Future> bundledBytes(String assetPath) async { + final data = await rootBundle.load('assets/shaders/$assetPath'); + return data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); + } + + Future expectBundledFile(String filePath, String assetPath) async { + expect(await File(filePath).readAsBytes(), await bundledBytes(assetPath)); + } + + test('traversal and absolute names cannot load or delete outside managed directory', () async { + for (final fileName in ['../sentinel.glsl', sentinel.path]) { + expect(await ShaderAssetLoader.getShadersForPreset(customPreset(fileName)), isEmpty); + await ShaderAssetLoader.deleteCustomShader(fileName); + expect(await sentinel.readAsString(), 'sentinel'); + } + }); + + test('repairs stale built-in bytes in the application cache without touching temporary storage', () async { + final cacheFile = File(path.join(root.path, 'cache', 'shaders', 'nvscaler', 'NVScaler.glsl')) + ..createSync(recursive: true) + ..writeAsStringSync('stale'); + final oldTemporaryFile = File(path.join(root.path, 'temp', 'shaders', 'nvscaler', 'NVScaler.glsl')) + ..createSync(recursive: true) + ..writeAsStringSync('old temporary sentinel'); + + final shaders = await ShaderAssetLoader.getShadersForPreset(ShaderPreset.nvscalerDefault); + + expect(shaders, [cacheFile.path]); + await expectBundledFile(shaders.single, 'nvscaler/NVScaler.glsl'); + expect(await oldTemporaryFile.readAsString(), 'old temporary sentinel'); + }); + + test('recovers a truncated built-in final through a complete staged replacement', () async { + final targetDirectory = Directory(path.join(root.path, 'cache', 'shaders', 'nvscaler')) + ..createSync(recursive: true); + final target = File(path.join(targetDirectory.path, 'NVScaler.glsl'))..writeAsBytesSync([1, 2, 3]); + final unrelatedPending = File('${target.path}.pending.interrupted')..writeAsBytesSync([1]); + + final shaders = await ShaderAssetLoader.getNVScalerShaders(); + + expect(shaders, [target.path]); + await expectBundledFile(target.path, 'nvscaler/NVScaler.glsl'); + expect(unrelatedPending.existsSync(), isTrue); + final ownedPending = await targetDirectory + .list() + .where((entry) => entry.path.contains('.pending.') && entry.path != unrelatedPending.path) + .toList(); + expect(ownedPending, isEmpty); + }); + + test('does not return a partial path when final-file promotion fails', () async { + final target = Directory(path.join(root.path, 'cache', 'shaders', 'nvscaler', 'NVScaler.glsl')) + ..createSync(recursive: true); + + expect(await ShaderAssetLoader.getNVScalerShaders(), isEmpty); + + final siblings = await target.parent.list().toList(); + expect(siblings.where((entry) => entry.path.contains('.pending.')), isEmpty); + expect(target.existsSync(), isTrue); + }); + + test('coalesces concurrent built-in loads onto one complete final path', () async { + final results = await Future.wait([ShaderAssetLoader.getNVScalerShaders(), ShaderAssetLoader.getNVScalerShaders()]); + + expect(results[0], results[1]); + expect(results[0], hasLength(1)); + await expectBundledFile(results[0].single, 'nvscaler/NVScaler.glsl'); + final directory = File(results[0].single).parent; + expect((await directory.list().toList()).where((entry) => entry.path.contains('.pending.')), isEmpty); + }); + + test('materializes representative built-in chains in MPV order with bundled bytes', () async { + final nvscaler = await ShaderAssetLoader.getNVScalerShaders(); + final artcnn = await ShaderAssetLoader.getArtCNNShaders( + const ArtCNNConfig(model: ArtCNNModel.c4f16, variant: ArtCNNVariant.denoise), + ); + final anime4k = await ShaderAssetLoader.getAnime4KShaders( + const Anime4KConfig(quality: Anime4KQuality.fast, mode: Anime4KMode.modeB), + ); + + final expected = { + nvscaler.single: 'nvscaler/NVScaler.glsl', + artcnn.single: 'artcnn/ArtCNN_C4F16_DN.glsl', + anime4k[0]: 'anime4k/Anime4K_Clamp_Highlights.glsl', + anime4k[1]: 'anime4k/Anime4K_Restore_CNN_M.glsl', + anime4k[2]: 'anime4k/Anime4K_Upscale_CNN_x2_M.glsl', + anime4k[3]: 'anime4k/Anime4K_AutoDownscalePre_x2.glsl', + }; + expect(anime4k.map(path.basename).toList(), [ + 'Anime4K_Clamp_Highlights.glsl', + 'Anime4K_Restore_CNN_M.glsl', + 'Anime4K_Upscale_CNN_x2_M.glsl', + 'Anime4K_AutoDownscalePre_x2.glsl', + ]); + for (final entry in expected.entries) { + expect(path.isWithin(path.join(root.path, 'cache'), entry.key), isTrue); + await expectBundledFile(entry.key, entry.value); + } + }); + + test('nested and non-GLSL names are rejected without touching matching files', () async { + final customDirectory = Directory(path.join(supportDirectory.path, 'custom_shaders'))..createSync(recursive: true); + final nested = File(path.join(customDirectory.path, 'subdir', 'name.glsl')) + ..createSync(recursive: true) + ..writeAsStringSync('nested'); + final extraExtension = File(path.join(customDirectory.path, 'name.glsl.txt'))..writeAsStringSync('extra'); + + for (final fileName in ['subdir/name.glsl', r'subdir\name.glsl', '.', '..', 'name.glsl.txt', 'name.txt']) { + expect(ShaderAssetLoader.isValidCustomShaderFileName(fileName), isFalse); + expect(await ShaderAssetLoader.getShadersForPreset(customPreset(fileName)), isEmpty); + await ShaderAssetLoader.deleteCustomShader(fileName); + } + + expect(await nested.readAsString(), 'nested'); + expect(await extraExtension.readAsString(), 'extra'); + }); + + test('non-GLSL import fails before creating a managed file', () async { + final source = File(path.join(root.path, 'shader.txt'))..writeAsStringSync('not glsl'); + + await expectLater(ShaderAssetLoader.importCustomShader(source.path), throwsArgumentError); + + final customDirectory = Directory(path.join(supportDirectory.path, 'custom_shaders')); + expect(customDirectory.existsSync(), isFalse); + }); + + test('imports a direct UUID GLSL child and deletes only that file', () async { + final source = File(path.join(root.path, 'shader.GLSL'))..writeAsStringSync('shader'); + + final storedName = await ShaderAssetLoader.importCustomShader(source.path); + expect(storedName, matches(RegExp(r'^[0-9a-f-]+\.glsl$'))); + expect(ShaderAssetLoader.isValidCustomShaderFileName(storedName), isTrue); + + final shaders = await ShaderAssetLoader.getShadersForPreset(customPreset(storedName)); + expect(shaders, hasLength(1)); + expect(path.dirname(shaders.single), path.join(supportDirectory.path, 'custom_shaders')); + expect(await File(shaders.single).readAsString(), 'shader'); + + await ShaderAssetLoader.deleteCustomShader(storedName); + expect(File(shaders.single).existsSync(), isFalse); + expect(await source.readAsString(), 'shader'); + }); + + test('legacy base-36 managed names remain loadable and deletable', () async { + const storedName = 'ks9p7.glsl'; + final customDirectory = Directory(path.join(supportDirectory.path, 'custom_shaders'))..createSync(recursive: true); + final managedFile = File(path.join(customDirectory.path, storedName))..writeAsStringSync('legacy'); + + expect(ShaderAssetLoader.isValidCustomShaderFileName(storedName), isTrue); + expect(await ShaderAssetLoader.getShadersForPreset(customPreset(storedName)), [managedFile.path]); + + await ShaderAssetLoader.deleteCustomShader(storedName); + expect(managedFile.existsSync(), isFalse); + }); +} diff --git a/test/services/shader_service_test.dart b/test/services/shader_service_test.dart new file mode 100644 index 00000000..41975c2a --- /dev/null +++ b/test/services/shader_service_test.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:plezy/models/shader_preset.dart'; +import 'package:plezy/mpv/player/player.dart'; +import 'package:plezy/services/shader_service.dart'; + +import '../test_helpers/io_fakes.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late PathProviderPlatform originalPathProvider; + late Directory root; + + setUp(() async { + originalPathProvider = PathProviderPlatform.instance; + root = await Directory.systemTemp.createTemp('plezy_shader_service_test_'); + PathProviderPlatform.instance = FakePathProvider(root); + }); + + tearDown(() async { + PathProviderPlatform.instance = originalPathProvider; + if (await root.exists()) await root.delete(recursive: true); + }); + + test('an escaped custom preset never reaches the MPV shader append command', () async { + final supportDirectory = Directory(path.join(root.path, 'support'))..createSync(recursive: true); + final sentinel = File(path.join(supportDirectory.path, 'sentinel.glsl'))..writeAsStringSync('sentinel'); + final player = _RecordingPlayer(); + final service = ShaderService(player); + const preset = ShaderPreset( + id: 'custom_traversal', + name: 'Traversal', + type: ShaderPresetType.custom, + fileName: '../sentinel.glsl', + ); + + await service.applyPreset(preset); + + expect(player.commands.where((command) => command.length > 2 && command[2] == 'append'), isEmpty); + expect(player.commands.single, ['change-list', 'glsl-shaders', 'clr', '']); + expect(await sentinel.readAsString(), 'sentinel'); + }); +} + +class _RecordingPlayer implements Player { + final commands = >[]; + + @override + String get playerType => 'mpv'; + + @override + Future command(List args) async { + commands.add(List.unmodifiable(args)); + } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} diff --git a/test/utils/initials_palette_test.dart b/test/utils/initials_palette_test.dart new file mode 100644 index 00000000..41475adf --- /dev/null +++ b/test/utils/initials_palette_test.dart @@ -0,0 +1,19 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/utils/initials_palette.dart'; + +void main() { + group('initialOf', () { + test('keeps fallback, trimming, and ordinary casing behavior', () { + expect(initialOf(''), '?'); + expect(initialOf(' \t\n'), '?'); + expect(initialOf(' alice '), 'A'); + }); + + test('returns the complete first extended grapheme', () { + expect(initialOf('๐Ÿ‡ฏ๐Ÿ‡ต Japan'), '๐Ÿ‡ฏ๐Ÿ‡ต'); + expect(initialOf('๐Ÿ‘๐Ÿฝ Approved'), '๐Ÿ‘๐Ÿฝ'); + expect(initialOf('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Family'), '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ'); + expect(initialOf('e\u0301clair'), 'E\u0301'); + }); + }); +} diff --git a/test/utils/search_relevance_test.dart b/test/utils/search_relevance_test.dart new file mode 100644 index 00000000..8f085655 --- /dev/null +++ b/test/utils/search_relevance_test.dart @@ -0,0 +1,132 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/media/media_item.dart'; +import 'package:plezy/utils/search_relevance.dart'; + +import '../test_helpers/media_items.dart'; + +void main() { + group('normalizeSearchText', () { + test('normalizes canonical, compatibility, separator, and typography variants', () { + expect(normalizeSearchText('Amรฉlie'), normalizeSearchText('Ame\u0301lie')); + expect(normalizeSearchText('๏ผก๏ฝ๏ฝ…๏ฝŒ๏ฝ‰๏ฝ…'), normalizeSearchText('Amelie')); + expect(normalizeSearchText('Spider\u00a0Man'), normalizeSearchText('Spider Man')); + + final expected = normalizeSearchText('Spider Man'); + for (final value in ['Spider-Man', 'Spiderโ€‘Man', 'Spiderโ€“Man', 'Spiderโ€”Man']) { + expect(normalizeSearchText(value), expected, reason: value); + } + }); + + test('preserves accents and script-significant marks', () { + expect(normalizeSearchText('Cafe'), isNot(normalizeSearchText('Cafรฉ'))); + expect(normalizeSearchText('Cafรฉ'), normalizeSearchText('Cafe\u0301')); + expect(normalizeSearchText('เค•'), isNot(normalizeSearchText('เค•เคฟ'))); + expect(normalizeSearchText('เค•เคฟ'), contains('เคฟ')); + }); + }); + + group('media search ranking', () { + test('does not give an English-only leading article bonus', () { + final items = [ + testMediaItem(id: 'the', title: 'The Boys'), + testMediaItem(id: 'les', title: 'Les Boys'), + testMediaItem(id: 'los', title: 'Los Boys'), + ]; + + expect(_ids(rankMediaSearchResults(items, 'Boys')), ['the', 'les', 'los']); + expect(_ids(rankMediaSearchResults(items.reversed.toList(), 'Boys')), ['los', 'les', 'the']); + }); + + test('keeps exact titles above longer prefixes', () { + final items = [ + testMediaItem(id: 'longer', title: 'The Boys in the Boat'), + testMediaItem(id: 'exact', title: 'The Boys'), + ]; + + expect(_ids(rankMediaSearchResults(items, 'The Boys')), ['exact', 'longer']); + }); + + test('preserves nullable-field weighting', () { + final items = [ + testMediaItem(id: 'parent', parentTitle: 'Target'), + testMediaItem(id: 'original', originalTitle: 'Target'), + ]; + + expect(_ids(rankMediaSearchResults(items, 'Target')), ['original', 'parent']); + }); + }); + + group('rankMediaSearchResults', () { + test('ranks equivalent query forms identically and preserves normalized ties', () { + final items = [ + testMediaItem(id: 'first', title: 'Spider-Man'), + testMediaItem(id: 'second', title: 'Spiderโ€‘Man'), + testMediaItem(id: 'third', title: 'Spider Man Returns'), + testMediaItem(id: 'accented', title: 'Amรฉlie'), + testMediaItem(id: 'decomposed', title: 'Ame\u0301lie'), + ]; + + final asciiOrder = _ids(rankMediaSearchResults(items, 'Spider Man')); + final compatibilityOrder = _ids(rankMediaSearchResults(items, '๏ผณ๏ฝ๏ฝ‰๏ฝ„๏ฝ…๏ฝ’ใ€€๏ผญ๏ฝ๏ฝŽ')); + final typographicOrder = _ids(rankMediaSearchResults(items, 'Spiderโ€”Man')); + + expect(compatibilityOrder, asciiOrder); + expect(typographicOrder, asciiOrder); + expect(asciiOrder.take(2), ['first', 'second']); + expect(_ids(rankMediaSearchResults(items, 'Spider Man', limit: 1)), ['first']); + expect(_ids(rankMediaSearchResults(items, 'Amรฉlie')).take(2), ['accented', 'decomposed']); + }); + + test('bounded selection matches the unbounded full ordering for a large input', () { + final items = [ + for (var i = 0; i < 1000; i++) + testMediaItem( + id: 'item-$i', + title: switch (i) { + 999 => 'Target', + _ when i % 137 == 0 => 'Target result $i', + _ when i % 41 == 0 => 'A Target result $i', + _ => 'Candidate $i', + }, + ), + ]; + final expected = _fullyRankedIds(items, 'Target').take(100).toList(); + + expect(_ids(rankMediaSearchResults(items, 'Target', limit: 100)), expected); + expect(expected.first, 'item-999'); + }); + + test('retains the earliest items when an equal-score run crosses the cap', () { + final items = [for (var i = 0; i < 150; i++) testMediaItem(id: 'tie-$i', title: 'Same title')]; + + expect(_ids(rankMediaSearchResults(items, 'Same title', limit: 100)), [for (var i = 0; i < 100; i++) 'tie-$i']); + }); + + test('preserves limit boundaries, full ordering, and empty-query input order', () { + final items = [ + testMediaItem(id: 'prefix', title: 'Target Extended'), + testMediaItem(id: 'exact', title: 'Target'), + testMediaItem(id: 'contains', title: 'A Target Story'), + ]; + final expected = _fullyRankedIds(items, 'Target'); + + expect(rankMediaSearchResults(items, 'Target', limit: 0), isEmpty); + expect(_ids(rankMediaSearchResults(items, 'Target', limit: 1)), ['exact']); + expect(_ids(rankMediaSearchResults(items, 'Target', limit: items.length)), expected); + expect(_ids(rankMediaSearchResults(items, 'Target', limit: items.length + 5)), expected); + expect(_ids(rankMediaSearchResults(items, 'Target')), expected); + expect(_ids(rankMediaSearchResults(items, 'โ€” โ€‘ !!!', limit: 2)), ['prefix', 'exact']); + }); + + test('keeps the negative-limit failure contract', () { + final items = [testMediaItem(title: 'Target')]; + + expect(() => rankMediaSearchResults(items, 'Target', limit: -1), throwsRangeError); + expect(() => rankMediaSearchResults(items, '!!!', limit: -1), throwsRangeError); + }); + }); +} + +List _ids(Iterable items) => [for (final item in items) item.id]; + +List _fullyRankedIds(List items, String query) => _ids(rankMediaSearchResults(items, query)); diff --git a/test/widgets/app_bar_back_button_test.dart b/test/widgets/app_bar_back_button_test.dart new file mode 100644 index 00000000..103517fb --- /dev/null +++ b/test/widgets/app_bar_back_button_test.dart @@ -0,0 +1,58 @@ +import 'dart:ui' show SemanticsAction; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/focus/input_mode_tracker.dart'; +import 'package:plezy/i18n/strings.g.dart'; +import 'package:plezy/widgets/app_bar_back_button.dart'; + +void main() { + Future pumpButton(WidgetTester tester, {required FocusNode focusNode, required VoidCallback onPressed}) async { + await tester.pumpWidget( + TranslationProvider( + child: MaterialApp( + home: InputModeTracker( + child: Scaffold( + body: AppBarBackButton(focusNode: focusNode, onPressed: onPressed), + ), + ), + ), + ), + ); + } + + testWidgets('pointer, D-pad select, and Space activate exactly once', (tester) async { + final focusNode = FocusNode(debugLabel: 'back_button_test'); + addTearDown(focusNode.dispose); + var activations = 0; + + await pumpButton(tester, focusNode: focusNode, onPressed: () => activations++); + + await tester.tap(find.byType(AppBarBackButton)); + expect(activations, 1); + + focusNode.requestFocus(); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.select); + expect(activations, 2); + + await tester.sendKeyEvent(LogicalKeyboardKey.space); + expect(activations, 3); + }); + + testWidgets('exposes one localized operable button node', (tester) async { + final semantics = tester.ensureSemantics(); + final focusNode = FocusNode(debugLabel: 'back_button_semantics'); + addTearDown(focusNode.dispose); + + await pumpButton(tester, focusNode: focusNode, onPressed: () {}); + + final finder = find.bySemanticsLabel(t.common.back); + expect(finder, findsOneWidget); + final data = tester.getSemantics(finder).getSemanticsData(); + expect(data.flagsCollection.isButton, isTrue); + expect(data.hasAction(SemanticsAction.tap), isTrue); + semantics.dispose(); + }); +} diff --git a/test/widgets/cycling_media_backdrop_test.dart b/test/widgets/cycling_media_backdrop_test.dart index ee66ae0f..593071fe 100644 --- a/test/widgets/cycling_media_backdrop_test.dart +++ b/test/widgets/cycling_media_backdrop_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/media/media_item.dart'; import 'package:plezy/media/media_kind.dart'; import 'package:plezy/services/settings_service.dart'; +import 'package:plezy/utils/media_image_helper.dart'; import 'package:plezy/widgets/cycling_media_backdrop.dart'; import 'package:plezy/widgets/tv_spotlight_background.dart'; @@ -62,7 +63,6 @@ void main() { mediaKey: mediaKey, imagePaths: paths, client: null, - localArtworkPathResolver: (path) => path, imageProviderResolver: (path) => imageProviders[path], allowNetwork: false, active: active, @@ -78,6 +78,48 @@ void main() { ); } + Widget buildAsyncBackdrop( + List paths, { + required Future Function(File file) fileExists, + Object? mediaKey = 'movie-1', + List fallbackPaths = const [], + }) { + final (memWidth, memHeight) = MediaImageHelper.getMemCacheDimensions( + displayWidth: 320, + displayHeight: 180, + imageType: ImageType.art, + ); + return MaterialApp( + home: MediaQuery( + data: const MediaQueryData(size: Size(320, 180), devicePixelRatio: 1), + child: SizedBox( + width: 320, + height: 180, + child: CyclingMediaBackdrop( + mediaKey: mediaKey, + imagePaths: paths, + fallbackImagePaths: fallbackPaths, + client: null, + localArtworkPathResolver: (path) => path, + imageProviderResolver: (path) { + final provider = imageProviders[path]; + return provider == null + ? null + : MediaImageHelper.boundedDecode(provider, memWidth: memWidth, memHeight: memHeight); + }, + localFileExists: fileExists, + allowNetwork: false, + width: 320, + height: 180, + fallbackColor: Colors.black, + rotationInterval: _rotationInterval, + fadeDuration: _fadeDuration, + ), + ), + ), + ); + } + String pathForProvider(ImageProvider provider) { while (provider is ResizeImage) { provider = provider.imageProvider; @@ -101,7 +143,11 @@ void main() { Future finishImageTransition(WidgetTester tester, {Duration fadeDuration = _fadeDuration}) async { final incoming = find.byType(Image).last; final image = tester.widget(incoming); - if (image.image is MemoryImage) { + var source = image.image; + while (source is ResizeImage) { + source = source.imageProvider; + } + if (source is MemoryImage) { final configuration = createLocalImageConfiguration(tester.element(incoming)); await tester.runAsync(() { final frame = Completer(); @@ -161,6 +207,113 @@ void main() { await tester.pumpWidget(const SizedBox.shrink()); }); + testWidgets('pending local rotation keeps the settled backdrop until the file resolves', (tester) async { + final checks = >{}; + final probes = {}; + Future fileExists(File file) { + probes.update(file.path, (count) => count + 1, ifAbsent: () => 1); + return (checks[file.path] ??= Completer()).future; + } + + await tester.pumpWidget(buildAsyncBackdrop([first.path, second.path], fileExists: fileExists)); + checks[first.path]!.complete(true); + await tester.pump(); + await tester.pump(); + await finishImageTransition(tester); + expectVisibleBackdrop(tester, first.path); + + await tester.pump(_rotationInterval); + expect(checks[second.path], isNotNull); + expect(renderedFilePaths(tester), [first.path]); + expect(probes[second.path], 1); + + checks[second.path]!.complete(true); + await tester.pump(); + await tester.pump(); + expect(renderedFilePaths(tester), [first.path, second.path]); + final incoming = tester.widget(find.byType(Image).last); + final resized = incoming.image as ResizeImage; + final (expectedWidth, expectedHeight) = MediaImageHelper.getMemCacheDimensions( + displayWidth: 320, + displayHeight: 180, + imageType: ImageType.art, + ); + expect(resized.width, expectedWidth); + expect(resized.height, expectedHeight); + await finishImageTransition(tester); + expectVisibleBackdrop(tester, second.path); + + await tester.pumpWidget(const SizedBox.shrink()); + }); + + testWidgets('late local completion cannot replace a newer media path', (tester) async { + final checks = >{}; + Future fileExists(File file) => (checks[file.path] ??= Completer()).future; + + await tester.pumpWidget(buildAsyncBackdrop([first.path], fileExists: fileExists, mediaKey: 'movie-a')); + expect(find.byType(Image), findsNothing); + + await tester.pumpWidget(buildAsyncBackdrop([second.path], fileExists: fileExists, mediaKey: 'movie-b')); + checks[second.path]!.complete(true); + await tester.pump(); + await tester.pump(); + await finishImageTransition(tester); + expectVisibleBackdrop(tester, second.path); + + checks[first.path]!.complete(true); + await tester.pump(); + await tester.pump(); + await tester.pump(); + expect(renderedFilePaths(tester), [second.path]); + expectVisibleBackdrop(tester, second.path); + + await tester.pumpWidget(const SizedBox.shrink()); + }); + + testWidgets('confirmed missing local path advances once and stays cached', (tester) async { + final missing = '${directory.path}/missing.png'; + final checks = >{}; + final probes = {}; + Future fileExists(File file) { + probes.update(file.path, (count) => count + 1, ifAbsent: () => 1); + return (checks[file.path] ??= Completer()).future; + } + + await tester.pumpWidget(buildAsyncBackdrop([first.path], fileExists: fileExists, mediaKey: 'settled')); + checks[first.path]!.complete(true); + await tester.pump(); + await tester.pump(); + await finishImageTransition(tester); + expectVisibleBackdrop(tester, first.path); + + await tester.pumpWidget( + buildAsyncBackdrop([missing, second.path], fileExists: fileExists, mediaKey: 'replacement'), + ); + expect(renderedFilePaths(tester), [first.path]); + + checks[missing]!.complete(false); + await tester.pump(); + await tester.pump(); + await tester.pump(); + expect(checks[second.path], isNotNull); + expect(renderedFilePaths(tester), [first.path]); + + checks[second.path]!.complete(true); + await tester.pump(); + await tester.pump(); + await finishImageTransition(tester); + expectVisibleBackdrop(tester, second.path); + + await tester.pumpWidget( + buildAsyncBackdrop([missing, second.path], fileExists: fileExists, mediaKey: 'replacement'), + ); + await tester.pump(_rotationInterval * 2); + expect(probes[missing], 1); + expectVisibleBackdrop(tester, second.path); + + await tester.pumpWidget(const SizedBox.shrink()); + }); + testWidgets('pauses while the application is not resumed', (tester) async { addTearDown(() => tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed)); await tester.pumpWidget(buildBackdrop([first.path, second.path])); @@ -244,12 +397,16 @@ void main() { ), ), ); + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 20))); + await tester.pump(); + await tester.pump(); expect(renderedFilePaths(tester), [first.path]); await tester.pump(const Duration(seconds: 10)); + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 20))); + await tester.pump(); + await tester.pump(); expect(renderedFilePaths(tester).last, second.path); - await finishImageTransition(tester, fadeDuration: const Duration(milliseconds: 280)); - expectVisibleBackdrop(tester, second.path); await tester.pumpWidget(const SizedBox.shrink()); }); diff --git a/test/widgets/dialog_action_button_test.dart b/test/widgets/dialog_action_button_test.dart index dc09c2b7..150c8d20 100644 --- a/test/widgets/dialog_action_button_test.dart +++ b/test/widgets/dialog_action_button_test.dart @@ -29,7 +29,7 @@ void main() { expect(backed, 1); }); - testWidgets('nullable callback keeps its graph position while disabling activation', (tester) async { + testWidgets('nullable callback disables activation and removes the action from traversal', (tester) async { final focusNode = FocusNode(debugLabel: 'disabled dialog action'); addTearDown(focusNode.dispose); @@ -48,9 +48,11 @@ void main() { ); await tester.pump(); - expect(focusNode.hasFocus, isTrue); + expect(focusNode.canRequestFocus, isFalse); + expect(focusNode.hasFocus, isFalse); expect(tester.widget(find.byType(FilledButton)).onPressed, isNull); - await tester.sendKeyEvent(LogicalKeyboardKey.enter); - expect(focusNode.hasFocus, isTrue); + focusNode.requestFocus(); + await tester.pump(); + expect(focusNode.hasFocus, isFalse); }); } diff --git a/test/widgets/focusable_text_field_test.dart b/test/widgets/focusable_text_field_test.dart index c709e8fe..0346ab26 100644 --- a/test/widgets/focusable_text_field_test.dart +++ b/test/widgets/focusable_text_field_test.dart @@ -674,6 +674,128 @@ void main() { expect(controller.selection, const TextSelection.collapsed(offset: 1)); }); + testWidgets('TV hardware caret and deletion stay on grapheme boundaries', (tester) async { + TvDetectionService.debugSetAppleTVOverride(null); + await TvDetectionService.getInstance(forceTv: true); + TvDetectionService.setForceTVSync(true); + final controller = TextEditingController(); + final fieldFocusNode = FocusNode(debugLabel: 'grapheme_field'); + addTearDown(controller.dispose); + addTearDown(fieldFocusNode.dispose); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FocusableTextField( + controller: controller, + focusNode: fieldFocusNode, + tvKeyboardAutoOpenBehavior: TvKeyboardAutoOpenBehavior.never, + ), + ), + ), + ); + fieldFocusNode.requestFocus(); + await tester.pump(); + + for (final grapheme in ['๐Ÿ˜€', 'e\u0301', '๐Ÿ‡ฏ๐Ÿ‡ต', '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ']) { + final text = 'A${grapheme}B'; + final graphemeEnd = 1 + grapheme.length; + + controller.value = TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: graphemeEnd), + ); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); + expect(controller.selection, TextSelection.collapsed(offset: graphemeEnd)); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); + expect(controller.selection, TextSelection.collapsed(offset: text.length)); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); + expect(controller.selection, TextSelection.collapsed(offset: text.length)); + + controller.selection = const TextSelection.collapsed(offset: 2); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + controller.selection = const TextSelection.collapsed(offset: 2); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); + expect(controller.selection, TextSelection.collapsed(offset: graphemeEnd)); + + controller.value = TextEditingValue(text: text, selection: const TextSelection.collapsed(offset: 0)); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); + await tester.sendKeyEvent(LogicalKeyboardKey.backspace); + expect(controller.text, text); + expect(controller.selection, const TextSelection.collapsed(offset: 0)); + await tester.sendKeyEvent(LogicalKeyboardKey.delete); + expect(controller.text, '${grapheme}B'); + expect(controller.selection, const TextSelection.collapsed(offset: 0)); + + controller.value = TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: graphemeEnd), + ); + await tester.sendKeyEvent(LogicalKeyboardKey.backspace); + expect(controller.text, 'AB'); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + + controller.value = TextEditingValue(text: text, selection: const TextSelection.collapsed(offset: 1)); + await tester.sendKeyEvent(LogicalKeyboardKey.delete); + expect(controller.text, 'AB'); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + + controller.value = TextEditingValue( + text: text, + selection: TextSelection(baseOffset: graphemeEnd - 1, extentOffset: 1), + ); + await tester.sendKeyEvent(LogicalKeyboardKey.backspace); + expect(controller.text, 'AB'); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + } + }); + + testWidgets('grapheme deletion preserves formatter and callback ordering', (tester) async { + TvDetectionService.debugSetAppleTVOverride(null); + await TvDetectionService.getInstance(forceTv: true); + TvDetectionService.setForceTVSync(true); + const grapheme = '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ'; + final text = 'A${grapheme}B'; + final controller = TextEditingController(text: text); + final fieldFocusNode = FocusNode(debugLabel: 'formatted_grapheme_field'); + final formatterCandidates = []; + final changes = []; + addTearDown(controller.dispose); + addTearDown(fieldFocusNode.dispose); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FocusableTextField( + controller: controller, + focusNode: fieldFocusNode, + tvKeyboardAutoOpenBehavior: TvKeyboardAutoOpenBehavior.never, + maxLength: 8, + inputFormatters: [ + TextInputFormatter.withFunction((_, nextValue) { + formatterCandidates.add(nextValue); + return nextValue; + }), + ], + onChanged: changes.add, + ), + ), + ), + ); + fieldFocusNode.requestFocus(); + await tester.pump(); + controller.selection = TextSelection.collapsed(offset: 1 + grapheme.length); + + await tester.sendKeyEvent(LogicalKeyboardKey.backspace); + + expect(formatterCandidates.single.text, 'AB'); + expect(controller.text, 'AB'); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + expect(changes, ['AB']); + }); testWidgets('TV keyboard done resolves callbacks against the latest field widget', (tester) async { TvDetectionService.debugSetAppleTVOverride(null); await TvDetectionService.getInstance(forceTv: true); diff --git a/test/widgets/hotkey_recorder_test.dart b/test/widgets/hotkey_recorder_test.dart index b20ba38b..285ba6fc 100644 --- a/test/widgets/hotkey_recorder_test.dart +++ b/test/widgets/hotkey_recorder_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -12,7 +14,7 @@ void main() { tearDown(SelectKeyUpSuppressor.clearSuppression); testWidgets('initially unbound shortcut captures from a tap and saves', (tester) async { - final saved = []; + final saved = []; await _pumpRecorder(tester, saved: saved); expect(_recorder(tester).enabled, isFalse); @@ -39,48 +41,106 @@ void main() { await tester.tap(find.widgetWithText(FilledButton, t.common.save)); expect(saved, hasLength(1)); - expect(saved.single.key, PhysicalKeyboardKey.keyK); - expect(saved.single.modifiers, isNull); + expect(saved.single!.key, PhysicalKeyboardKey.keyK); + expect(saved.single!.modifiers, isNull); }); - testWidgets('cleared shortcut can capture and save a replacement', (tester) async { - final saved = []; + testWidgets('cleared assigned shortcut saves an explicit unassignment', (tester) async { + final saved = []; await _pumpRecorder( tester, saved: saved, currentHotKey: const HotKey(key: PhysicalKeyboardKey.keyJ, modifiers: [HotKeyModifier.shift]), ); - expect(find.text(physicalKeyLabel(PhysicalKeyboardKey.keyJ)), findsOneWidget); - expect(_saveAction(tester).onPressed, isNotNull); - await tester.tap(find.byTooltip(t.hotkeys.clearShortcut)); await tester.pump(); expect(_recorder(tester).enabled, isFalse); expect(find.text(physicalKeyLabel(PhysicalKeyboardKey.keyJ)), findsNothing); - expect(find.text(t.hotkeys.pressToRecord), findsNWidgets(2)); expect(FocusManager.instance.primaryFocus?.debugLabel, 'HotKeyRecorder.record'); - expect(_saveAction(tester).onPressed, isNull); - - await tester.tap(find.byType(HotKeyRecorder)); - await tester.pump(); - await tester.sendKeyEvent(LogicalKeyboardKey.keyL, physicalKey: PhysicalKeyboardKey.keyL); - await _pumpFocusChange(tester); - - expect(_recorder(tester).enabled, isFalse); - expect(find.text(physicalKeyLabel(PhysicalKeyboardKey.keyL)), findsOneWidget); - expect(FocusManager.instance.primaryFocus?.debugLabel, 'HotKeyRecorder.save'); + expect(_saveAction(tester).onPressed, isNotNull); await tester.tap(find.widgetWithText(FilledButton, t.common.save)); + await tester.pump(); - expect(saved, hasLength(1)); - expect(saved.single.key, PhysicalKeyboardKey.keyL); - expect(saved.single.modifiers, isNull); + expect(saved, [null]); + }); + + testWidgets('D-pad can save a cleared shortcut', (tester) async { + final saved = []; + await _pumpRecorder( + tester, + saved: saved, + currentHotKey: const HotKey(key: PhysicalKeyboardKey.keyJ), + ); + + await tester.tap(find.byTooltip(t.hotkeys.clearShortcut)); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); + await _pumpFocusChange(tester); + + expect(FocusManager.instance.primaryFocus?.debugLabel, 'HotKeyRecorder.save'); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pump(); + expect(saved, [null]); + }); + + testWidgets('cancel and Escape discard a local clear', (tester) async { + final saved = []; + var cancelCount = 0; + const original = HotKey(key: PhysicalKeyboardKey.keyJ); + await _pumpRecorder(tester, saved: saved, currentHotKey: original, onCancel: () => cancelCount++); + + await tester.tap(find.byTooltip(t.hotkeys.clearShortcut)); + await tester.pump(); + await tester.tap(find.widgetWithText(TextButton, t.common.cancel)); + expect(saved, isEmpty); + expect(cancelCount, 1); + + await _pumpRecorder(tester, saved: saved, currentHotKey: original, onCancel: () => cancelCount++); + expect(find.text(physicalKeyLabel(PhysicalKeyboardKey.keyJ)), findsOneWidget); + await tester.tap(find.ancestor(of: find.byType(HotKeyRecorder), matching: find.byType(GestureDetector)).first); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pump(); + + expect(saved, isEmpty); + expect(cancelCount, 2); + }); + + testWidgets('save is single-flight and blocks cancellation', (tester) async { + final gate = Completer(); + var saveCount = 0; + var cancelCount = 0; + await _pumpRecorder( + tester, + saved: [], + currentHotKey: const HotKey(key: PhysicalKeyboardKey.keyJ), + onCancel: () => cancelCount++, + onSave: (_) { + saveCount++; + return gate.future; + }, + ); + + await tester.tap(find.widgetWithText(FilledButton, t.common.save)); + await tester.tap(find.widgetWithText(FilledButton, t.common.save), warnIfMissed: false); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pump(); + + expect(saveCount, 1); + expect(cancelCount, 0); + expect(_saveAction(tester).onPressed, isNull); + expect(_cancelAction(tester).onPressed, isNull); + + gate.complete(); + await tester.pump(); + expect(_cancelAction(tester).onPressed, isNotNull); }); testWidgets('modifier-first Control+P completes with the held modifier', (tester) async { - final saved = []; + final saved = []; await _pumpRecorder(tester, saved: saved); await tester.tap(find.byType(HotKeyRecorder)); await tester.pump(); @@ -105,8 +165,8 @@ void main() { await tester.tap(find.widgetWithText(FilledButton, t.common.save)); expect(saved, hasLength(1)); - expect(saved.single.key, PhysicalKeyboardKey.keyP); - expect(saved.single.modifiers, [HotKeyModifier.control]); + expect(saved.single!.key, PhysicalKeyboardKey.keyP); + expect(saved.single!.modifiers, [HotKeyModifier.control]); }); for (final entry in <(String, LogicalKeyboardKey, PhysicalKeyboardKey)>[ @@ -114,7 +174,7 @@ void main() { ('select', LogicalKeyboardKey.select, PhysicalKeyboardKey.select), ]) { testWidgets('${entry.$1} completion does not rearm capture or activate Save on key-up', (tester) async { - final saved = []; + final saved = []; await _pumpRecorder(tester, saved: saved); await tester.tap(find.byType(HotKeyRecorder)); await tester.pump(); @@ -138,21 +198,28 @@ void main() { await tester.tap(find.widgetWithText(FilledButton, t.common.save)); expect(saved, hasLength(1)); - expect(saved.single.key, entry.$3); - expect(saved.single.modifiers, isNull); + expect(saved.single!.key, entry.$3); + expect(saved.single!.modifiers, isNull); }); } } -Future _pumpRecorder(WidgetTester tester, {required List saved, HotKey? currentHotKey}) async { +Future _pumpRecorder( + WidgetTester tester, { + required List saved, + HotKey? currentHotKey, + VoidCallback? onCancel, + FutureOr Function(HotKey?)? onSave, +}) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: HotKeyRecorderWidget( + key: UniqueKey(), actionName: 'Play/Pause', currentHotKey: currentHotKey, - onHotKeyRecorded: saved.add, - onCancel: () {}, + onHotKeyRecorded: onSave ?? saved.add, + onCancel: onCancel ?? () {}, ), ), ), @@ -169,3 +236,6 @@ HotKeyRecorder _recorder(WidgetTester tester) => tester.widget(find.byType(HotKe DialogActionButton _saveAction(WidgetTester tester) => tester.widget(find.widgetWithText(DialogActionButton, t.common.save)); + +DialogActionButton _cancelAction(WidgetTester tester) => + tester.widget(find.widgetWithText(DialogActionButton, t.common.cancel)); diff --git a/test/widgets/hub_section_test.dart b/test/widgets/hub_section_test.dart index a9e9e68e..30769d74 100644 --- a/test/widgets/hub_section_test.dart +++ b/test/widgets/hub_section_test.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:plezy/focus/input_mode_tracker.dart'; +import 'package:plezy/focus/locked_hub_controller.dart'; import 'package:plezy/media/media_backend.dart'; import 'package:plezy/media/media_hub.dart'; import 'package:plezy/media/media_item.dart'; @@ -43,6 +44,7 @@ void main() { _TestApp( child: HubSection( hub: _hubWith(item), + focusMemory: HubFocusMemory(), icon: Symbols.live_tv_rounded, onItemTap: (value) => tappedItem = value, onItemLongPress: (value) => longPressedItem = value, @@ -74,6 +76,7 @@ void main() { child: HubSection( key: hubKey, hub: _hubWith(item), + focusMemory: HubFocusMemory(), icon: Symbols.live_tv_rounded, onItemTap: (value) => tappedItem = value, onItemLongPress: (value) => longPressedItem = value, @@ -109,6 +112,7 @@ void main() { _TestApp( child: HubSection( hub: _hubWith(item), + focusMemory: HubFocusMemory(), icon: Symbols.live_tv_rounded, cardSizing: HubCardSizing.grid, episodePosterModeOverride: EpisodePosterMode.seriesPoster, @@ -128,6 +132,69 @@ void main() { ); expect(outerPadding.padding.resolve(TextDirection.ltr).bottom, 0); }); + + testWidgets('restores within one owner but resets for a fresh owner', (tester) async { + final items = [ + for (var index = 0; index < 3; index++) + testMediaItem(id: 'item_$index', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Item $index'), + ]; + MediaHub hub(String id) => MediaHub(id: id, title: id, type: 'movie', items: items, size: items.length); + + final ownerA = HubFocusMemory(); + final ownerB = HubFocusMemory(); + String? focusedItemId; + + Future mount({ + required HubFocusMemory owner, + required String hubId, + required GlobalKey key, + }) async { + await tester.pumpWidget( + _TestApp( + child: HubSection( + key: key, + hub: hub(hubId), + focusMemory: owner, + icon: Symbols.movie_rounded, + onFocusedItemChanged: (item) => focusedItemId = item.id, + ), + ), + ); + } + + final firstMountKey = GlobalKey(); + await mount(owner: ownerA, hubId: 'detail_episodes', key: firstMountKey); + firstMountKey.currentState!.requestFocusAt(2); + await tester.pumpAndSettle(); + expect(focusedItemId, 'item_2'); + + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pump(); + + final remountKey = GlobalKey(); + await mount(owner: ownerA, hubId: 'detail_episodes', key: remountKey); + remountKey.currentState!.requestFocusFromMemory(); + await tester.pumpAndSettle(); + expect(focusedItemId, 'item_2'); + + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pump(); + + final secondHubKey = GlobalKey(); + await mount(owner: ownerA, hubId: 'detail_extras', key: secondHubKey); + secondHubKey.currentState!.requestFocusFromMemory(); + await tester.pumpAndSettle(); + expect(focusedItemId, 'item_2'); + + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pump(); + + final freshOwnerKey = GlobalKey(); + await mount(owner: ownerB, hubId: 'detail_episodes', key: freshOwnerKey); + freshOwnerKey.currentState!.requestFocusFromMemory(); + await tester.pumpAndSettle(); + expect(focusedItemId, 'item_0'); + }); } MediaHub _hubWith(MediaItem item) { diff --git a/test/widgets/media_card_full_card_test.dart b/test/widgets/media_card_full_card_test.dart index 2208c7ce..a1137f84 100644 --- a/test/widgets/media_card_full_card_test.dart +++ b/test/widgets/media_card_full_card_test.dart @@ -1,8 +1,11 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/focus/focus_glow_overlay.dart'; import 'package:plezy/focus/focus_theme.dart'; import 'package:plezy/focus/input_mode_tracker.dart'; +import 'package:plezy/i18n/strings.g.dart'; import 'package:plezy/media/media_backend.dart'; import 'package:plezy/media/media_kind.dart'; @@ -208,6 +211,151 @@ void main() { expect(find.byType(CompositedTransformFollower), findsNothing); }); + testWidgets('grid and list cards expose card and detail actions without decorative semantics', (tester) async { + final semantics = tester.ensureSemantics(); + final item = testMediaItem( + id: 'semantic_episode', + backend: MediaBackend.plex, + kind: MediaKind.episode, + title: 'Decorative Episode Title', + summary: 'Decorative episode summary', + parentId: 'season_2', + parentIndex: 2, + index: 3, + grandparentId: 'show_1', + grandparentTitle: 'Semantic Series', + ); + + for (final forceGridMode in [true, false]) { + await tester.pumpWidget( + _TestApp( + child: SizedBox( + width: forceGridMode ? 200 : 420, + height: forceGridMode ? 330 : 180, + child: MediaCard(item: item, forceGridMode: forceGridMode, forceListMode: !forceGridMode, isOffline: true), + ), + ), + ); + + final cardData = tester.getSemantics(find.bySemanticsLabel(mediaCardSemanticLabel(item))).getSemanticsData(); + expect(cardData.flagsCollection.isButton, isTrue); + expect(cardData.hasAction(ui.SemanticsAction.tap), isTrue); + + for (final detailLabel in ['Semantic Series', 'S2']) { + final detailData = tester.getSemantics(find.bySemanticsLabel(detailLabel)).getSemanticsData(); + expect(detailData.flagsCollection.isButton, isTrue); + expect(detailData.hasAction(ui.SemanticsAction.tap), isTrue); + expect(detailData.hint, t.mediaMenu.viewDetails); + } + + expect(find.bySemanticsLabel(RegExp('Decorative Episode Title|Decorative episode summary')), findsNothing); + } + + semantics.dispose(); + }); + + testWidgets('movie and season title links remain distinct semantic actions', (tester) async { + final semantics = tester.ensureSemantics(); + final scenarios = [ + ( + item: testMediaItem( + id: 'linked_movie', + kind: MediaKind.movie, + title: 'Linked Movie', + summary: 'Movie decorative summary', + ), + forceGridMode: true, + detailLabel: 'Linked Movie', + decorativeLabel: 'Movie decorative summary', + ), + ( + item: testMediaItem( + id: 'linked_season', + kind: MediaKind.season, + title: 'Season Two', + parentId: 'linked_show', + parentTitle: 'Linked Series', + summary: 'Season decorative summary', + ), + forceGridMode: false, + detailLabel: 'Linked Series', + decorativeLabel: 'Season Two', + ), + ]; + + for (final scenario in scenarios) { + await tester.pumpWidget( + _TestApp( + child: SizedBox( + width: scenario.forceGridMode ? 200 : 420, + height: scenario.forceGridMode ? 330 : 180, + child: MediaCard( + item: scenario.item, + forceGridMode: scenario.forceGridMode, + forceListMode: !scenario.forceGridMode, + isOffline: true, + ), + ), + ), + ); + + expect( + tester + .getSemantics(find.bySemanticsLabel(mediaCardSemanticLabel(scenario.item))) + .getSemanticsData() + .hasAction(ui.SemanticsAction.tap), + isTrue, + ); + final detail = tester.getSemantics(find.bySemanticsLabel(scenario.detailLabel)).getSemanticsData(); + expect(detail.flagsCollection.isButton, isTrue); + expect(detail.hasAction(ui.SemanticsAction.tap), isTrue); + expect(detail.hint, t.mediaMenu.viewDetails); + expect(find.bySemanticsLabel(scenario.decorativeLabel), findsNothing); + } + + semantics.dispose(); + }); + + testWidgets('custom card actions keep detail-link semantics disabled in grid and list modes', (tester) async { + final semantics = tester.ensureSemantics(); + final item = testMediaItem( + id: 'semantic_movie', + backend: MediaBackend.plex, + kind: MediaKind.movie, + title: 'Custom Semantic Movie', + summary: 'Decorative movie summary', + ); + var tapCount = 0; + + for (final forceGridMode in [true, false]) { + await tester.pumpWidget( + _TestApp( + child: SizedBox( + width: forceGridMode ? 200 : 420, + height: forceGridMode ? 330 : 180, + child: MediaCard( + item: item, + forceGridMode: forceGridMode, + forceListMode: !forceGridMode, + isOffline: true, + onTap: () => tapCount++, + ), + ), + ), + ); + + final card = tester.getSemantics(find.bySemanticsLabel(mediaCardSemanticLabel(item))); + expect(card.getSemanticsData().hasAction(ui.SemanticsAction.tap), isTrue); + expect(find.bySemanticsLabel('Custom Semantic Movie'), findsNothing); + expect(find.bySemanticsLabel(RegExp('Decorative movie summary')), findsNothing); + + card.owner!.performAction(card.id, ui.SemanticsAction.tap); + expect(tapCount, forceGridMode ? 1 : 2); + } + + semantics.dispose(); + }); + testWidgets('custom tap owns pointer and programmatic activation', (tester) async { final item = testMediaItem( id: 'custom_tap', diff --git a/test/widgets/optimized_media_image_test.dart b/test/widgets/optimized_media_image_test.dart index 6d9eec25..e0bf2d2f 100644 --- a/test/widgets/optimized_media_image_test.dart +++ b/test/widgets/optimized_media_image_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -89,6 +90,102 @@ void main() { expect(tester.getSize(placeholder), const Size(96, 96)); }); + testWidgets('local resolver rejects stale path completions and disposal completions', (tester) async { + final checks = >{}; + final probes = {}; + var path = '/artwork/a.png'; + late StateSetter rebuild; + Future fileExists(File file) { + probes.update(file.path, (count) => count + 1, ifAbsent: () => 1); + return (checks[file.path] ??= Completer()).future; + } + + Widget resolutionBuilder(BuildContext context, LocalFileResolution resolution, File? file) { + return Text('${resolution.name}:${file?.path ?? path}'); + } + + await tester.pumpWidget( + MaterialApp( + home: StatefulBuilder( + builder: (context, setState) { + rebuild = setState; + return ResolvedLocalFile(path: path, fileExists: fileExists, builder: resolutionBuilder); + }, + ), + ), + ); + expect(find.text('pending:/artwork/a.png'), findsOneWidget); + + rebuild(() => path = '/artwork/b.png'); + await tester.pump(); + rebuild(() {}); + await tester.pump(); + expect(find.text('pending:/artwork/b.png'), findsOneWidget); + expect(probes['/artwork/b.png'], 1); + + checks['/artwork/a.png']!.complete(true); + await tester.pump(); + expect(find.text('pending:/artwork/b.png'), findsOneWidget); + + checks['/artwork/b.png']!.complete(true); + await tester.pump(); + await tester.pump(); + expect(find.text('present:/artwork/b.png'), findsOneWidget); + + rebuild(() => path = '/artwork/c.png'); + await tester.pump(); + await tester.pumpWidget(const SizedBox.shrink()); + checks['/artwork/c.png']!.complete(true); + await tester.pump(); + await tester.pump(); + expect(tester.takeException(), isNull); + }); + + testWidgets('local resolver caches confirmed missing paths only when requested', (tester) async { + final probes = {}; + var path = '/artwork/missing-a.png'; + late StateSetter rebuild; + Future fileExists(File file) { + probes.update(file.path, (count) => count + 1, ifAbsent: () => 1); + return Future.value(false); + } + + await tester.pumpWidget( + MaterialApp( + home: StatefulBuilder( + builder: (context, setState) { + rebuild = setState; + return ResolvedLocalFile( + path: path, + cacheMissing: true, + fileExists: fileExists, + builder: (context, resolution, file) => Text('${resolution.name}:$path'), + ); + }, + ), + ), + ); + await tester.pump(); + expect(find.text('missing:/artwork/missing-a.png'), findsOneWidget); + expect(probes['/artwork/missing-a.png'], 1); + + rebuild(() {}); + await tester.pump(); + rebuild(() {}); + await tester.pump(); + expect(probes['/artwork/missing-a.png'], 1); + + rebuild(() => path = '/artwork/missing-b.png'); + await tester.pump(); + await tester.pump(); + expect(probes['/artwork/missing-b.png'], 1); + + rebuild(() => path = '/artwork/missing-a.png'); + await tester.pump(); + expect(find.text('missing:/artwork/missing-a.png'), findsOneWidget); + expect(probes['/artwork/missing-a.png'], 1); + }); + testWidgets('same local artwork path re-resolves after the file appears', (tester) async { tester.view.devicePixelRatio = 1; tester.view.physicalSize = const Size(1280, 720); diff --git a/test/widgets/pin_entry_dialog_test.dart b/test/widgets/pin_entry_dialog_test.dart index 55945455..c08e8774 100644 --- a/test/widgets/pin_entry_dialog_test.dart +++ b/test/widgets/pin_entry_dialog_test.dart @@ -65,6 +65,34 @@ void main() { expect(find.byType(PinEntryDialog), findsNothing); }); + testWidgets('mobile PIN normalizes oversized input before submitting', (tester) async { + TvDetectionService.debugSetAppleTVOverride(false); + String? result; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(platform: TargetPlatform.iOS), + home: Builder( + builder: (context) => Scaffold( + body: TextButton( + onPressed: () async { + result = await showPinEntryDialog(context, 'Protected Profile'); + }, + child: const Text('Open'), + ), + ), + ), + ), + ); + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + + tester.widget(find.byType(TextField)).onChanged!('12345'); + await tester.pumpAndSettle(); + + expect(result, '1234'); + expect(find.byType(PinEntryDialog), findsNothing); + }); testWidgets('mobile duplicate submit does not pop route below PIN dialog', (tester) async { TvDetectionService.debugSetAppleTVOverride(false); String? pinResult; @@ -304,6 +332,24 @@ void main() { expect(find.byType(PinEntryDialog), findsNothing); }); + testWidgets('unowned keyboard keys remain available to ancestor handlers', (tester) async { + TvDetectionService.debugSetAppleTVOverride(true); + + await _pumpPinDialogLauncher(tester, onResult: (_) {}); + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + + final result = _dispatchKey( + const KeyDownEvent( + physicalKey: PhysicalKeyboardKey.f12, + logicalKey: LogicalKeyboardKey.f12, + timeStamp: Duration.zero, + ), + ); + + expect(result, KeyEventResult.ignored); + expect(find.byType(PinEntryDialog), findsOneWidget); + }); testWidgets('non-mobile PIN entry accepts physical keyboard digits', (tester) async { TvDetectionService.debugSetAppleTVOverride(true); String? result; diff --git a/test/widgets/player_queue_spoilers_test.dart b/test/widgets/player_queue_spoilers_test.dart index fbf3e99e..21a0ac27 100644 --- a/test/widgets/player_queue_spoilers_test.dart +++ b/test/widgets/player_queue_spoilers_test.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/focus/focusable_wrapper.dart'; +import 'package:plezy/focus/input_mode_tracker.dart'; import 'package:plezy/i18n/strings.g.dart'; import 'package:plezy/media/media_backend.dart'; import 'package:plezy/media/media_item.dart'; @@ -105,6 +107,139 @@ void main() { expect(thumbnails.map((thumbnail) => thumbnail.blurThumbnail), [true, false, false]); }); + testWidgets('content strip falls back from chapters to a focusable queue', (tester) async { + final playback = _playbackWithQueue(); + addTearDown(playback.dispose); + final player = _FakePlayer(); + final stripKey = GlobalKey(); + final chapter = MediaChapter(id: 1, startTimeOffset: 10000, title: 'Old Chapter'); + MediaItem? selectedItem; + + await tester.pumpWidget( + _queueHarness( + playback: playback, + child: ContentStrip( + key: stripKey, + player: player, + chapters: [chapter], + chaptersLoaded: true, + canControl: true, + showQueueTab: true, + onQueueItemSelected: (item) => selectedItem = item, + useFocusNavigation: true, + ), + ), + ); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); + await tester.pump(); + + expect(find.text(t.videoControls.chapters), findsOneWidget); + expect(find.text('Old Chapter'), findsOneWidget); + expect(find.text('Spoiler Episode'), findsNothing); + + await tester.pumpWidget( + _queueHarness( + playback: playback, + child: ContentStrip( + key: stripKey, + player: player, + chapters: const [], + chaptersLoaded: true, + canControl: true, + showQueueTab: true, + onQueueItemSelected: (item) => selectedItem = item, + useFocusNavigation: true, + ), + ), + ); + await tester.pump(); + + expect(find.text(t.videoControls.queue), findsOneWidget); + expect(find.text('Spoiler Episode'), findsOneWidget); + expect(find.text('Old Chapter'), findsNothing); + + stripKey.currentState!.requestInitialFocus(); + await tester.pump(); + final firstQueueItem = tester.widget( + find.ancestor(of: find.text('Spoiler Episode'), matching: find.byType(FocusableWrapper)), + ); + expect(firstQueueItem.focusNode!.hasPrimaryFocus, isTrue); + + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pump(); + expect(selectedItem?.id, 'spoiler-episode'); + }); + + testWidgets('content strip preserves queue selection until only chapters remain', (tester) async { + final playback = _playbackWithQueue(); + addTearDown(playback.dispose); + final player = _FakePlayer(); + final stripKey = GlobalKey(); + final initialChapter = MediaChapter(id: 1, startTimeOffset: 10000, title: 'Initial Chapter'); + final replacementChapter = MediaChapter(id: 2, startTimeOffset: 20000, title: 'Replacement Chapter'); + + await tester.pumpWidget( + _queueHarness( + playback: playback, + child: ContentStrip( + key: stripKey, + player: player, + chapters: [initialChapter], + chaptersLoaded: true, + canControl: true, + showQueueTab: true, + onQueueItemSelected: (_) {}, + ), + ), + ); + await tester.pump(); + await tester.tap(find.text(t.videoControls.queue)); + await tester.pump(); + expect(find.text('Spoiler Episode'), findsOneWidget); + expect(find.text('Initial Chapter'), findsNothing); + + await tester.pumpWidget( + _queueHarness( + playback: playback, + child: ContentStrip( + key: stripKey, + player: player, + chapters: [replacementChapter], + chaptersLoaded: true, + canControl: true, + showQueueTab: true, + onQueueItemSelected: (_) {}, + ), + ), + ); + await tester.pump(); + + expect(find.text('Spoiler Episode'), findsOneWidget); + expect(find.text('Replacement Chapter'), findsNothing); + + await tester.pumpWidget( + _queueHarness( + playback: playback, + child: ContentStrip( + key: stripKey, + player: player, + chapters: [replacementChapter], + chaptersLoaded: true, + canControl: true, + showQueueTab: false, + onQueueItemSelected: null, + ), + ), + ); + await tester.pump(); + + expect(find.text('Replacement Chapter'), findsOneWidget); + expect(find.text('Spoiler Episode'), findsNothing); + await tester.tap(find.text('Replacement Chapter')); + await tester.pump(); + expect(player.seeks, [const Duration(seconds: 20)]); + }); + testWidgets('denied chapter remains visible but touch and select do not seek', (tester) async { final playback = PlaybackStateProvider(); addTearDown(playback.dispose); @@ -197,9 +332,11 @@ void main() { Widget _queueHarness({required PlaybackStateProvider playback, required Widget child}) { return ChangeNotifierProvider.value( value: playback, - child: MaterialApp( - theme: ThemeData(extensions: const [_testTokens]), - home: Scaffold(body: SizedBox(width: 600, height: 400, child: child)), + child: InputModeTracker( + child: MaterialApp( + theme: ThemeData(extensions: const [_testTokens]), + home: Scaffold(body: SizedBox(width: 600, height: 400, child: child)), + ), ), ); } diff --git a/test/widgets/server_activities_button_test.dart b/test/widgets/server_activities_button_test.dart index ed799831..dfe410a1 100644 --- a/test/widgets/server_activities_button_test.dart +++ b/test/widgets/server_activities_button_test.dart @@ -1,19 +1,35 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:plezy/database/app_database.dart'; import 'package:plezy/i18n/strings.g.dart'; +import 'package:plezy/media/ids.dart'; import 'package:plezy/providers/multi_server_provider.dart'; import 'package:plezy/services/data_aggregation_service.dart'; import 'package:plezy/services/multi_server_manager.dart'; +import 'package:plezy/services/plex_api_cache.dart'; +import 'package:plezy/theme/mono_theme.dart'; import 'package:plezy/widgets/server_activities_button.dart'; import 'package:provider/provider.dart'; +import '../test_helpers/backend_client_fixtures.dart'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); + late AppDatabase database; setUp(() { LocaleSettings.setLocaleSync(AppLocale.en); + database = AppDatabase.forTesting(NativeDatabase.memory()); + PlexApiCache.initialize(database); }); + tearDown(() => database.close()); + testWidgets('togglePanel opens and closes the server activities overlay', (tester) async { final manager = MultiServerManager(); final multiServerProvider = MultiServerProvider(manager, DataAggregationService(manager)); @@ -47,4 +63,224 @@ void main() { expect(find.text(t.serverTasks.title), findsNothing); }); + + testWidgets('polls three seconds after completion without overlapping activity aggregates', (tester) async { + final transport = _ControlledActivitiesClient(); + final harness = await _pumpActivitiesHarness(tester, transport); + + expect(transport.activityRequests, hasLength(1)); + transport.completeActivities(0, const ['Initial activity']); + await tester.pump(); + await tester.pump(); + expect(find.text('Initial activity'), findsOneWidget); + + await tester.pump(const Duration(seconds: 3)); + expect(transport.activityRequests, hasLength(2)); + expect(transport.activeActivityGets, 1); + + await tester.pump(const Duration(seconds: 4)); + expect(transport.activityRequests, hasLength(2)); + expect(transport.maximumActiveActivityGets, 1); + + transport.completeActivities(1, const ['Completed poll']); + await tester.pump(); + await tester.pump(); + expect(find.text('Completed poll'), findsOneWidget); + + await tester.pump(const Duration(milliseconds: 2999)); + expect(transport.activityRequests, hasLength(2)); + await tester.pump(const Duration(milliseconds: 1)); + expect(transport.activityRequests, hasLength(3)); + expect(transport.maximumActiveActivityGets, 1); + + harness.buttonKey.currentState!.togglePanel(); + await tester.pump(); + }); + + testWidgets('close and reopen rejects a stale completion and preserves the new poll cadence', (tester) async { + final transport = _ControlledActivitiesClient(honorAbort: false); + final harness = await _pumpActivitiesHarness(tester, transport); + final oldRequest = transport.activityRequests.single; + + harness.buttonKey.currentState!.togglePanel(); + await tester.pump(); + await expectLater(oldRequest.abortObserved.future, completes); + + harness.buttonKey.currentState!.togglePanel(); + await tester.pump(); + expect(transport.activityRequests, hasLength(2)); + + transport.completeActivities(1, const ['Fresh activity']); + await tester.pump(); + await tester.pump(); + expect(find.text('Fresh activity'), findsOneWidget); + + transport.completeActivities(0, const ['Old activity']); + await tester.pump(); + await tester.pump(); + expect(find.text('Fresh activity'), findsOneWidget); + expect(find.text('Old activity'), findsNothing); + + await tester.pump(const Duration(milliseconds: 2999)); + expect(transport.activityRequests, hasLength(2)); + await tester.pump(const Duration(milliseconds: 1)); + expect(transport.activityRequests, hasLength(3)); + transport.completeActivities(2, const ['Next activity']); + await tester.pump(); + await tester.pump(); + + harness.buttonKey.currentState!.togglePanel(); + await tester.pump(); + }); + + testWidgets('post-cancel refresh cannot be undone by a pre-cancel poll', (tester) async { + final transport = _ControlledActivitiesClient(honorAbort: false); + final harness = await _pumpActivitiesHarness(tester, transport); + + transport.completeActivities(0, const ['Cancelable activity'], cancellable: true); + await tester.pump(); + await tester.pump(); + expect(find.text('Cancelable activity'), findsOneWidget); + + await tester.pump(const Duration(seconds: 3)); + expect(transport.activityRequests, hasLength(2)); + + await tester.tap(find.byTooltip(t.common.cancel)); + await tester.pump(); + await tester.pump(); + expect(transport.deleteCount, 1); + expect(transport.activityRequests, hasLength(3)); + await expectLater(transport.activityRequests[1].abortObserved.future, completes); + + transport.completeActivities(2, const []); + await tester.pump(); + await tester.pump(); + expect(find.text(t.serverTasks.noTasks), findsOneWidget); + + transport.completeActivities(1, const ['Cancelable activity'], cancellable: true); + await tester.pump(); + await tester.pump(); + expect(find.text(t.serverTasks.noTasks), findsOneWidget); + expect(find.text('Cancelable activity'), findsNothing); + expect(transport.activityRequests, hasLength(3)); + + harness.buttonKey.currentState!.togglePanel(); + await tester.pump(); + }); +} + +class _ActivitiesHarness { + const _ActivitiesHarness(this.buttonKey); + + final GlobalKey buttonKey; +} + +Future<_ActivitiesHarness> _pumpActivitiesHarness(WidgetTester tester, _ControlledActivitiesClient transport) async { + final serverId = ServerId('plex-server'); + final client = testPlexClient(serverId: serverId, serverName: 'Test server', httpClient: transport); + final manager = MultiServerManager()..debugRegisterClientForTesting(client); + final multiServerProvider = MultiServerProvider(manager, DataAggregationService(manager)); + final buttonKey = GlobalKey(); + + addTearDown(() { + multiServerProvider.dispose(); + manager.dispose(); + }); + + await tester.pumpWidget( + TranslationProvider( + child: ChangeNotifierProvider.value( + value: multiServerProvider, + child: MaterialApp( + theme: monoTheme(dark: true), + home: Scaffold(body: ServerActivitiesButton(key: buttonKey)), + ), + ), + ), + ); + buttonKey.currentState!.togglePanel(); + await tester.pump(); + return _ActivitiesHarness(buttonKey); +} + +class _ControlledActivityRequest { + _ControlledActivityRequest(this.request); + + final http.BaseRequest request; + final response = Completer(); + final abortObserved = Completer(); +} + +class _ControlledActivitiesClient extends http.BaseClient { + _ControlledActivitiesClient({this.honorAbort = true}); + + final bool honorAbort; + final activityRequests = <_ControlledActivityRequest>[]; + var activeActivityGets = 0; + var maximumActiveActivityGets = 0; + var deleteCount = 0; + + @override + Future send(http.BaseRequest request) { + if (request.method == 'DELETE' && request.url.path.startsWith('/activities/')) { + deleteCount++; + return Future.value(_response(request, const {})); + } + if (request.method != 'GET' || request.url.path != '/activities') { + return Future.value(_response(request, const {})); + } + + final pending = _ControlledActivityRequest(request); + activityRequests.add(pending); + activeActivityGets++; + if (activeActivityGets > maximumActiveActivityGets) { + maximumActiveActivityGets = activeActivityGets; + } + + final abortTrigger = (request as http.Abortable).abortTrigger!; + unawaited( + abortTrigger.then((_) { + if (!pending.abortObserved.isCompleted) { + pending.abortObserved.complete(); + } + if (honorAbort && !pending.response.isCompleted) { + pending.response.completeError(http.RequestAbortedException(request.url)); + } + }), + ); + + return pending.response.future.whenComplete(() { + activeActivityGets--; + }); + } + + void completeActivities(int requestIndex, List titles, {bool cancellable = false}) { + final pending = activityRequests[requestIndex]; + if (pending.response.isCompleted) return; + pending.response.complete( + _response(pending.request, { + 'MediaContainer': { + 'Activity': [ + for (var index = 0; index < titles.length; index++) + { + 'uuid': 'activity-$requestIndex-$index', + 'type': 'library.update', + 'title': titles[index], + 'progress': 50, + 'cancellable': cancellable, + }, + ], + }, + }), + ); + } + + http.StreamedResponse _response(http.BaseRequest request, Map body) { + return http.StreamedResponse( + Stream.value(utf8.encode(jsonEncode(body))), + 200, + headers: const {'content-type': 'application/json'}, + request: request, + ); + } } diff --git a/test/widgets/track_sheet_test.dart b/test/widgets/track_sheet_test.dart index da8f6c67..44a2b0a1 100644 --- a/test/widgets/track_sheet_test.dart +++ b/test/widgets/track_sheet_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'dart:ui' show Tristate; import 'package:flutter/material.dart'; @@ -106,7 +108,9 @@ void main() { isTranscoding: true, sourceSubtitleTracks: [MediaSubtitleTrack(id: 0, title: 'Stream zero', selected: false, forced: false)], selectedSubtitleChoice: const PlaybackSourceSubtitleChoice.off(), - onSwitchSubtitle: (choice) => switchedChoice = choice, + onSwitchSubtitle: (choice) async { + switchedChoice = choice; + }, subtitleSearchSupported: false, ), ); @@ -143,7 +147,9 @@ void main() { ], selectedSubtitleChoice: const PlaybackSourceSubtitleChoice.source(1), sourceSubtitleSidecarIds: const {2}, - onSwitchSubtitle: (choice) => switchedSourceChoice = choice, + onSwitchSubtitle: (choice) async { + switchedSourceChoice = choice; + }, subtitleSearchSupported: false, ), ); @@ -155,6 +161,71 @@ void main() { expect(switchedSourceChoice, const PlaybackSourceSubtitleChoice.source(2)); }); + testWidgets('keeps a source sheet open until the async selection commits', (tester) async { + final player = _FakeTrackSheetPlayer( + tracks: const Tracks( + subtitle: [SubtitleTrack(id: 's1', language: 'eng', codec: 'srt')], + ), + track: const TrackSelection( + subtitle: SubtitleTrack(id: 's1', language: 'eng', codec: 'srt'), + ), + ); + final selectionGate = Completer(); + late BuildContext hostContext; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(extensions: const [_testTokens]), + home: OverlaySheetHost( + child: Builder( + builder: (context) { + hostContext = context; + return const Scaffold(body: SizedBox.expand()); + }, + ), + ), + ), + ); + final sheetResult = OverlaySheetController.of(hostContext).show( + builder: (_) => SizedBox( + height: 400, + child: TrackSheet( + player: player, + trackControlsState: TrackControlsState( + sourceSubtitleTracks: [ + MediaSubtitleTrack(id: 1, languageCode: 'eng', codec: 'srt', selected: true, forced: false), + MediaSubtitleTrack( + id: 2, + title: 'Remote sidecar', + codec: 'ass', + external: true, + selected: false, + forced: false, + ), + ], + selectedSubtitleChoice: const PlaybackSourceSubtitleChoice.source(1), + sourceSubtitleSidecarIds: const {2}, + onSwitchSubtitle: (_) => selectionGate.future, + subtitleSearchSupported: false, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Remote sidecar')); + await tester.pump(); + + expect(find.byType(LinearProgressIndicator), findsOneWidget); + expect(find.text('Remote sidecar'), findsOneWidget); + + selectionGate.complete(); + await tester.pumpAndSettle(); + await sheetResult; + + expect(find.text('Remote sidecar'), findsNothing); + }); + testWidgets('direct-play embedded selection stays on the native player path', (tester) async { final player = _FakeTrackSheetPlayer( tracks: const Tracks( @@ -178,7 +249,9 @@ void main() { MediaSubtitleTrack(id: 2, languageCode: 'swe', selected: false, forced: false), ], selectedSubtitleChoice: const PlaybackSourceSubtitleChoice.source(1), - onSwitchSubtitle: (choice) => switchedSourceChoice = choice, + onSwitchSubtitle: (choice) async { + switchedSourceChoice = choice; + }, subtitleSearchSupported: false, ), ); @@ -221,7 +294,7 @@ void main() { selectedSubtitleChoice: const PlaybackSourceSubtitleChoice.source(1), selectedSecondarySubtitleStreamId: 2, sourceSubtitleSidecarIds: const {2}, - onSwitchSubtitle: (_) {}, + onSwitchSubtitle: (_) async {}, subtitleSearchSupported: false, ), ); @@ -284,7 +357,7 @@ void main() { TrackControlsState( isTranscoding: true, sourceSubtitleTracks: [sourceSubtitle], - onSwitchSubtitle: (_) {}, + onSwitchSubtitle: (_) async {}, ).hasSubtitleControls(const Tracks()), isTrue, ); @@ -295,7 +368,7 @@ void main() { final state = TrackControlsState( sourceSubtitleTracks: [sourceSidecar], sourceSubtitleSidecarIds: {sourceSidecar.id}, - onSwitchSubtitle: (_) {}, + onSwitchSubtitle: (_) async {}, ); expect(state.canUseSourceSubtitles, isFalse); @@ -305,7 +378,7 @@ void main() { test('does not misclassify a Jellyfin external-delivery embedded row as a sidecar', () { final sourceTrack = MediaSubtitleTrack(id: 1, usesExternalDelivery: true, selected: false, forced: false); - final state = TrackControlsState(sourceSubtitleTracks: [sourceTrack], onSwitchSubtitle: (_) {}); + final state = TrackControlsState(sourceSubtitleTracks: [sourceTrack], onSwitchSubtitle: (_) async {}); expect(state.directPlaySourceSidecars, isEmpty); }); diff --git a/test/widgets/tv_browse_rail_test.dart b/test/widgets/tv_browse_rail_test.dart index d52009dc..3a2702be 100644 --- a/test/widgets/tv_browse_rail_test.dart +++ b/test/widgets/tv_browse_rail_test.dart @@ -319,10 +319,12 @@ void main() { }); }); + late HubFocusMemory focusMemory; + setUp(() async { resetSharedPreferencesForTest(); SettingsService.resetForTesting(); - HubFocusMemory.clear(); + focusMemory = HubFocusMemory(); await SettingsService.getInstance(); }); @@ -352,6 +354,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], autofocus: true, iconForHub: (_, _) => Icons.movie_rounded, @@ -413,7 +416,7 @@ void main() { body: SizedBox( width: 1280, height: 720, - child: TvBrowseRail(hubs: [hub], iconForHub: (_, _) => Icons.tv_rounded), + child: TvBrowseRail(focusMemory: focusMemory, hubs: [hub], iconForHub: (_, _) => Icons.tv_rounded), ), ), ), @@ -454,6 +457,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hubFor('serverA'), hubFor('serverB')], iconForHub: (_, _) => Icons.movie_rounded, ), @@ -496,7 +500,7 @@ void main() { body: SizedBox( width: 1280, height: 720, - child: TvBrowseRail(hubs: [hub], iconForHub: (_, _) => Icons.movie_rounded), + child: TvBrowseRail(focusMemory: focusMemory, hubs: [hub], iconForHub: (_, _) => Icons.movie_rounded), ), ), ), @@ -539,7 +543,12 @@ void main() { body: SizedBox( width: 1280, height: 720, - child: TvBrowseRail(hubs: [hub], autofocus: true, iconForHub: (_, _) => Icons.movie_rounded), + child: TvBrowseRail( + focusMemory: focusMemory, + hubs: [hub], + autofocus: true, + iconForHub: (_, _) => Icons.movie_rounded, + ), ), ), ), @@ -625,6 +634,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [firstHub, secondHub], autofocus: true, iconForHub: (_, _) => Icons.movie_rounded, @@ -696,7 +706,12 @@ void main() { body: SizedBox( width: 1280, height: 720, - child: TvBrowseRail(hubs: [hub], autofocus: true, iconForHub: (_, _) => Icons.movie_rounded), + child: TvBrowseRail( + focusMemory: focusMemory, + hubs: [hub], + autofocus: true, + iconForHub: (_, _) => Icons.movie_rounded, + ), ), ), ), @@ -751,7 +766,12 @@ void main() { body: SizedBox( width: 1280, height: 720, - child: TvBrowseRail(hubs: [hub], autofocus: true, iconForHub: (_, _) => Icons.movie_rounded), + child: TvBrowseRail( + focusMemory: focusMemory, + hubs: [hub], + autofocus: true, + iconForHub: (_, _) => Icons.movie_rounded, + ), ), ), ), @@ -808,6 +828,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], autofocus: true, iconForHub: (_, _) => Icons.movie_rounded, @@ -865,6 +886,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], autofocus: true, iconForHub: (_, _) => Icons.movie_rounded, @@ -922,7 +944,11 @@ void main() { body: SizedBox( width: 1280, height: 720, - child: TvBrowseRail(hubs: [firstHub, secondHub], iconForHub: (_, _) => Icons.movie_rounded), + child: TvBrowseRail( + focusMemory: focusMemory, + hubs: [firstHub, secondHub], + iconForHub: (_, _) => Icons.movie_rounded, + ), ), ), ), @@ -957,6 +983,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, key: const ValueKey('rail'), hubs: hubs, initialHubId: initialHubId, @@ -998,6 +1025,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, key: const ValueKey('rail'), hubs: hubs, initialHubId: initialHubId, @@ -1040,6 +1068,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, key: const ValueKey('rail'), hubs: hubs, initialItemId: initialItemId, @@ -1107,7 +1136,7 @@ void main() { // Seed remembered focus under the rail's server-qualified hub key (mirrors // _TvBrowseRailState._hubKey: ':'), so the multi-server keying // resolves it the same way the rail does. - HubFocusMemory.setForHub('${episodeHub.serverId ?? ''}:${episodeHub.id}', 5); + focusMemory.setForHub('${episodeHub.serverId ?? ''}:${episodeHub.id}', 5); await tester.pumpWidget( ChangeNotifierProvider( @@ -1121,6 +1150,7 @@ void main() { width: 700, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [movieHub, episodeHub], autofocus: true, iconForHub: (_, _) => Icons.tv_rounded, @@ -1232,6 +1262,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, key: const ValueKey('rail'), hubs: hubs, autofocus: true, @@ -1314,6 +1345,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, key: const ValueKey('rail'), hubs: [firstHub, activeHub, backgroundLoaded ? backgroundUpdatedHub : backgroundInitialHub], autofocus: true, @@ -1391,6 +1423,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [firstHub, middleLoaded ? middleUpdatedHub : middleInitialHub, lastHub], autofocus: true, iconForHub: (_, _) => Icons.tv_rounded, @@ -1452,7 +1485,7 @@ void main() { expect(_verticalRailPosition(tester).pixels, closeTo(middleTargetOffset, 0.1)); }); - testWidgets('uses per-hub item focus instead of global column hint', (tester) async { + testWidgets('uses per-hub item focus instead of the owner last-column hint', (tester) async { List movieItems() => List.generate( 8, (index) => @@ -1492,6 +1525,7 @@ void main() { width: 700, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [movieHub, episodeHub], autofocus: true, iconForHub: (_, _) => Icons.tv_rounded, @@ -1522,6 +1556,97 @@ void main() { expect(focused.last, 'movies:movie_5'); }); + testWidgets('repeated detail hub ids restore only within their browse owner', (tester) async { + final episodeItems = [ + for (var index = 0; index < 12; index++) + testMediaItem( + id: 'episode_$index', + backend: MediaBackend.plex, + kind: MediaKind.episode, + title: 'Episode $index', + thumbPath: '/episode_$index', + ), + ]; + final extraItems = [ + for (var index = 0; index < 3; index++) + testMediaItem(id: 'extra_$index', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Extra $index'), + ]; + final episodeHub = MediaHub( + id: 'detail_episodes', + title: 'Episodes', + type: 'episode', + items: episodeItems, + size: episodeItems.length, + ); + final extrasHub = MediaHub( + id: 'detail_extras', + title: 'Extras', + type: 'movie', + items: extraItems, + size: extraItems.length, + ); + final focused = []; + + Future mount(HubFocusMemory owner, List hubs) async { + final serverManager = MultiServerManager(); + await tester.pumpWidget( + ChangeNotifierProvider( + create: (_) => MultiServerProvider(serverManager, DataAggregationService(serverManager)), + child: MaterialApp( + theme: monoTheme(dark: true), + home: Scaffold( + body: SizedBox( + width: 700, + height: 720, + child: TvBrowseRail( + focusMemory: owner, + hubs: hubs, + autofocus: true, + iconForHub: (_, _) => Icons.tv_rounded, + onFocusedHubItemChanged: (hub, item) => focused.add('${hub.id}:${item.id}'), + episodePosterModeForHub: (_) => EpisodePosterMode.episodeThumbnail, + ), + ), + ), + ), + ), + ); + await tester.pump(); + tester.state(find.byType(TvBrowseRail)).requestFocus(); + await tester.pump(); + } + + Future press(LogicalKeyboardKey key) async { + await tester.sendKeyDownEvent(key); + await tester.pump(); + await tester.sendKeyUpEvent(key); + await tester.pumpAndSettle(); + } + + await mount(focusMemory, [episodeHub, extrasHub]); + for (var index = 0; index < 5; index++) { + await press(LogicalKeyboardKey.arrowRight); + } + expect(focused.last, 'detail_episodes:episode_5'); + expect(_activeRailPosition(tester).pixels, greaterThan(0)); + + await press(LogicalKeyboardKey.arrowDown); + expect(focused.last, 'detail_extras:extra_0'); + await press(LogicalKeyboardKey.arrowUp); + expect(focused.last, 'detail_episodes:episode_5'); + expect(_activeRailPosition(tester).pixels, greaterThan(0)); + + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pump(); + + final freshOwner = HubFocusMemory(); + await mount(freshOwner, [extrasHub, episodeHub]); + await press(LogicalKeyboardKey.arrowDown); + + expect(focused.last, 'detail_episodes:episode_0'); + expect(_activeRailPosition(tester).pixels, 0); + }); + testWidgets('keeps late episode thumbnails visible in long TV rows', (tester) async { await SettingsService.instanceOrNull!.write(SettingsService.tvFullCardLayout, false); tester.view.devicePixelRatio = 1.0; @@ -1563,6 +1688,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], autofocus: true, iconForHub: (_, _) => Icons.tv_rounded, @@ -1653,6 +1779,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], autofocus: true, iconForHub: (_, _) => Icons.tv_rounded, @@ -1726,6 +1853,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], iconForHub: (_, _) => Icons.person_rounded, onActivateItem: (_, _) { @@ -1788,6 +1916,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], iconForHub: (_, _) => Icons.person_rounded, onActivateItem: (_, _) { @@ -1838,6 +1967,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], iconForHub: (_, _) => Icons.person_rounded, onActivateItem: (_, _) { @@ -1885,6 +2015,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], iconForHub: (_, _) => Icons.person_rounded, selectSuppressionGestureSignal: gesture, @@ -1940,6 +2071,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], iconForHub: (_, _) => Icons.person_rounded, selectSuppressionGestureSignal: gesture, @@ -1989,7 +2121,12 @@ void main() { body: SizedBox( width: 1280, height: 720, - child: TvBrowseRail(hubs: [hub], autofocus: autofocus, iconForHub: (_, _) => Icons.tv_rounded), + child: TvBrowseRail( + focusMemory: focusMemory, + hubs: [hub], + autofocus: autofocus, + iconForHub: (_, _) => Icons.tv_rounded, + ), ), ), ), @@ -2025,7 +2162,11 @@ void main() { left: 0, right: 0, bottom: 0, - child: TvBrowseRail(hubs: [hub], iconForHub: (_, _) => Icons.movie_rounded), + child: TvBrowseRail( + focusMemory: focusMemory, + hubs: [hub], + iconForHub: (_, _) => Icons.movie_rounded, + ), ), ], ), @@ -2061,6 +2202,7 @@ void main() { width: 1060, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, hubs: [hub], iconForHub: (_, _) => Icons.movie_rounded, backgroundBleedLeft: SideNavigationRailState.expandedWidth, @@ -2101,6 +2243,7 @@ void main() { width: 1280, height: 720, child: TvBrowseRail( + focusMemory: focusMemory, key: const ValueKey('rail'), hubs: [hub], iconForHub: (_, _) => Icons.movie_rounded, diff --git a/test/widgets/tv_virtual_keyboard_test.dart b/test/widgets/tv_virtual_keyboard_test.dart index 51807bff..aa897023 100644 --- a/test/widgets/tv_virtual_keyboard_test.dart +++ b/test/widgets/tv_virtual_keyboard_test.dart @@ -58,6 +58,24 @@ void main() { expect(find.byType(Dialog), findsOneWidget); }); + testWidgets('unowned keyboard keys remain available to ancestor handlers', (tester) async { + final controller = TextEditingController(); + addTearDown(controller.dispose); + + await _pumpKeyboard(tester, controller: controller); + + final result = _dispatchKey( + const KeyDownEvent( + physicalKey: PhysicalKeyboardKey.f12, + logicalKey: LogicalKeyboardKey.f12, + timeStamp: Duration.zero, + ), + ); + + expect(result, KeyEventResult.ignored); + expect(controller.text, isEmpty); + expect(find.byType(Dialog), findsOneWidget); + }); testWidgets('directional pad enter activates highlighted key', (tester) async { final controller = TextEditingController(); addTearDown(controller.dispose); @@ -230,6 +248,79 @@ void main() { expect(find.byType(Dialog), findsNothing); expect(underlyingBackEvents, 0); }); + + testWidgets('rendered backspace deletes whole graphemes and keeps the keyboard open', (tester) async { + final controller = TextEditingController(); + addTearDown(controller.dispose); + + for (final grapheme in ['๐Ÿ˜€', 'e\u0301', '๐Ÿ‡ฏ๐Ÿ‡ต', '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ']) { + controller.value = TextEditingValue( + text: 'A${grapheme}B', + selection: TextSelection.collapsed(offset: 1 + grapheme.length), + ); + await _pumpKeyboard(tester, controller: controller); + + await tester.tap(find.byIcon(Symbols.backspace_rounded)); + await tester.pump(); + + expect(controller.text, 'AB'); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + expect(find.byType(Dialog), findsOneWidget); + + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pumpAndSettle(); + } + }); + + testWidgets('physical delete removes a whole grapheme and dismisses the keyboard', (tester) async { + final controller = TextEditingController(); + addTearDown(controller.dispose); + + for (final grapheme in ['๐Ÿ˜€', 'e\u0301', '๐Ÿ‡ฏ๐Ÿ‡ต', '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ']) { + controller.value = TextEditingValue(text: 'A${grapheme}B', selection: const TextSelection.collapsed(offset: 1)); + await _pumpKeyboard(tester, controller: controller); + + await tester.sendKeyEvent(LogicalKeyboardKey.delete); + await tester.pumpAndSettle(); + + expect(controller.text, 'AB'); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + expect(find.byType(Dialog), findsNothing); + } + }); + + testWidgets('grapheme selection deletion preserves formatter and callback ordering', (tester) async { + const grapheme = '๐Ÿ‡ฏ๐Ÿ‡ต'; + final controller = TextEditingController() + ..value = TextEditingValue( + text: 'A${grapheme}B', + selection: TextSelection(baseOffset: grapheme.length, extentOffset: 1), + ); + final formatterCandidates = []; + final changes = []; + addTearDown(controller.dispose); + + await _pumpKeyboard( + tester, + controller: controller, + maxLength: 8, + inputFormatters: [ + TextInputFormatter.withFunction((_, nextValue) { + formatterCandidates.add(nextValue); + return nextValue; + }), + ], + onChanged: changes.add, + ); + + await tester.tap(find.byIcon(Symbols.backspace_rounded)); + await tester.pump(); + + expect(formatterCandidates.single.text, 'AB'); + expect(controller.text, 'AB'); + expect(controller.selection, const TextSelection.collapsed(offset: 1)); + expect(changes, ['AB']); + }); } Future _pumpKeyboard( @@ -237,6 +328,9 @@ Future _pumpKeyboard( required TextEditingController controller, TextInputType? keyboardType, int? maxLines, + List? inputFormatters, + int? maxLength, + ValueChanged? onChanged, ValueChanged? onSubmitted, }) async { TvDetectionService.debugSetAppleTVOverride(true); @@ -260,6 +354,9 @@ Future _pumpKeyboard( controller: controller, keyboardType: keyboardType, maxLines: maxLines, + inputFormatters: inputFormatters, + maxLength: maxLength, + onChanged: onChanged, onSubmitted: onSubmitted, ); await tester.pumpAndSettle(); diff --git a/test/widgets/video_controls_test.dart b/test/widgets/video_controls_test.dart index eab0d32e..2c428548 100644 --- a/test/widgets/video_controls_test.dart +++ b/test/widgets/video_controls_test.dart @@ -4,6 +4,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/date_symbol_data_local.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plezy/focus/key_event_utils.dart'; @@ -13,9 +14,11 @@ import 'package:plezy/media/media_version.dart'; import 'package:plezy/models/shader_preset.dart'; import 'package:plezy/mpv/mpv.dart'; import 'package:plezy/services/playback_subtitle_resolver.dart'; +import 'package:plezy/providers/playback_state_provider.dart'; import 'package:plezy/services/settings_service.dart'; import 'package:plezy/services/video_volume_controller.dart'; import 'package:plezy/theme/mono_tokens.dart'; +import 'package:plezy/widgets/video_controls/widgets/player_toast_indicator.dart'; import 'package:plezy/widgets/video_controls/desktop_video_controls.dart'; import 'package:plezy/widgets/video_controls/mobile_video_controls.dart'; import 'package:plezy/watch_together/providers/watch_together_provider.dart'; @@ -27,6 +30,7 @@ import 'package:plezy/widgets/video_controls/widgets/mobile_skip_zones.dart'; import 'package:plezy/widgets/video_controls/widgets/skip_marker_button.dart'; import 'package:plezy/widgets/video_controls/widgets/sync_offset_control.dart'; import 'package:plezy/widgets/video_controls/widgets/timeline_slider.dart'; +import 'package:plezy/widgets/video_controls/video_control_button.dart'; import 'package:plezy/widgets/video_controls/widgets/video_timeline_bar.dart'; import '../test_helpers/watch_together_fakes.dart'; @@ -1479,6 +1483,85 @@ void main() { }); }); + group('subtitle visibility', () { + testWidgets('rolls back a failed latest toggle to the preceding successful mutation', (tester) async { + LocaleSettings.setLocaleSync(AppLocale.en); + await initializeDateFormatting('en'); + tester.view.physicalSize = const Size(1200, 800); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + resetSharedPreferencesForTest(); + SettingsService.resetForTesting(); + final settings = await SettingsService.getInstance(); + final firstWrite = Completer(); + final secondWrite = Completer(); + final player = _FakeSubtitleVisibilityPlayer(writes: [firstWrite, secondWrite]); + final volume = VideoVolumeController(player: player, settings: settings, initialVolume: 100); + final playbackState = PlaybackStateProvider(); + final watchTogether = WatchTogetherProvider(); + final chrome = PlayerChromeController(); + final toast = PlayerToastController(); + addTearDown(volume.dispose); + addTearDown(playbackState.dispose); + addTearDown(watchTogether.dispose); + addTearDown(chrome.dispose); + addTearDown(toast.dispose); + + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: playbackState), + ChangeNotifierProvider.value(value: watchTogether), + ], + child: MaterialApp( + theme: ThemeData(platform: TargetPlatform.macOS, extensions: const [_testTokens]), + home: Scaffold( + body: SizedBox( + width: 1200, + height: 800, + child: PlexVideoControls( + player: player, + volumeController: volume, + metadata: testMediaItem(id: 'subtitle-visibility'), + toastController: toast, + canNavigateMediaItems: false, + chromeController: chrome, + isLive: true, + ), + ), + ), + ), + ), + ); + await tester.pump(); + expect(find.byIcon(Symbols.subtitles_rounded), findsOneWidget); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyS); + await tester.pump(); + expect(player.propertyValues, ['no']); + expect(find.byIcon(Symbols.subtitles_off_rounded), findsOneWidget); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyS); + await tester.pump(); + expect(player.propertyValues, ['no'], reason: 'the latest toggle must wait for the in-flight native write'); + expect(find.byIcon(Symbols.subtitles_rounded), findsOneWidget); + + firstWrite.complete(); + await tester.pump(); + await tester.pump(); + expect(player.propertyValues, ['no', 'yes']); + + secondWrite.completeError(PlatformException(code: 'SET_PROPERTY_FAILED')); + await tester.pump(); + await tester.pump(); + + expect(tester.takeException(), isNull); + expect(find.byIcon(Symbols.subtitles_off_rounded), findsOneWidget); + chrome.cancelAutoHide(); + await tester.pumpWidget(const SizedBox.shrink()); + }); + }); + group('SyncOffsetControl', () { testWidgets('uses 100ms slider steps without rendering tick marks', (tester) async { LocaleSettings.setLocaleSync(AppLocale.en); @@ -1594,16 +1677,165 @@ void main() { tester.widget(find.byType(Slider)).onChangeEnd!(200); await tester.pump(); await tester.pump(); - expect(persistedOffsets, [200]); + expect(persistedOffsets, isEmpty); staleWrite.completeError(PlatformException(code: 'SET_PROPERTY_FAILED')); await tester.pump(); await tester.pump(); + await tester.pump(); expect(tester.takeException(), isNull); expect(tester.widget(find.byType(Slider)).value, 200); expect(persistedOffsets, [200]); }); + + testWidgets('rolls back a failed latest write to the preceding successful offset', (tester) async { + final firstWrite = Completer(); + final secondWrite = Completer(); + final propertyValues = []; + final persistedOffsets = []; + final player = _FakeSyncPlayer( + onSetProperty: (_, value) { + propertyValues.add(value); + return propertyValues.length == 1 ? firstWrite.future : secondWrite.future; + }, + ); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(extensions: const [_testTokens]), + home: Scaffold( + body: SizedBox( + width: 700, + child: SyncOffsetControl( + player: player, + propertyName: 'sub-delay', + initialOffset: 0, + labelText: 'Subtitles', + onOffsetChanged: (offset) async { + persistedOffsets.add(offset); + }, + compact: true, + ), + ), + ), + ), + ); + + tester.widget(find.byType(Slider)).onChanged!(100); + tester.widget(find.byType(Slider)).onChangeEnd!(100); + await tester.pump(); + expect(propertyValues, ['0.1']); + + tester.widget(find.byType(Slider)).onChanged!(200); + tester.widget(find.byType(Slider)).onChangeEnd!(200); + await tester.pump(); + expect(propertyValues, ['0.1']); + expect(tester.widget(find.byType(Slider)).value, 200); + + firstWrite.complete(); + await tester.pump(); + await tester.pump(); + expect(propertyValues, ['0.1', '0.2']); + expect(persistedOffsets, [100]); + + secondWrite.completeError(PlatformException(code: 'SET_PROPERTY_FAILED')); + await tester.pump(); + await tester.pump(); + + expect(tester.takeException(), isNull); + expect(tester.widget(find.byType(Slider)).value, 100); + expect(persistedOffsets, [100]); + }); + + testWidgets('persists an accepted offset after the control is disposed', (tester) async { + final propertyWrite = Completer(); + final persistedOffsets = []; + final player = _FakeSyncPlayer(onSetProperty: (_, _) => propertyWrite.future); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(extensions: const [_testTokens]), + home: Scaffold( + body: SizedBox( + width: 700, + child: SyncOffsetControl( + player: player, + propertyName: 'sub-delay', + initialOffset: 0, + labelText: 'Subtitles', + onOffsetChanged: (offset) async => persistedOffsets.add(offset), + compact: true, + ), + ), + ), + ), + ); + + tester.widget(find.byType(Slider)).onChanged!(100); + tester.widget(find.byType(Slider)).onChangeEnd!(100); + await tester.pumpWidget(const SizedBox.shrink()); + propertyWrite.complete(); + await tester.pump(); + + expect(persistedOffsets, [100]); + }); + }); + + group('VideoControlButton semantics', () { + testWidgets('exposes one operable node with value and checked state', (tester) async { + final semantics = tester.ensureSemantics(); + final focusNode = FocusNode(debugLabel: 'semantic_video_control'); + addTearDown(focusNode.dispose); + var activations = 0; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: VideoControlButton( + icon: Icons.settings, + tooltip: 'Player settings', + semanticValue: '720p', + checked: true, + focusNode: focusNode, + onPressed: () => activations++, + ), + ), + ), + ); + + final finder = find.bySemanticsLabel('Player settings'); + expect(finder, findsOneWidget); + final data = tester.getSemantics(finder).getSemanticsData(); + expect(data.value, '720p'); + expect(data.flagsCollection.isButton, isTrue); + expect(data.flagsCollection.isChecked, ui.CheckedState.isTrue); + expect(data.hasAction(ui.SemanticsAction.tap), isTrue); + + final node = tester.getSemantics(finder); + node.owner!.performAction(node.id, ui.SemanticsAction.tap); + await tester.pump(); + expect(activations, 1); + semantics.dispose(); + }); + + testWidgets('keeps disabled controls discoverable without a tap action', (tester) async { + final semantics = tester.ensureSemantics(); + + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: VideoControlButton(icon: Icons.skip_next, semanticLabel: 'Next item', onPressed: null), + ), + ), + ); + + final data = tester.getSemantics(find.bySemanticsLabel('Next item')).getSemanticsData(); + expect(data.flagsCollection.isButton, isTrue); + expect(data.flagsCollection.isEnabled, ui.Tristate.isFalse); + expect(data.hasAction(ui.SemanticsAction.tap), isFalse); + semantics.dispose(); + }); }); } @@ -1670,3 +1902,57 @@ class _FakeSyncPlayer implements Player { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } + +class _FakeSubtitleVisibilityPlayer implements Player { + _FakeSubtitleVisibilityPlayer({required this.writes}); + + final List> writes; + final List propertyValues = []; + + @override + String get playerType => 'mpv'; + + @override + PlayerState get state => PlayerState( + duration: const Duration(minutes: 45), + seekable: true, + tracks: const Tracks( + subtitle: [SubtitleTrack(id: 'subtitle-1', language: 'eng')], + ), + track: const TrackSelection( + subtitle: SubtitleTrack(id: 'subtitle-1', language: 'eng'), + ), + ); + + @override + PlayerStreams get streams => PlayerStreams( + playing: const Stream.empty(), + completed: const Stream.empty(), + buffering: const Stream.empty(), + position: const Stream.empty(), + duration: const Stream.empty(), + seekable: const Stream.empty(), + buffer: const Stream.empty(), + volume: const Stream.empty(), + rate: const Stream.empty(), + tracks: const Stream.empty(), + track: const Stream.empty(), + log: const Stream.empty(), + error: const Stream.empty(), + audioDevice: const Stream.empty(), + audioDevices: const Stream>.empty(), + bufferRanges: const Stream>.empty(), + playbackRestart: const Stream.empty(), + backendSwitched: const Stream.empty(), + ); + + @override + Future setProperty(String name, String value) { + expect(name, 'sub-visibility'); + propertyValues.add(value); + return writes[propertyValues.length - 1].future; + } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +}