1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 11:43:51 +00:00

Merge branch 'arch/ng-localize' into kiro-localize

# Conflicts:
#	apps/browser/src/autofill/notification/bar.ts
#	apps/browser/webpack.config.js
#	package-lock.json
#	package.json
This commit is contained in:
Hinton
2025-07-28 00:45:06 +02:00
18 changed files with 580 additions and 654 deletions

View File

@@ -44,8 +44,11 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./apps/web/webpack.i18n.js"
},
"outputPath": "dist/web",
"index": "apps/web/src/index.html",
"main": "apps/web/src/main.ts",
@@ -55,6 +58,12 @@
"styles": [],
"scripts": []
}
},
"extract-i18n": {
"builder": "@angular-builders/custom-webpack:extract-i18n",
"options": {
"buildTarget": "web:build"
}
}
}
},

View File

@@ -0,0 +1,11 @@
{
"locale": "sv-Se",
"translations": {
"4606963464835766483": "Uppdaterad {$ICU}",
"2002272803511843863": "{VAR_PLURAL, plural, =0 {precis nu} =1 {en minut sedan} other {för {INTERPOLATION} minuter sedan}}",
"1150463724722084961": "Detta är en flytande länk till {$START_LINK}Inställningar{$CLOSE_LINK} med text före och efter.",
"5010897546053474360": "En fras som vi verkligen {$START_TAG_STRONG}behöver{$CLOSE_TAG_STRONG} framhäva!",
"email": "E-postadress",
"616177228530588915": "En översatt sträng med en länk. {$PH}"
}
}

View File

