├── .github
└── workflows
│ ├── audit.yml
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── examples
└── get_locale.rs
├── gen-windows-bindings
├── Cargo.toml
├── bindings.config
└── src
│ └── main.rs
├── src
├── android.rs
├── apple.rs
├── lib.rs
├── unix.rs
├── wasm.rs
├── windows.rs
└── windows_sys.rs
└── tests
└── wasm_worker.rs
/.github/workflows/audit.yml:
--------------------------------------------------------------------------------
1 | name: Security audit
2 | permissions:
3 | contents: read
4 | on:
5 | schedule:
6 | - cron: '0 0 * * 0'
7 | push:
8 | paths:
9 | - '**/Cargo.toml'
10 | - '**/Cargo.lock'
11 | pull_request:
12 |
13 | jobs:
14 | audit:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v2
18 | with:
19 | persist-credentials: false
20 | - uses: actions-rs/audit-check@v1
21 | with:
22 | token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | pull_request:
6 |
7 | name: CI
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | clippy:
13 | name: Clippy ${{ matrix.rust }} / ${{ matrix.triple.target }}
14 | runs-on: ${{ matrix.triple.os }}
15 | strategy:
16 | matrix:
17 | # Borrowed from https://github.com/GuillaumeGomez/sysinfo/blob/master/.github/workflows/CI.yml#L45
18 | triple:
19 | - { os: 'ubuntu-latest', target: 'x86_64-unknown-linux-gnu', cross: false }
20 | - { os: 'ubuntu-latest', target: 'i686-unknown-linux-gnu', cross: true }
21 | # `macos-latest` is now using macOS 14 and ARM64. Future runners will also
22 | # be ARM64: https://github.com/actions/runner-images/issues/9741
23 | - { os: 'macos-latest', target: 'x86_64-apple-darwin', cross: false, always_install_target: true }
24 | - { os: 'macos-latest', target: 'aarch64-apple-darwin', cross: false }
25 | - { os: 'windows-latest', target: 'x86_64-pc-windows-msvc', cross: false }
26 | # iOS
27 | - { os: 'macos-latest', target: 'aarch64-apple-ios', cross: true }
28 | - { os: 'macos-latest', target: 'x86_64-apple-ios', cross: true }
29 | ## ARM64
30 | - { os: 'ubuntu-latest', target: 'aarch64-unknown-linux-gnu', cross: true }
31 | - { os: 'ubuntu-latest', target: 'aarch64-unknown-linux-musl', cross: true }
32 | ## ARMv7
33 | - { os: 'ubuntu-latest', target: 'armv7-unknown-linux-gnueabihf', cross: true }
34 | - { os: 'ubuntu-latest', target: 'armv7-unknown-linux-musleabihf', cross: true }
35 | ## ARMv6
36 | - { os: 'ubuntu-latest', target: 'arm-unknown-linux-gnueabihf', cross: true }
37 | - { os: 'ubuntu-latest', target: 'arm-unknown-linux-musleabihf', cross: true }
38 | # Android
39 | - { os: 'ubuntu-latest', target: 'aarch64-linux-android', cross: true }
40 | - { os: 'ubuntu-latest', target: 'armv7-linux-androideabi', cross: true }
41 | - { os: 'ubuntu-latest', target: 'x86_64-linux-android', cross: true }
42 | - { os: 'ubuntu-latest', target: 'i686-linux-android', cross: true }
43 |
44 | # WASM
45 | - { os: 'ubuntu-latest', target: 'wasm32-unknown-unknown', cross: false, always_install_target: true }
46 | rust:
47 | - stable
48 | # MSRV
49 | - 1.56.0
50 | steps:
51 | - uses: actions/checkout@v2
52 | with:
53 | persist-credentials: false
54 | - uses: actions-rs/toolchain@v1
55 | with:
56 | profile: minimal
57 | toolchain: ${{ matrix.rust }}
58 | components: clippy
59 |
60 | - name: Install cross-target
61 | if: matrix.triple.cross || matrix.triple.always_install_target
62 | run: rustup target add ${{ matrix.triple.target }}
63 |
64 | - uses: actions-rs/cargo@v1
65 | with:
66 | command: clippy
67 | args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml -- -D clippy::dbg_macro -D warnings -D missing_docs -F unused_must_use
68 | use-cross: ${{ matrix.triple.cross }}
69 |
70 | test:
71 | name: Test Desktop OSes
72 | strategy:
73 | matrix:
74 | os:
75 | - ubuntu-latest
76 | - macos-latest
77 | - windows-latest
78 | runs-on: ${{ matrix.os }}
79 | steps:
80 | - uses: actions/checkout@v2
81 | with:
82 | persist-credentials: false
83 | - uses: actions-rs/toolchain@v1
84 | with:
85 | profile: minimal
86 | toolchain: stable
87 |
88 | - uses: actions-rs/cargo@v1
89 | with:
90 | command: test
91 |
92 | test_ios:
93 | name: "Test iOS (Catalyst)"
94 | runs-on: macos-latest
95 | steps:
96 | - uses: actions/checkout@v2
97 | with:
98 | persist-credentials: false
99 | - name: Run iOS tests
100 | run: |
101 | rustup target add aarch64-apple-ios-macabi
102 | cargo test --target aarch64-apple-ios-macabi
103 |
104 | test_wasm:
105 | name: Test wasm32-unknown-unknown
106 | runs-on: ubuntu-latest
107 | steps:
108 | - uses: actions/checkout@v2
109 | with:
110 | persist-credentials: false
111 | - uses: actions-rs/toolchain@v1
112 | with:
113 | profile: minimal
114 | toolchain: stable
115 |
116 | - name: Install wasm-pack
117 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
118 | - name: Test wasm32
119 | run: |
120 | rustup target add wasm32-unknown-unknown
121 | wasm-pack test --firefox --headless -- --features "js"
122 |
123 | test_fallback:
124 | name: Check fallback implementation
125 | runs-on: ubuntu-latest
126 | steps:
127 | - uses: actions/checkout@v2
128 | with:
129 | persist-credentials: false
130 | - uses: actions-rs/toolchain@v1
131 | with:
132 | profile: minimal
133 | toolchain: stable
134 |
135 | - name: "Clippy"
136 | # Install a target that is obviously not supported and then check it to ensure
137 | # the fallback target stays in sync with the other platforms.
138 | run: |
139 | rustup target add x86_64-unknown-none
140 | cargo clippy --target x86_64-unknown-none -- -D clippy::dbg_macro -D warnings -D missing_docs -F unused_must_use
141 |
142 | fmt:
143 | name: Rustfmt
144 | runs-on: ubuntu-latest
145 | steps:
146 | - uses: actions/checkout@v2
147 | with:
148 | persist-credentials: false
149 | - uses: actions-rs/toolchain@v1
150 | with:
151 | profile: minimal
152 | toolchain: stable
153 | components: rustfmt
154 | - uses: actions-rs/cargo@v1
155 | with:
156 | command: fmt
157 | args: --all -- --check
158 |
159 | semver:
160 | runs-on: ubuntu-latest
161 | steps:
162 | - uses: actions/checkout@v4
163 | with:
164 | persist-credentials: false
165 | - name: Check semver
166 | uses: obi1kenobi/cargo-semver-checks-action@v2
167 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 |
3 | Cargo.lock
4 |
5 | /.idea/*
6 |
7 | /.vscode/*
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | The list of changes in each version can be found on GitHub: https://github.com/1Password/sys-locale/releases
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "sys-locale"
3 | version = "0.3.2"
4 | authors = ["1Password"]
5 | description = "Small and lightweight library to obtain the active system locale"
6 | keywords = ["locale", "i18n", "localization", "nostd"]
7 | repository = "https://github.com/1Password/sys-locale"
8 | edition = "2018"
9 | license = "MIT OR Apache-2.0"
10 | rust-version = "1.56"
11 |
12 | exclude = [
13 | "/.github/",
14 | "/examples/",
15 | "/tests/",
16 | "/.gitignore"
17 | ]
18 |
19 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
20 | [target.'cfg(target_os = "android")'.dependencies]
21 | libc = "0.2"
22 |
23 | [target.'cfg(all(target_family = "wasm", not(unix)))'.dependencies]
24 | js-sys = { version = "0.3", optional = true }
25 | wasm-bindgen = { version = "0.2", optional = true }
26 | web-sys = { version = "0.3", features = ["Window", "WorkerGlobalScope", "Navigator", "WorkerNavigator"], optional = true }
27 |
28 | [features]
29 | js = ["js-sys", "wasm-bindgen", "web-sys"]
30 |
31 | [target.'cfg(all(target_family = "wasm", not(unix)))'.dev-dependencies]
32 | wasm-bindgen-test = "0.3"
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 1Password
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sys-locale
2 |
3 | [](https://crates.io/crates/sys-locale)
4 | [](https://docs.rs/sys-locale)
5 | 
6 | [](https://crates.io/crates/sys-locale)
7 | 
8 |
9 | A small and lightweight Rust library to get the current active locale on the system.
10 |
11 | `sys-locale` is small library to get the current locale set for the system or application with the relevant platform APIs. The library is also `no_std` compatible, relying only on `alloc`, except on Linux and BSD.
12 |
13 | Platform support currently includes:
14 | - Android
15 | - iOS (and derivatives such as watchOS, tvOS, and visionOS)
16 | - macOS
17 | - Linux, BSD, and other UNIX variations
18 | - WebAssembly, for the following platforms:
19 | - Inside of a web browser (via the `js` feature)
20 | - Emscripten (via the `UNIX` backend)
21 | Further support for other WASM targets is dependent on upstream
22 | support in those target's runtimes and specifications.
23 | - Windows
24 |
25 | ```rust
26 | use sys_locale::get_locale;
27 |
28 | let locale = get_locale().unwrap_or_else(|| String::from("en-US"));
29 |
30 | println!("The current locale is {}", locale);
31 | ```
32 |
33 | ## MSRV
34 |
35 | The Minimum Supported Rust Version is currently 1.56.0. This will be bumped to a newer stable version of Rust when needed.
36 |
37 | ## Credits
38 |
39 | Made with ❤️ by the [1Password](https://1password.com/) team.
40 |
41 | #### License
42 |
43 |
44 | Licensed under either of Apache License, Version
45 | 2.0 or MIT license at your option.
46 |
47 |
48 |
49 |
50 |
51 | Unless you explicitly state otherwise, any contribution intentionally submitted
52 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
53 | be dual licensed as above, without any additional terms or conditions.
54 |
55 |
--------------------------------------------------------------------------------
/examples/get_locale.rs:
--------------------------------------------------------------------------------
1 | //! A small example to run on your computer to see what locale the library returns.
2 | #![allow(unknown_lints)]
3 | #![allow(clippy::uninlined_format_args)]
4 |
5 | use sys_locale::get_locale;
6 |
7 | fn main() {
8 | let locale = get_locale().unwrap_or_else(|| String::from("en-US"));
9 |
10 | println!("The current locale is {}", locale);
11 | }
12 |
--------------------------------------------------------------------------------
/gen-windows-bindings/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "gen-windows-bindings"
3 | version = "0.1.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [dependencies]
8 | windows-bindgen = "0.51"
9 |
10 | # Prevent this from interfering with workspaces
11 | [workspace]
12 | members = ["."]
--------------------------------------------------------------------------------
/gen-windows-bindings/bindings.config:
--------------------------------------------------------------------------------
1 | --out ../src/windows_sys.rs
2 | // We use `std` for now until the versioning of `windows-targets` has had more time to settle.
3 | // After that, replace it with the `sys` feature instead.
4 | --config flatten std minimal
5 |
6 | --filter
7 | Windows.Win32.Foundation.TRUE
8 | Windows.Win32.Globalization.GetUserPreferredUILanguages
9 | Windows.Win32.Globalization.MUI_LANGUAGE_NAME
10 |
--------------------------------------------------------------------------------
/gen-windows-bindings/src/main.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | windows_bindgen::bindgen(&["--etc", "./bindings.config"]).unwrap();
3 | }
4 |
--------------------------------------------------------------------------------
/src/android.rs:
--------------------------------------------------------------------------------
1 | use alloc::{string::String, vec};
2 | use core::convert::TryFrom;
3 |
4 | fn get_property(name: &'static [u8]) -> Option {
5 | let mut value = vec![0u8; libc::PROP_VALUE_MAX as usize];
6 | // SAFETY: `name` is valid to read from and `value` is valid to write to.
7 | let len =
8 | unsafe { libc::__system_property_get(name.as_ptr().cast(), value.as_mut_ptr().cast()) };
9 |
10 | usize::try_from(len)
11 | .ok()
12 | .filter(|n| *n != 0)
13 | .and_then(move |n| {
14 | // Remove excess bytes and the NUL terminator
15 | value.resize(n, 0);
16 | String::from_utf8(value).ok()
17 | })
18 | }
19 |
20 | const LOCALE_KEY: &[u8] = b"persist.sys.locale\0";
21 | const PRODUCT_LOCALE_KEY: &[u8] = b"ro.product.locale\0";
22 |
23 | const PRODUCT_LANGUAGE_KEY: &[u8] = b"ro.product.locale.language\0";
24 | const PRODUCT_REGION_KEY: &[u8] = b"ro.product.locale.region\0";
25 |
26 | // Android 4.0 and below
27 | const LANG_KEY: &[u8] = b"persist.sys.language\0";
28 | const COUNTRY_KEY: &[u8] = b"persist.sys.country\0";
29 | const LOCALEVAR_KEY: &[u8] = b"persist.sys.localevar\0";
30 |
31 | // Ported from https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/jni/AndroidRuntime.cpp#431
32 | fn read_locale() -> Option {
33 | if let Some(locale) = get_property(LOCALE_KEY) {
34 | return Some(locale);
35 | }
36 |
37 | // Android 4.0 and below
38 | if let Some(mut language) = get_property(LANG_KEY) {
39 | // The details of this functionality are not publically available, so this is just
40 | // adapted "best effort" from the original code.
41 | match get_property(COUNTRY_KEY) {
42 | Some(country) => {
43 | language.push('-');
44 | language.push_str(&country);
45 | }
46 | None => {
47 | if let Some(variant) = get_property(LOCALEVAR_KEY) {
48 | language.push('-');
49 | language.push_str(&variant);
50 | }
51 | }
52 | };
53 |
54 | return Some(language);
55 | }
56 |
57 | if let Some(locale) = get_property(PRODUCT_LOCALE_KEY) {
58 | return Some(locale);
59 | }
60 |
61 | let product_language = get_property(PRODUCT_LANGUAGE_KEY);
62 | let product_region = get_property(PRODUCT_REGION_KEY);
63 | match (product_language, product_region) {
64 | (Some(mut lang), Some(region)) => {
65 | lang.push('-');
66 | lang.push_str(®ion);
67 | Some(lang)
68 | }
69 | _ => None,
70 | }
71 | }
72 |
73 | pub(crate) fn get() -> impl Iterator {
74 | read_locale().into_iter()
75 | }
76 |
--------------------------------------------------------------------------------
/src/apple.rs:
--------------------------------------------------------------------------------
1 | use alloc::{string::String, vec::Vec};
2 | use core::ffi::c_void;
3 |
4 | type CFIndex = isize;
5 | type Boolean = u8;
6 | type CFStringEncoding = u32;
7 |
8 | #[allow(non_upper_case_globals)]
9 | const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;
10 |
11 | #[repr(C)]
12 | #[derive(Clone, Copy)]
13 | struct CFRange {
14 | pub location: CFIndex,
15 | pub length: CFIndex,
16 | }
17 |
18 | type CFTypeRef = *const c_void;
19 |
20 | #[repr(C)]
21 | struct __CFArray(c_void);
22 | type CFArrayRef = *const __CFArray;
23 |
24 | #[repr(C)]
25 | struct __CFString(c_void);
26 | type CFStringRef = *const __CFString;
27 |
28 | // Most of these definitions come from `core-foundation-sys`, but we want this crate
29 | // to be `no_std` and `core-foundation-sys` isn't currently.
30 | #[link(name = "CoreFoundation", kind = "framework")]
31 | extern "C" {
32 | fn CFArrayGetCount(theArray: CFArrayRef) -> CFIndex;
33 | fn CFArrayGetValueAtIndex(theArray: CFArrayRef, idx: CFIndex) -> *const c_void;
34 |
35 | fn CFStringGetLength(theString: CFStringRef) -> CFIndex;
36 | fn CFStringGetBytes(
37 | theString: CFStringRef,
38 | range: CFRange,
39 | encoding: CFStringEncoding,
40 | lossByte: u8,
41 | isExternalRepresentation: Boolean,
42 | buffer: *mut u8,
43 | maxBufLen: CFIndex,
44 | usedBufLen: *mut CFIndex,
45 | ) -> CFIndex;
46 |
47 | fn CFRelease(cf: CFTypeRef);
48 |
49 | fn CFLocaleCopyPreferredLanguages() -> CFArrayRef;
50 | }
51 |
52 | pub(crate) fn get() -> impl Iterator {
53 | let preferred_langs = get_languages();
54 | let mut idx = 0;
55 |
56 | #[allow(clippy::as_conversions)]
57 | core::iter::from_fn(move || unsafe {
58 | let (langs, num_langs) = preferred_langs.as_ref()?;
59 |
60 | // 0 to N-1 inclusive
61 | if idx >= *num_langs {
62 | return None;
63 | }
64 |
65 | // SAFETY: The current index has been checked that its still within bounds of the array.
66 | // XXX: We don't retain the strings because we know we have total ownership of the backing array.
67 | let locale = CFArrayGetValueAtIndex(langs.0, idx) as CFStringRef;
68 | idx += 1;
69 |
70 | // SAFETY: `locale` is a valid CFString pointer because the array will always contain a value.
71 | let str_len = CFStringGetLength(locale);
72 |
73 | let range = CFRange {
74 | location: 0,
75 | length: str_len,
76 | };
77 |
78 | let mut capacity = 0;
79 | // SAFETY:
80 | // - `locale` is a valid CFString
81 | // - The supplied range is within the length of the string.
82 | // - `capacity` is writable.
83 | // Passing NULL and `0` is correct for the buffer to get the
84 | // encoded output length.
85 | CFStringGetBytes(
86 | locale,
87 | range,
88 | kCFStringEncodingUTF8,
89 | 0,
90 | false as Boolean,
91 | core::ptr::null_mut(),
92 | 0,
93 | &mut capacity,
94 | );
95 |
96 | // Guard against a zero-sized allocation, if that were to somehow occur.
97 | if capacity == 0 {
98 | return None;
99 | }
100 |
101 | // Note: This is the number of bytes (u8) that will be written to
102 | // the buffer, not the number of codepoints they would contain.
103 | let mut buffer = Vec::with_capacity(capacity as usize);
104 |
105 | // SAFETY:
106 | // - `locale` is a valid CFString
107 | // - The supplied range is within the length of the string.
108 | // - `buffer` is writable and has sufficent capacity to receive the data.
109 | // - `maxBufLen` is correctly based on `buffer`'s available capacity.
110 | // - `out_len` is writable.
111 | let mut out_len = 0;
112 | CFStringGetBytes(
113 | locale,
114 | range,
115 | kCFStringEncodingUTF8,
116 | 0,
117 | false as Boolean,
118 | buffer.as_mut_ptr(),
119 | capacity as CFIndex,
120 | &mut out_len,
121 | );
122 |
123 | // Sanity check that both calls to `CFStringGetBytes`
124 | // were equivalent. If they weren't, the system is doing
125 | // something very wrong...
126 | assert!(out_len <= capacity);
127 |
128 | // SAFETY: The system has written `out_len` elements, so they are
129 | // initialized and inside the buffer's capacity bounds.
130 | buffer.set_len(out_len as usize);
131 |
132 | // This should always contain UTF-8 since we told the system to
133 | // write UTF-8 into the buffer, but the value is small enough that
134 | // using `from_utf8_unchecked` isn't worthwhile.
135 | String::from_utf8(buffer).ok()
136 | })
137 | }
138 |
139 | fn get_languages() -> Option<(CFArray, CFIndex)> {
140 | unsafe {
141 | // SAFETY: This function is safe to call and has no invariants. Any value inside the
142 | // array will be owned by us.
143 | let langs = CFLocaleCopyPreferredLanguages();
144 | if !langs.is_null() {
145 | let langs = CFArray(langs);
146 | // SAFETY: The returned array is a valid CFArray object.
147 | let count = CFArrayGetCount(langs.0);
148 | if count != 0 {
149 | Some((langs, count))
150 | } else {
151 | None
152 | }
153 | } else {
154 | None
155 | }
156 | }
157 | }
158 |
159 | struct CFArray(CFArrayRef);
160 |
161 | impl Drop for CFArray {
162 | fn drop(&mut self) {
163 | // SAFETY: This wrapper contains a valid CFArray.
164 | unsafe { CFRelease(self.0.cast()) }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! A library to safely and easily obtain the current locale on the system or for an application.
2 | //!
3 | //! This library currently supports the following platforms:
4 | //! - Android
5 | //! - iOS (and derivatives such as watchOS, tvOS, and visionOS)
6 | //! - macOS
7 | //! - Linux, BSD, and other UNIX variations
8 | //! - WebAssembly on the web (via the `js` feature)
9 | //! - Windows
10 | #![cfg_attr(any(not(unix), target_vendor = "apple", target_os = "android"), no_std)]
11 | extern crate alloc;
12 | use alloc::string::String;
13 |
14 | #[cfg(target_os = "android")]
15 | mod android;
16 | #[cfg(target_os = "android")]
17 | use android as provider;
18 |
19 | #[cfg(target_vendor = "apple")]
20 | mod apple;
21 | #[cfg(target_vendor = "apple")]
22 | use apple as provider;
23 |
24 | #[cfg(all(unix, not(any(target_vendor = "apple", target_os = "android"))))]
25 | mod unix;
26 | #[cfg(all(unix, not(any(target_vendor = "apple", target_os = "android"))))]
27 | use unix as provider;
28 |
29 | #[cfg(all(target_family = "wasm", feature = "js", not(unix)))]
30 | mod wasm;
31 | #[cfg(all(target_family = "wasm", feature = "js", not(unix)))]
32 | use wasm as provider;
33 |
34 | #[cfg(windows)]
35 | mod windows;
36 | #[cfg(windows)]
37 | use windows as provider;
38 |
39 | #[cfg(not(any(unix, all(target_family = "wasm", feature = "js", not(unix)), windows)))]
40 | mod provider {
41 | pub fn get() -> impl Iterator {
42 | core::iter::empty()
43 | }
44 | }
45 |
46 | /// Returns the most preferred locale for the system or application.
47 | ///
48 | /// This is equivalent to `get_locales().next()` (the first entry).
49 | ///
50 | /// # Returns
51 | ///
52 | /// Returns [`Some(String)`] with a BCP 47 language tag inside.
53 | /// If the locale couldn't be obtained, [`None`] is returned instead.
54 | ///
55 | /// # Example
56 | ///
57 | /// ```no_run
58 | /// use sys_locale::get_locale;
59 | ///
60 | /// let current_locale = get_locale().unwrap_or_else(|| String::from("en-US"));
61 | ///
62 | /// println!("The locale is {}", current_locale);
63 | /// ```
64 | pub fn get_locale() -> Option {
65 | get_locales().next()
66 | }
67 |
68 | /// Returns the preferred locales for the system or application, in descending order of preference.
69 | ///
70 | /// # Returns
71 | ///
72 | /// Returns an [`Iterator`] with any number of BCP 47 language tags inside.
73 | /// If no locale preferences could be obtained, the iterator will be empty.
74 | ///
75 | /// # Example
76 | ///
77 | /// ```no_run
78 | /// use sys_locale::get_locales;
79 | ///
80 | /// let mut locales = get_locales();
81 | ///
82 | /// println!("The most preferred locale is {}", locales.next().unwrap_or("en-US".to_string()));
83 | /// println!("The least preferred locale is {}", locales.last().unwrap_or("en-US".to_string()));
84 | /// ```
85 | pub fn get_locales() -> impl Iterator {
86 | provider::get()
87 | }
88 |
89 | #[cfg(test)]
90 | mod tests {
91 | use super::{get_locale, get_locales};
92 | extern crate std;
93 |
94 | #[cfg(all(target_family = "wasm", feature = "js", not(unix)))]
95 | use wasm_bindgen_test::wasm_bindgen_test as test;
96 | #[cfg(all(target_family = "wasm", feature = "js", not(unix)))]
97 | wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
98 |
99 | #[test]
100 | fn can_obtain_locale() {
101 | assert!(get_locale().is_some(), "no locales were returned");
102 | let locales = get_locales();
103 | for (i, locale) in locales.enumerate() {
104 | assert!(!locale.is_empty(), "locale string {} was empty", i);
105 | assert!(
106 | !locale.ends_with('\0'),
107 | "locale {} contained trailing NUL",
108 | i
109 | );
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/unix.rs:
--------------------------------------------------------------------------------
1 | use std::{env, ffi::OsStr};
2 |
3 | const LANGUAGE: &str = "LANGUAGE";
4 | const LC_ALL: &str = "LC_ALL";
5 | const LC_MESSAGES: &str = "LC_MESSAGES";
6 | const LANG: &str = "LANG";
7 |
8 | /// Environment variable access abstraction to allow testing without
9 | /// mutating env variables.
10 | ///
11 | /// Use [StdEnv] to query [std::env]
12 | trait EnvAccess {
13 | /// See also [std::env::var]
14 | fn get(&self, key: impl AsRef) -> Option;
15 | }
16 |
17 | /// Proxy to [std::env]
18 | struct StdEnv;
19 | impl EnvAccess for StdEnv {
20 | fn get(&self, key: impl AsRef) -> Option {
21 | env::var(key).ok()
22 | }
23 | }
24 |
25 | pub(crate) fn get() -> impl Iterator {
26 | _get(&StdEnv)
27 | }
28 |
29 | /// Retrieves a list of unique locales by checking specific environment variables
30 | /// in a predefined order: LANGUAGE, LC_ALL, LC_MESSAGES, and LANG.
31 | ///
32 | /// The function first checks the `LANGUAGE` environment variable, which can contain
33 | /// one or more locales separated by a colon (`:`). It then splits these values,
34 | /// converts them from [POSIX](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap08.html)
35 | /// to [BCP 47](https://www.ietf.org/rfc/bcp/bcp47.html) format, and adds them to the list of locales
36 | /// if they are not already included.
37 | ///
38 | /// Next, the function checks the `LC_ALL`, `LC_MESSAGES`, and `LANG` environment
39 | /// variables. Each of these variables contains a single locale. If a locale is found,
40 | /// and it's not empty, it is converted to BCP 47 format and added to the list if
41 | /// it is not already included.
42 | ///
43 | /// For more information check this issue: https://github.com/1Password/sys-locale/issues/14.
44 | ///
45 | /// The function ensures that locales are returned in the order of precedence
46 | /// and without duplicates. The final list of locales is returned as an iterator.
47 | ///
48 | /// # Returns
49 | ///
50 | /// An iterator over the unique locales found in the environment variables.
51 | ///
52 | /// # Environment Variables Checked
53 | ///
54 | /// 1. `LANGUAGE` - Can contain multiple locales, each separated by a colon (`:`), highest priority.
55 | /// 2. `LC_ALL` - Contains a single locale, high priority.
56 | /// 3. `LC_MESSAGES` - Contains a single locale, medium priority.
57 | /// 4. `LANG` - Contains a single locale, low priority.
58 | ///
59 | /// # Example
60 | ///
61 | /// ```ignore
62 | /// let locales: Vec = _get(&env).collect();
63 | /// for locale in locales {
64 | /// println!("User's preferred locales: {}", locale);
65 | /// }
66 | /// ```
67 | fn _get(env: &impl EnvAccess) -> impl Iterator {
68 | let mut locales = Vec::new();
69 |
70 | // LANGUAGE contains one or multiple locales separated by colon (':')
71 | if let Some(val) = env.get(LANGUAGE).filter(|val| !val.is_empty()) {
72 | for part in val.split(':') {
73 | let locale = posix_to_bcp47(part);
74 | if !locales.contains(&locale) {
75 | locales.push(locale);
76 | }
77 | }
78 | }
79 |
80 | // LC_ALL, LC_MESSAGES and LANG contain one locale
81 | for variable in [LC_ALL, LC_MESSAGES, LANG] {
82 | if let Some(val) = env.get(variable).filter(|val| !val.is_empty()) {
83 | let locale = posix_to_bcp47(&val);
84 | if !locales.contains(&locale) {
85 | locales.push(locale);
86 | }
87 | }
88 | }
89 |
90 | locales.into_iter()
91 | }
92 |
93 | /// Converts a POSIX locale string to a BCP 47 locale string.
94 | ///
95 | /// This function processes the input `code` by removing any character encoding
96 | /// (the part after the `.` character) and any modifiers (the part after the `@` character).
97 | /// It replaces underscores (`_`) with hyphens (`-`) to conform to BCP 47 formatting.
98 | ///
99 | /// If the locale is already in the BCP 47 format, no changes are made.
100 | ///
101 | /// Useful links:
102 | /// - [The Open Group Base Specifications Issue 8 - 7. Locale](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap07.html)
103 | /// - [The Open Group Base Specifications Issue 8 - 8. Environment Variables](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap08.html)
104 | /// - [BCP 47 specification](https://www.ietf.org/rfc/bcp/bcp47.html)
105 | ///
106 | /// # Examples
107 | ///
108 | /// ```ignore
109 | /// let bcp47 = posix_to_bcp47("en-US"); // already BCP 47
110 | /// assert_eq!(bcp47, "en-US"); // no changes
111 | ///
112 | /// let bcp47 = posix_to_bcp47("en_US");
113 | /// assert_eq!(bcp47, "en-US");
114 | ///
115 | /// let bcp47 = posix_to_bcp47("ru_RU.UTF-8");
116 | /// assert_eq!(bcp47, "ru-RU");
117 | ///
118 | /// let bcp47 = posix_to_bcp47("fr_FR@dict");
119 | /// assert_eq!(bcp47, "fr-FR");
120 | ///
121 | /// let bcp47 = posix_to_bcp47("de_DE.UTF-8@euro");
122 | /// assert_eq!(bcp47, "de-DE");
123 | /// ```
124 | ///
125 | /// # TODO
126 | ///
127 | /// 1. Implement POSIX to BCP 47 modifier conversion (see https://github.com/1Password/sys-locale/issues/32).
128 | /// 2. Optimize to avoid creating a new buffer (see https://github.com/1Password/sys-locale/pull/33).
129 | fn posix_to_bcp47(locale: &str) -> String {
130 | locale
131 | .chars()
132 | .take_while(|&c| c != '.' && c != '@')
133 | .map(|c| if c == '_' { '-' } else { c })
134 | .collect()
135 | }
136 |
137 | #[cfg(test)]
138 | mod tests {
139 | use super::{EnvAccess, _get, posix_to_bcp47, LANG, LANGUAGE, LC_ALL, LC_MESSAGES};
140 | use std::{
141 | collections::HashMap,
142 | ffi::{OsStr, OsString},
143 | };
144 |
145 | type MockEnv = HashMap;
146 | impl EnvAccess for MockEnv {
147 | fn get(&self, key: impl AsRef) -> Option {
148 | self.get(key.as_ref()).cloned()
149 | }
150 | }
151 |
152 | const BCP_47: &str = "fr-FR";
153 | const POSIX: &str = "fr_FR";
154 | const POSIX_ENC: &str = "fr_FR.UTF-8";
155 | const POSIX_MOD: &str = "fr_FR@euro";
156 | const POSIX_ENC_MOD: &str = "fr_FR.UTF-8@euro";
157 |
158 | #[test]
159 | fn parse_identifier() {
160 | assert_eq!(posix_to_bcp47(BCP_47), BCP_47);
161 | assert_eq!(posix_to_bcp47(POSIX), BCP_47);
162 | assert_eq!(posix_to_bcp47(POSIX_ENC), BCP_47);
163 | assert_eq!(posix_to_bcp47(POSIX_MOD), BCP_47);
164 | assert_eq!(posix_to_bcp47(POSIX_ENC_MOD), BCP_47);
165 | }
166 |
167 | #[test]
168 | fn env_get() {
169 | fn case(
170 | env: &mut MockEnv,
171 | language: impl Into,
172 | lc_all: impl Into,
173 | lc_messages: impl Into,
174 | lang: impl Into,
175 | expected: impl IntoIterator>,
176 | ) {
177 | env.insert(LANGUAGE.into(), language.into());
178 | env.insert(LC_ALL.into(), lc_all.into());
179 | env.insert(LC_MESSAGES.into(), lc_messages.into());
180 | env.insert(LANG.into(), lang.into());
181 | assert!(_get(env).eq(expected.into_iter().map(|s| s.into())));
182 | }
183 |
184 | let mut env = MockEnv::new();
185 | assert_eq!(_get(&env).next(), None);
186 |
187 | // Empty
188 | case(&mut env, "", "", "", "", &[] as &[String]);
189 |
190 | // Constants
191 | case(
192 | &mut env,
193 | POSIX_ENC_MOD,
194 | POSIX_ENC,
195 | POSIX_MOD,
196 | POSIX,
197 | [BCP_47],
198 | );
199 |
200 | // Only one variable
201 | case(&mut env, "en_US", "", "", "", ["en-US"]);
202 | case(&mut env, "", "en_US", "", "", ["en-US"]);
203 | case(&mut env, "", "", "en_US", "", ["en-US"]);
204 | case(&mut env, "", "", "", "en_US", ["en-US"]);
205 |
206 | // Duplicates
207 | case(&mut env, "en_US", "en_US", "en_US", "en_US", ["en-US"]);
208 | case(
209 | &mut env,
210 | "en_US",
211 | "en_US",
212 | "ru_RU",
213 | "en_US",
214 | ["en-US", "ru-RU"],
215 | );
216 | case(
217 | &mut env,
218 | "en_US",
219 | "ru_RU",
220 | "ru_RU",
221 | "en_US",
222 | ["en-US", "ru-RU"],
223 | );
224 | case(
225 | &mut env,
226 | "en_US",
227 | "es_ES",
228 | "ru_RU",
229 | "en_US",
230 | ["en-US", "es-ES", "ru-RU"],
231 | );
232 | case(
233 | &mut env,
234 | "en_US:ru_RU:es_ES:en_US",
235 | "es_ES",
236 | "ru_RU",
237 | "en_US",
238 | ["en-US", "ru-RU", "es-ES"],
239 | );
240 |
241 | // Duplicates with different case
242 | case(
243 | &mut env,
244 | "en_US:fr_fr",
245 | "EN_US",
246 | "fR_Fr",
247 | "En_US",
248 | ["en-US", "fr-fr", "EN-US", "fR-Fr", "En-US"],
249 | );
250 |
251 | // More complicated cases
252 | case(
253 | &mut env,
254 | "ru_RU:ru:en_US:en",
255 | "ru_RU.UTF-8",
256 | "ru_RU.UTF-8",
257 | "ru_RU.UTF-8",
258 | ["ru-RU", "ru", "en-US", "en"],
259 | );
260 | case(
261 | &mut env,
262 | "fr_FR.UTF-8@euro:fr_FR.UTF-8:fr_FR:fr:en_US.UTF-8:en_US:en",
263 | "es_ES.UTF-8@euro",
264 | "fr_FR.UTF-8@euro",
265 | "fr_FR.UTF-8@euro",
266 | ["fr-FR", "fr", "en-US", "en", "es-ES"],
267 | );
268 | case(
269 | &mut env,
270 | "",
271 | "es_ES.UTF-8@euro",
272 | "fr_FR.UTF-8@euro",
273 | "fr_FR.UTF-8@euro",
274 | ["es-ES", "fr-FR"],
275 | );
276 | case(
277 | &mut env,
278 | "fr_FR@euro",
279 | "fr_FR.UTF-8",
280 | "en_US.UTF-8",
281 | "en_US.UTF-8@dict",
282 | ["fr-FR", "en-US"],
283 | );
284 |
285 | // Already BCP 47
286 | case(&mut env, BCP_47, BCP_47, BCP_47, POSIX, [BCP_47]);
287 | case(
288 | &mut env,
289 | "fr-FR",
290 | "es-ES",
291 | "de-DE",
292 | "en-US",
293 | ["fr-FR", "es-ES", "de-DE", "en-US"],
294 | );
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/src/wasm.rs:
--------------------------------------------------------------------------------
1 | use alloc::string::String;
2 |
3 | use js_sys::{JsString, Object};
4 | use wasm_bindgen::{prelude::*, JsCast, JsValue};
5 |
6 | #[derive(Clone)]
7 | enum GlobalType {
8 | Window(web_sys::Window),
9 | Worker(web_sys::WorkerGlobalScope),
10 | }
11 |
12 | /// Returns a handle to the global scope object.
13 | ///
14 | /// Simplified version of https://github.com/rustwasm/wasm-bindgen/blob/main/crates/js-sys/src/lib.rs,
15 | /// which we can't use directly because it discards information about how it
16 | /// retrieved the global.
17 | fn global() -> GlobalType {
18 | #[wasm_bindgen]
19 | extern "C" {
20 | type Global;
21 |
22 | #[wasm_bindgen(getter, catch, static_method_of = Global, js_class = window, js_name = window)]
23 | fn get_window() -> Result