@@ -1,3 +1,5 @@
import "@angular/localize/init";
import { loadTranslations } from "@angular/localize";
import { render } from "lit";
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
@@ -23,6 +25,19 @@ import {
NotificationTypes,
} from "./abstractions/notification-bar";
async function initLanguage(locale: string): Promise<void> {
if (locale === "en") {
return;
}
const json = await fetch("/_locales/messages." + locale + ".json").then((r) => r.json());
loadTranslations(json.translations);
$localize.locale = locale;
}
void initLanguage("sv-se");
const logService = new ConsoleLogService(false);
let notificationBarIframeInitData: NotificationBarIframeInitData = {};
let windowMessageOrigin: string;
@@ -55,6 +70,7 @@ function applyNotificationBarStyle() {
}
function getI18n() {
const a = 5;
return {
appName: chrome.i18n.getMessage("appName"),
atRiskPassword: chrome.i18n.getMessage("atRiskPassword"),
@@ -219,7 +235,7 @@ export function getNotificationTestId(
function setElementText(template: HTMLTemplateElement, elementId: string, text: string): void {
const element = template.content.getElementById(elementId);
if (element) {
element.textContent = text;
element.innerHTML = text;
}
}

View File

@@ -1,4 +1,5 @@
import { enableProdMode } from "@angular/core";
import { loadTranslations } from "@angular/localize";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { PopupSizeService } from "../platform/popup/layout/popup-size.service";
@@ -22,8 +23,17 @@ if (process.env.ENV === "production") {
enableProdMode();
}
function init() {
void platformBrowserDynamic().bootstrapModule(AppModule);
}
void initLanguage("sv-se").then(() => {
return platformBrowserDynamic().bootstrapModule(AppModule);
});
init();
async function initLanguage(locale: string): Promise<void> {
if (locale === "en") {
return;
}
const json = await fetch("/_locales/messages." + locale + ".json").then((r) => r.json());
loadTranslations(json.translations);
$localize.locale = locale;
}

View File

@@ -1,3 +1,4 @@
import "core-js/stable";
import "core-js/proposals/explicit-resource-management";
import "zone.js";
import "@angular/localize/init";

View File

@@ -81,7 +81,7 @@ const moduleRules = [
{
loader: "babel-loader",
options: {
configFile: "../../babel.config.json",
configFile: "../../babel2.config.json",
cacheDirectory: ENV === "development",
compact: ENV !== "development",
},

View File

@@ -13,15 +13,7 @@ export class I18nService extends BaseI18nService {
systemLanguage || "en-US",
localesDirectory,
async (formattedLocale: string) => {
const filePath =
this.localesDirectory +
"/" +
formattedLocale +
"/messages.json?cache=" +
process.env.CACHE_TAG;
const localesResult = await fetch(filePath);
const locales = await localesResult.json();
return locales;
return Promise.resolve({});
},
globalStateProvider,
);

View File

@@ -0,0 +1,10 @@
{
"locale": "sv-Se",
"translations": {
"4606963464835766483": "Uppdaterad {$ICU}",
"2002272803511843863": "{VAR_PLURAL, plural, =0 {precis nu} =1 {en minut sedan} other {för {INTERPOLATION} minuter sedan}}",
"1150463724722084961": "Detta är en flytande länk till {$START_LINK}Inställningar{$CLOSE_LINK} med text före och efter.",
"5010897546053474360": "En fras som vi verkligen {$START_TAG_STRONG}behöver{$CLOSE_TAG_STRONG} framhäva!",
"email": "E-postadress"
}
}

View File

@@ -1,4 +1,5 @@
import { enableProdMode } from "@angular/core";
import { loadTranslations } from "@angular/localize";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app/app.module";
@@ -7,4 +8,17 @@ if (process.env.NODE_ENV === "production") {
enableProdMode();
}
void platformBrowserDynamic().bootstrapModule(AppModule);
void initLanguage("sv-se").then(() => {
return platformBrowserDynamic().bootstrapModule(AppModule);
});
async function initLanguage(locale: string): Promise<void> {
if (locale === "en") {
return;
}
const json = await fetch("/locales/messages." + locale + ".json").then((r) => r.json());
loadTranslations(json);
$localize.locale = locale;
}

View File

@@ -2,6 +2,8 @@ import "core-js/stable";
import "core-js/proposals/explicit-resource-management";
import "zone.js";
import "@angular/localize/init";
if (process.env.NODE_ENV === "production") {
// Production
} else {

View File

@@ -80,7 +80,7 @@ const moduleRules = [
{
loader: "babel-loader",
options: {
configFile: "../../babel.config.json",
configFile: "../../babel2.config.json",
cacheDirectory: NODE_ENV !== "production",
},
},

218
apps/web/webpack.i18n.js Normal file
View File

@@ -0,0 +1,218 @@
const path = require("path");
const { AngularWebpackPlugin } = require("@ngtools/webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackInjector = require("html-webpack-injector");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const webpack = require("webpack");
const config = require("./config.js");
const pjson = require("./package.json");
const ENV = process.env.ENV == null ? "development" : process.env.ENV;
const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV;
const LOGGING = process.env.LOGGING != "false";
const envConfig = config.load(ENV);
if (LOGGING) {
config.log(envConfig);
}
const moduleRules = [
{
test: /\.(html)$/,
loader: "html-loader",
},
{
test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
exclude: /loading(|-white).svg/,
generator: {
filename: "fonts/[name].[contenthash][ext]",
},
type: "asset/resource",
},
{
test: /\.(jpe?g|png|gif|svg|webp|avif)$/i,
exclude: /.*(bwi-font)\.svg/,
generator: {
filename: "images/[name][ext]",
},
type: "asset/resource",
},
{
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
"resolve-url-loader",
{
loader: "sass-loader",
options: {
sourceMap: true,
},
},
],
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
"resolve-url-loader",
{
loader: "postcss-loader",
options: {
sourceMap: true,
},
},
],
},
{
test: /\.[jt]sx?$/,
use: [
{
loader: "@ngtools/webpack",
},
],
},
{
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
];
const plugins = [
new HtmlWebpackPlugin({
template: "./apps/web/src/index.html",
filename: "index.html",
chunks: ["theme_head", "app/polyfills", "app/vendor", "app/main", "styles"],
}),
new HtmlWebpackInjector(),
new HtmlWebpackPlugin({
template: "./apps/web/src/404.html",
filename: "404.html",
chunks: ["styles"],
// 404 page is a wildcard, this ensures it uses absolute paths.
publicPath: "/",
}),
new CopyWebpackPlugin({
patterns: [
{ from: "./apps/web/src/.nojekyll" },
{ from: "./apps/web/src/manifest.json" },
{ from: "./apps/web/src/favicon.ico" },
{ from: "./apps/web/src/browserconfig.xml" },
{ from: "./apps/web/src/app-id.json" },
{ from: "./apps/web/src/images", to: "images" },
{ from: "./apps/web/src/locales", to: "locales" },
{ from: "./node_modules/qrious/dist/qrious.min.js", to: "scripts" },
{ from: "./node_modules/braintree-web-drop-in/dist/browser/dropin.js", to: "scripts" },
{
from: "./apps/web/src/version.json",
transform(content, path) {
return content.toString().replace("process.env.APPLICATION_VERSION", pjson.version);
},
},
],
}),
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
chunkFilename: "[id].[contenthash].css",
}),
new webpack.ProvidePlugin({
process: "process/browser.js",
}),
new webpack.EnvironmentPlugin({
ENV: ENV,
NODE_ENV: NODE_ENV === "production" ? "production" : "development",
APPLICATION_VERSION: pjson.version,
CACHE_TAG: Math.random().toString(36).substring(7),
URLS: envConfig["urls"] ?? {},
STRIPE_KEY: envConfig["stripeKey"] ?? "",
BRAINTREE_KEY: envConfig["braintreeKey"] ?? "",
PAYPAL_CONFIG: envConfig["paypal"] ?? {},
FLAGS: envConfig["flags"] ?? {},
DEV_FLAGS: NODE_ENV === "development" ? envConfig["devFlags"] : {},
ADDITIONAL_REGIONS: envConfig["additionalRegions"] ?? [],
}),
new AngularWebpackPlugin({
tsconfig: "apps/web/tsconfig.build.json",
entryModule: "src/app/app.module#AppModule",
sourceMap: true,
}),
];
const webpackConfig = {
mode: NODE_ENV,
devtool: "source-map",
target: "web",
entry: {
"app/polyfills": "./apps/web/src/polyfills.ts",
"app/main": "./apps/web/src/main.ts",
styles: ["./apps/web/src/scss/styles.scss", "./apps/web/src/scss/tailwind.css"],
theme_head: "./apps/web/src/theme.ts",
},
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "app/vendor",
chunks: (chunk) => {
return chunk.name === "app/main";
},
},
},
},
minimizer: [
new TerserPlugin({
terserOptions: {
safari10: true,
// Replicate Angular CLI behaviour
compress: {
global_defs: {
ngDevMode: false,
ngI18nClosureMode: false,
},
},
},
}),
],
},
resolve: {
extensions: [".ts", ".js"],
symlinks: false,
modules: [path.resolve("../../node_modules")],
fallback: {
buffer: false,
util: require.resolve("util/"),
assert: false,
url: false,
fs: false,
process: false,
path: require.resolve("path-browserify"),
},
},
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname, "build"),
clean: true,
},
module: {
noParse: /argon2(-simd)?\.wasm$/,
rules: moduleRules,
},
experiments: {
asyncWebAssembly: true,
},
plugins: plugins,
};
module.exports = webpackConfig;

View File

@@ -1,4 +1,5 @@
import { enableProdMode } from "@angular/core";
import { loadTranslations } from "@angular/localize";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app/app.module";
@@ -7,4 +8,17 @@ if (process.env.NODE_ENV === "production") {
enableProdMode();
}
void platformBrowserDynamic().bootstrapModule(AppModule);
void initLanguage("sv-se").then(() => {
return platformBrowserDynamic().bootstrapModule(AppModule);
});
async function initLanguage(locale: string): Promise<void> {
if (locale === "en") {
return;
}
const json = await fetch("/locales/messages." + locale + ".json").then((r) => r.json());
loadTranslations(json.translations);
$localize.locale = locale;
}

View File

@@ -1,4 +1,4 @@
<!--
<!--
# Table of Contents
This file contains a single consolidated template for all visual clients.
@@ -10,6 +10,12 @@
MASTER_PASSWORD_ENTRY: displays the master password input field + login button
-->
<p i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ 5 }} minutes ago}}</p>
<p i18n>This is a inline link to <a href="/settings">Settings</a> with text before and after.</p>
<p i18n>A phrase we really <strong>need</strong> to highlight!</p>
<form [bitSubmit]="submit" [formGroup]="formGroup">
<div [ngClass]="{ 'tw-hidden': loginUiState !== LoginUiState.EMAIL_ENTRY }">
<!-- Email Address input -->

View File

@@ -77,6 +77,7 @@ export class LoginComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
readonly Icons = { WaveIcon, VaultIcon };
protected minutes = 5;
clientType: ClientType;
ClientType = ClientType;

888
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,6 +47,7 @@
"@angular-eslint/schematics": "19.6.0",
"@angular/cli": "19.2.14",
"@angular/compiler-cli": "19.2.14",
"@angular/localize": "19.2.14",
"@babel/core": "7.24.9",
"@babel/preset-env": "7.24.8",
"@compodoc/compodoc": "1.1.26",
@@ -155,6 +156,7 @@
"webpack-node-externals": "3.0.0"
},
"dependencies": {
"@angular-builders/custom-webpack": "19.0.1",
"@angular/animations": "19.2.14",
"@angular/cdk": "19.2.18",
"@angular/common": "19.2.14",