├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
└── extensions.json
├── LICENSE.md
├── README.md
├── angular.json
├── apps
├── .gitkeep
├── demo-e2e
│ ├── cypress.json
│ ├── src
│ │ ├── fixtures
│ │ │ └── example.json
│ │ ├── integration
│ │ │ └── app.spec.ts
│ │ ├── plugins
│ │ │ └── index.js
│ │ └── support
│ │ │ ├── app.po.ts
│ │ │ ├── commands.ts
│ │ │ └── index.ts
│ ├── tsconfig.e2e.json
│ ├── tsconfig.json
│ └── tslint.json
├── demo
│ ├── browserslist
│ ├── jest.config.js
│ ├── src
│ │ ├── app
│ │ │ ├── app-routing.module.ts
│ │ │ ├── app.component.html
│ │ │ ├── app.component.scss
│ │ │ ├── app.component.spec.ts
│ │ │ ├── app.component.ts
│ │ │ ├── app.module.ts
│ │ │ └── lazy-loaded
│ │ │ │ ├── lazy-loaded.component.ts
│ │ │ │ └── lazy-loaded.module.ts
│ │ ├── assets
│ │ │ ├── .gitkeep
│ │ │ └── i18n
│ │ │ │ ├── fr.json
│ │ │ │ └── lazy.fr.json
│ │ ├── environments
│ │ │ ├── environment.prod.ts
│ │ │ └── environment.ts
│ │ ├── favicon.ico
│ │ ├── i18n-setup.ts
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── polyfills.ts
│ │ ├── styles.scss
│ │ └── test-setup.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── electron
│ ├── .gitignore
│ ├── src
│ │ ├── icons
│ │ │ ├── icon.icns
│ │ │ ├── icon.ico
│ │ │ └── icon.png
│ │ ├── index.ts
│ │ └── package.json
│ └── tsconfig.json
├── web-e2e
│ ├── cypress.json
│ ├── src
│ │ ├── fixtures
│ │ │ └── example.json
│ │ ├── integration
│ │ │ └── app.spec.ts
│ │ ├── plugins
│ │ │ └── index.js
│ │ └── support
│ │ │ ├── app.po.ts
│ │ │ ├── commands.ts
│ │ │ └── index.ts
│ ├── tsconfig.e2e.json
│ ├── tsconfig.json
│ └── tslint.json
└── web
│ ├── browserslist
│ ├── jest.config.js
│ ├── src
│ ├── app
│ │ ├── app.component.scss
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.electron.module.ts
│ │ └── app.module.ts
│ ├── assets
│ │ ├── .gitkeep
│ │ └── img
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-256x256.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── bg.jpg
│ │ │ ├── bg.png
│ │ │ ├── browserconfig.xml
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── favicon.ico
│ │ │ ├── locl-full.png
│ │ │ ├── locl-social.png
│ │ │ ├── mstile-150x150.png
│ │ │ ├── safari-pinned-tab.svg
│ │ │ └── site.webmanifest
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.electron.ts
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.scss
│ └── test-setup.ts
│ ├── tsconfig.electron.json
│ ├── tsconfig.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── commitlint.config.js
├── decorate-angular-cli.js
├── jest.config.js
├── lerna.json
├── libs
├── .gitkeep
├── app
│ ├── src
│ │ ├── electron
│ │ │ ├── .xplatframework
│ │ │ ├── index.ts
│ │ │ └── src
│ │ │ │ ├── core.module.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── services
│ │ │ │ ├── electron.service.ts
│ │ │ │ └── index.ts
│ │ ├── test-setup.ts
│ │ └── web
│ │ │ ├── .xplatframework
│ │ │ ├── index.ts
│ │ │ ├── scss
│ │ │ ├── _index.scss
│ │ │ ├── _spacing.scss
│ │ │ ├── _tags.scss
│ │ │ ├── _variables.scss
│ │ │ └── package.json
│ │ │ └── src
│ │ │ ├── core.module.spec.ts
│ │ │ ├── core.module.ts
│ │ │ ├── index.ts
│ │ │ └── services
│ │ │ ├── index.ts
│ │ │ ├── log.service.ts
│ │ │ ├── tokens.ts
│ │ │ └── window.service.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── cli
│ ├── README.md
│ ├── ng-package.json
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── cmds
│ │ │ ├── common
│ │ │ │ ├── diagnostics.ts
│ │ │ │ ├── file_utils.ts
│ │ │ │ └── util.ts
│ │ │ ├── convert.ts
│ │ │ ├── convert
│ │ │ │ ├── convert.ts
│ │ │ │ ├── message_serialization
│ │ │ │ │ ├── message_renderer.ts
│ │ │ │ │ ├── message_serializer.ts
│ │ │ │ │ └── target_message_renderer.ts
│ │ │ │ ├── translation_parsers
│ │ │ │ │ ├── simple_json_translation_parser.ts
│ │ │ │ │ ├── translation_parse_error.ts
│ │ │ │ │ ├── translation_parser.ts
│ │ │ │ │ ├── translation_utils.ts
│ │ │ │ │ ├── xliff1_translation_parser.ts
│ │ │ │ │ ├── xliff2_translation_parser.ts
│ │ │ │ │ └── xtb_translation_parser.ts
│ │ │ │ └── translations.ts
│ │ │ ├── extract.ts
│ │ │ └── extract
│ │ │ │ ├── extract.ts
│ │ │ │ ├── extractor.ts
│ │ │ │ ├── source_file_utils.ts
│ │ │ │ ├── source_files
│ │ │ │ ├── es2015_extract_plugin.ts
│ │ │ │ └── es5_extract_plugin.ts
│ │ │ │ └── translation_files
│ │ │ │ ├── json_translation_serializer.ts
│ │ │ │ ├── translation_serializer.ts
│ │ │ │ ├── xliff1_translation_serializer.ts
│ │ │ │ ├── xliff2_translation_serializer.ts
│ │ │ │ ├── xmb_translation_serializer.ts
│ │ │ │ ├── xml_file.ts
│ │ │ │ └── xtb_translation_serializer.ts
│ │ ├── index.ts
│ │ ├── lib
│ │ │ └── cli.module.ts
│ │ ├── locl
│ │ ├── main.ts
│ │ ├── test-setup.ts
│ │ └── tsconfig.json
│ ├── test
│ │ └── cmds
│ │ │ ├── convert.spec.ts
│ │ │ ├── extract.spec.ts
│ │ │ └── mock.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── common
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── lib
│ │ │ ├── common.module.spec.ts
│ │ │ └── common.module.ts
│ │ └── test-setup.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── core
│ ├── README.md
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── lib
│ │ │ ├── core.module.ts
│ │ │ ├── global.ts
│ │ │ ├── init.ts
│ │ │ └── interfaces.ts
│ │ └── test-setup.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── scss
│ ├── _index.scss
│ ├── _variables.scss
│ └── package.json
└── utils
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── index.ts
│ ├── lib
│ │ ├── angular.ts
│ │ ├── objects.ts
│ │ └── platform.ts
│ └── test-setup.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.spec.json
│ └── tslint.json
├── nx.json
├── package-lock.json
├── package.json
├── testing
├── karma.conf.js
├── test.libs.ts
├── test.xplat.ts
├── tsconfig.libs.json
├── tsconfig.libs.spec.json
├── tsconfig.xplat.json
└── tsconfig.xplat.spec.json
├── tools
├── commitlint-plugin-body-content
│ ├── commitlint-plugin-body-content.js
│ └── package.json
├── electron
│ └── postinstall.js
├── schematics
│ └── .gitkeep
├── tsconfig.tools.json
└── web
│ └── postinstall.js
├── tsconfig.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = false
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [ocombe]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | yarn-error.log
34 | testem.log
35 | /typings
36 | .released-packages
37 |
38 | # System Files
39 | .DS_Store
40 | Thumbs.db
41 |
42 | # libs
43 | libs/**/*.js
44 | libs/**/*.map
45 | libs/**/*.d.ts
46 | libs/**/*.metadata.json
47 | libs/**/*.ngfactory.ts
48 | libs/**/*.ngsummary.json
49 |
50 | # xplat
51 | xplat/**/*.js
52 | xplat/**/*.map
53 | xplat/**/*.d.ts
54 | xplat/**/*.metadata.json
55 | xplat/**/*.ngfactory.ts
56 | xplat/**/*.ngsummary.json
57 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | **/xplat/*/.xplatframework
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "nrwl.angular-console",
4 | "angular.ng-template",
5 | "ms-vscode.vscode-typescript-tslint-plugin",
6 | "esbenp.prettier-vscode"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | 🌐 **Locl is an internationalization (i18n) tools suite for Angular.**
4 |
5 | ### Demos
6 | You can find a complete demo in the [apps/demo folder](apps/demo) and another one simpler [on StackBlitz](https://stackblitz.com/edit/ivy-ovy4cd) (it can take a long time to load the first time because ivy support on StackBlitz is still WIP).
7 |
8 | ### Core
9 | A library with various utility functions to help you with \$localize.
10 |
11 | - [Documentation](libs/core)
12 |
13 | ### CLI
14 | Dev tools to help you with `$localize` and Angular i18n.
15 |
16 | - [Documentation](libs/cli)
17 |
18 | ### Roadmap
19 | We have a bunch of features planned on our roadmap, but you can see the features requested by the community and vote for the ones that you want the most on https://locl.hellonext.co/.
20 |
21 | ### License
22 |
23 | Locl tools and libraries are distributed under the [AGPL v3.0 license]().
24 | You can use them for free in open source projects, but if you want to use them in commercial products with close source you are required to buy a proprietary license (coming soon).
25 |
--------------------------------------------------------------------------------
/apps/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/apps/demo-e2e/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileServerFolder": ".",
3 | "fixturesFolder": "./src/fixtures",
4 | "integrationFolder": "./src/integration",
5 | "pluginsFile": "./src/plugins/index",
6 | "supportFile": false,
7 | "video": true,
8 | "videosFolder": "../../dist/cypress/apps/web-demo-e2e/videos",
9 | "screenshotsFolder": "../../dist/cypress/apps/web-demo-e2e/screenshots",
10 | "chromeWebSecurity": false
11 | }
12 |
--------------------------------------------------------------------------------
/apps/demo-e2e/src/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io"
4 | }
5 |
--------------------------------------------------------------------------------
/apps/demo-e2e/src/integration/app.spec.ts:
--------------------------------------------------------------------------------
1 | import { getGreeting } from '../support/app.po';
2 |
3 | describe('web-demo', () => {
4 | beforeEach(() => cy.visit('/'));
5 |
6 | it('should display welcome message', () => {
7 | getGreeting().contains('Welcome to web-demo!');
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/apps/demo-e2e/src/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor');
15 |
16 | module.exports = (on, config) => {
17 | // `on` is used to hook into various events Cypress emits
18 | // `config` is the resolved Cypress config
19 |
20 | // Preprocess Typescript
21 | on('file:preprocessor', preprocessTypescript(config));
22 | };
23 |
--------------------------------------------------------------------------------
/apps/demo-e2e/src/support/app.po.ts:
--------------------------------------------------------------------------------
1 | export const getGreeting = () => cy.get('h1');
2 |
--------------------------------------------------------------------------------
/apps/demo-e2e/src/support/commands.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/apps/demo-e2e/src/support/index.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands';
18 |
--------------------------------------------------------------------------------
/apps/demo-e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "sourceMap": false,
5 | "outDir": "../../dist/out-tsc"
6 | },
7 | "include": ["src/**/*.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/demo-e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["cypress", "node"]
5 | },
6 | "include": ["**/*.ts"]
7 | }
8 |
--------------------------------------------------------------------------------
/apps/demo-e2e/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": [],
4 | "linterOptions": {
5 | "exclude": ["!**/*"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/demo/browserslist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/apps/demo/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'web-demo',
3 | preset: '../../jest.config.js',
4 | coverageDirectory: '../../coverage/apps/web-demo',
5 | snapshotSerializers: [
6 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
7 | 'jest-preset-angular/build/AngularSnapshotSerializer.js',
8 | 'jest-preset-angular/build/HTMLCommentSerializer.js'
9 | ]
10 | };
11 |
--------------------------------------------------------------------------------
/apps/demo/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { RouterModule, Routes } from '@angular/router';
4 | import { getTranslations, ParsedTranslationBundle } from '@locl/core';
5 |
6 | const routes: Routes = [
7 | {
8 | path: 'lazy',
9 | loadChildren: () =>
10 | getTranslations('/assets/i18n/lazy.fr.json').then(
11 | (data: ParsedTranslationBundle) => {
12 | return import('./lazy-loaded/lazy-loaded.module').then(
13 | mod => mod.LazyLoadedModule
14 | );
15 | }
16 | )
17 | }
18 | ];
19 |
20 | @NgModule({
21 | declarations: [],
22 | imports: [CommonModule, RouterModule.forRoot(routes)],
23 | exports: [RouterModule]
24 | })
25 | export class AppRoutingModule {}
26 |
--------------------------------------------------------------------------------
/apps/demo/src/app/app.component.html:
--------------------------------------------------------------------------------
1 | {{ title }}!
2 |
3 | It works! {{ title }}
4 |
5 |
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/apps/demo/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Remove template code below
3 | */
4 | :host {
5 | display: block;
6 | font-family: sans-serif;
7 | min-width: 300px;
8 | max-width: 600px;
9 | margin: 50px auto;
10 | }
11 |
12 | .gutter-left {
13 | margin-left: 9px;
14 | }
15 |
16 | .col-span-2 {
17 | grid-column: span 2;
18 | }
19 |
20 | .flex {
21 | display: flex;
22 | align-items: center;
23 | justify-content: center;
24 | }
25 |
26 | header {
27 | background-color: #143055;
28 | color: white;
29 | padding: 5px;
30 | border-radius: 3px;
31 | }
32 |
33 | main {
34 | padding: 0 36px;
35 | }
36 |
37 | p {
38 | text-align: center;
39 | }
40 |
41 | h1 {
42 | text-align: center;
43 | margin-left: 18px;
44 | font-size: 24px;
45 | }
46 |
47 | h2 {
48 | text-align: center;
49 | font-size: 20px;
50 | margin: 40px 0 10px 0;
51 | }
52 |
53 | .resources {
54 | text-align: center;
55 | list-style: none;
56 | padding: 0;
57 | display: grid;
58 | grid-gap: 9px;
59 | grid-template-columns: 1fr 1fr;
60 | }
61 |
62 | .resource {
63 | color: #0094ba;
64 | height: 36px;
65 | background-color: rgba(0, 0, 0, 0);
66 | border: 1px solid rgba(0, 0, 0, 0.12);
67 | border-radius: 4px;
68 | padding: 3px 9px;
69 | text-decoration: none;
70 | }
71 |
72 | .resource:hover {
73 | background-color: rgba(68, 138, 255, 0.04);
74 | }
75 |
76 | pre {
77 | padding: 9px;
78 | border-radius: 4px;
79 | background-color: black;
80 | color: #eee;
81 | }
82 |
83 | details {
84 | border-radius: 4px;
85 | color: #333;
86 | background-color: rgba(0, 0, 0, 0);
87 | border: 1px solid rgba(0, 0, 0, 0.12);
88 | padding: 3px 9px;
89 | margin-bottom: 9px;
90 | }
91 |
92 | summary {
93 | cursor: pointer;
94 | outline: none;
95 | height: 36px;
96 | line-height: 36px;
97 | }
98 |
99 | .github-star-container {
100 | margin-top: 12px;
101 | line-height: 20px;
102 | }
103 |
104 | .github-star-container a {
105 | display: flex;
106 | align-items: center;
107 | text-decoration: none;
108 | color: #333;
109 | }
110 |
111 | .github-star-badge {
112 | color: #24292e;
113 | display: flex;
114 | align-items: center;
115 | font-size: 12px;
116 | padding: 3px 10px;
117 | border: 1px solid rgba(27, 31, 35, 0.2);
118 | border-radius: 3px;
119 | background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%);
120 | margin-left: 4px;
121 | font-weight: 600;
122 | }
123 |
124 | .github-star-badge:hover {
125 | background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%);
126 | border-color: rgba(27, 31, 35, 0.35);
127 | background-position: -0.5em;
128 | }
129 | .github-star-badge .material-icons {
130 | height: 16px;
131 | width: 16px;
132 | margin-right: 4px;
133 | }
134 |
--------------------------------------------------------------------------------
/apps/demo/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 |
4 | describe('AppComponent', () => {
5 | beforeEach(async(() => {
6 | TestBed.configureTestingModule({
7 | declarations: [AppComponent]
8 | }).compileComponents();
9 | }));
10 |
11 | it('should create the app', () => {
12 | const fixture = TestBed.createComponent(AppComponent);
13 | const app = fixture.debugElement.componentInstance;
14 | expect(app).toBeTruthy();
15 | });
16 |
17 | it(`should have as title 'web-demo'`, () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.debugElement.componentInstance;
20 | expect(app.title).toEqual('web-demo');
21 | });
22 |
23 | it('should render title', () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | fixture.detectChanges();
26 | const compiled = fixture.debugElement.nativeElement;
27 | expect(compiled.querySelector('h1').textContent).toContain(
28 | 'Welcome to web-demo!'
29 | );
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/apps/demo/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | const name = '$localize';
4 | const lib = 'Locl';
5 |
6 | @Component({
7 | selector: 'locl-root',
8 | styleUrls: ['./app.component.scss'],
9 | templateUrl: './app.component.html'
10 | })
11 | export class AppComponent {
12 | title = $localize`Welcome to the demo of ${name} and ${lib} made for ${name}!`;
13 |
14 | constructor() {
15 | console.log($localize`:@@foo:custom id!`);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/apps/demo/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { AppRoutingModule } from './app-routing.module';
4 |
5 | import { AppComponent } from './app.component';
6 |
7 | @NgModule({
8 | imports: [BrowserModule, AppRoutingModule],
9 | providers: [],
10 | declarations: [AppComponent],
11 | bootstrap: [AppComponent]
12 | })
13 | export class AppModule {}
14 |
--------------------------------------------------------------------------------
/apps/demo/src/app/lazy-loaded/lazy-loaded.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'locl-lazy-loaded',
5 | template: `
6 | Lazy loading works!
7 | `,
8 | styles: []
9 | })
10 | export class LazyLoadedComponent implements OnInit {
11 | constructor() {}
12 |
13 | ngOnInit() {}
14 | }
15 |
--------------------------------------------------------------------------------
/apps/demo/src/app/lazy-loaded/lazy-loaded.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { RouterModule, Routes } from '@angular/router';
4 | import { LazyLoadedComponent } from './lazy-loaded.component';
5 |
6 | const lazyRoutes: Routes = [
7 | {
8 | path: '',
9 | component: LazyLoadedComponent
10 | }
11 | ];
12 |
13 | @NgModule({
14 | imports: [CommonModule, RouterModule.forChild(lazyRoutes)],
15 | declarations: [LazyLoadedComponent]
16 | })
17 | export class LazyLoadedModule {}
18 |
--------------------------------------------------------------------------------
/apps/demo/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/demo/src/assets/.gitkeep
--------------------------------------------------------------------------------
/apps/demo/src/assets/i18n/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": "fr",
3 | "translations": {
4 | "143924624516046945": "Ça fonctionne! {$INTERPOLATION}",
5 | "6586379816467235622": "Bienvenue à la démo de {$PH} et {$PH_1} fait pour {$PH_2}!",
6 | "foo": "id personnalisé"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/demo/src/assets/i18n/lazy.fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "locale": "fr",
3 | "translations": {
4 | "2945825304347416089": "Chargement asynchrone fonctionne !"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/apps/demo/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/apps/demo/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/apps/demo/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/demo/src/favicon.ico
--------------------------------------------------------------------------------
/apps/demo/src/i18n-setup.ts:
--------------------------------------------------------------------------------
1 | // import { computeMsgId } from '@angular/compiler';
2 | // import { loadTranslations } from '@angular/localize';
3 |
4 | /***************************************************************************************************
5 | * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
6 | */
7 | import '@angular/localize/init';
8 | // import * as fr from './assets/i18n/fr.json';
9 | // loadTranslations((fr as any).default.translations);
10 | // console.log('hey',computeMsgId('Welcome to {$INTERPOLATION}!', ''));
11 |
--------------------------------------------------------------------------------
/apps/demo/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WebDemo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/apps/demo/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 | import { getTranslations, ParsedTranslationBundle } from '@locl/core';
4 | import { AppModule } from './app/app.module';
5 |
6 | import { environment } from './environments/environment';
7 |
8 | if (environment.production) {
9 | enableProdMode();
10 | }
11 |
12 | getTranslations('/assets/i18n/fr.json').then(
13 | (data: ParsedTranslationBundle) => {
14 | platformBrowserDynamic()
15 | .bootstrapModule(AppModule)
16 | .catch(err => console.error(err));
17 | }
18 | );
19 |
--------------------------------------------------------------------------------
/apps/demo/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
22 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
23 |
24 | /**
25 | * Web Animations `@angular/platform-browser/animations`
26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28 | */
29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
30 |
31 | /**
32 | * By default, zone.js will patch all possible macroTask and DomEvents
33 | * user can disable parts of macroTask/DomEvents patch by setting following flags
34 | * because those flags need to be set before `zone.js` being loaded, and webpack
35 | * will put import in the top of bundle, so user need to create a separate file
36 | * in this directory (for example: zone-flags.ts), and put the following flags
37 | * into that file, and then add the following code before importing zone.js.
38 | * import './zone-flags.ts';
39 | *
40 | * The flags allowed in zone-flags.ts are listed here.
41 | *
42 | * The following flags will work for all browsers.
43 | *
44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47 | *
48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
50 | *
51 | * (window as any).__Zone_enable_cross_context_check = true;
52 | *
53 | */
54 |
55 | /***************************************************************************************************
56 | * Zone JS is required by default for Angular itself.
57 | */
58 | import 'zone.js/dist/zone'; // Included with Angular CLI.
59 |
60 | /***************************************************************************************************
61 | * APPLICATION IMPORTS
62 | */
63 |
--------------------------------------------------------------------------------
/apps/demo/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/apps/demo/src/test-setup.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular';
2 | import '@angular/localize/init';
3 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "types": [],
6 | "resolveJsonModule": true
7 | },
8 | "files": ["src/main.ts", "src/polyfills.ts"],
9 | "include": ["**/*.ts"],
10 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["node", "jest"],
5 | "resolveJsonModule": true
6 | },
7 | "include": ["**/*.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "files": ["src/test-setup.ts"],
9 | "include": ["**/*.spec.ts", "**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/demo/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {
4 | "directive-selector": [true, "attribute", "locl", "camelCase"],
5 | "component-selector": [true, "element", "locl", "kebab-case"]
6 | },
7 | "linterOptions": {
8 | "exclude": ["!**/*"]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/apps/electron/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | *.js.map
--------------------------------------------------------------------------------
/apps/electron/src/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/electron/src/icons/icon.icns
--------------------------------------------------------------------------------
/apps/electron/src/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/electron/src/icons/icon.ico
--------------------------------------------------------------------------------
/apps/electron/src/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/electron/src/icons/icon.png
--------------------------------------------------------------------------------
/apps/electron/src/index.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, ipcMain, screen } from 'electron';
2 | import * as path from 'path';
3 | import * as url from 'url';
4 |
5 | let serve;
6 | const args = process.argv.slice(1);
7 | serve = args.some(val => val === '--serve');
8 |
9 | let win: Electron.BrowserWindow = null;
10 |
11 | const getFromEnv = parseInt(process.env.ELECTRON_IS_DEV, 10) === 1;
12 | const isEnvSet = 'ELECTRON_IS_DEV' in process.env;
13 | const debugMode = isEnvSet
14 | ? getFromEnv
15 | : process.defaultApp ||
16 | /node_modules[\\/]electron[\\/]/.test(process.execPath);
17 |
18 | /**
19 | * Electron window settings
20 | */
21 | const mainWindowSettings: Electron.BrowserWindowConstructorOptions = {
22 | frame: true,
23 | resizable: true,
24 | focusable: true,
25 | fullscreenable: true,
26 | kiosk: false,
27 | webPreferences: {
28 | devTools: debugMode,
29 | nodeIntegration: true
30 | }
31 | };
32 |
33 | /**
34 | * Hooks for electron main process
35 | */
36 | function initMainListener() {
37 | ipcMain.on('ELECTRON_BRIDGE_HOST', (event, msg) => {
38 | console.log('msg received', msg);
39 | if (msg === 'ping') {
40 | event.sender.send('ELECTRON_BRIDGE_CLIENT', 'pong');
41 | }
42 | });
43 | }
44 |
45 | /**
46 | * Create main window presentation
47 | */
48 | function createWindow() {
49 | const sizes = screen.getPrimaryDisplay().workAreaSize;
50 |
51 | if (debugMode) {
52 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
53 |
54 | mainWindowSettings.width = 800;
55 | mainWindowSettings.height = 600;
56 | } else {
57 | mainWindowSettings.width = sizes.width;
58 | mainWindowSettings.height = sizes.height;
59 | mainWindowSettings.x = 0;
60 | mainWindowSettings.y = 0;
61 | }
62 |
63 | win = new BrowserWindow(mainWindowSettings);
64 |
65 | let launchPath;
66 | if (serve) {
67 | require('electron-reload')(__dirname, {
68 | electron: require(`${__dirname}/../../../node_modules/electron`)
69 | });
70 | launchPath = 'http://localhost:4200';
71 | win.loadURL(launchPath);
72 | } else {
73 | launchPath = url.format({
74 | pathname: path.join(__dirname, 'index.html'),
75 | protocol: 'file:',
76 | slashes: true
77 | });
78 | win.loadURL(launchPath);
79 | }
80 |
81 | console.log('launched electron with:', launchPath);
82 |
83 | win.on('closed', () => {
84 | // Dereference the window object, usually you would store windows
85 | // in an array if your app supports multi windows, this is the time
86 | // when you should delete the corresponding element.
87 | win = null;
88 | });
89 |
90 | initMainListener();
91 |
92 | if (debugMode) {
93 | // Open the DevTools.
94 | win.webContents.openDevTools();
95 | // client.create(applicationRef);
96 | }
97 | }
98 |
99 | try {
100 | app.on('ready', createWindow);
101 |
102 | app.on('window-all-closed', () => {
103 | if (process.platform !== 'darwin') {
104 | app.quit();
105 | }
106 | });
107 |
108 | app.on('activate', () => {
109 | // On macOS it's common to re-create a window in the app when the
110 | // dock icon is clicked and there are no other windows open.
111 | if (win === null) {
112 | createWindow();
113 | }
114 | });
115 | } catch (err) {
116 | }
117 |
--------------------------------------------------------------------------------
/apps/electron/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@locl/app",
3 | "version": "0.0.1",
4 | "description": "Translation app by Locl",
5 | "main": "index.js",
6 | "author": {
7 | "name": "Olivier Combe",
8 | "email": "olivier@locl.app"
9 | },
10 | "homepage": "https://www.locl.app/",
11 | "repository": {
12 | "url": "https://github.com/loclapp/locl"
13 | },
14 | "license": "MIT",
15 | "build": {
16 | "appId": "com.locl.app",
17 | "productName": "App",
18 | "copyright": "Copyright © 2018-2019 locl",
19 | "asar": false,
20 | "npmRebuild": false,
21 | "directories": {
22 | "buildResources": "icons",
23 | "output": "../electronapp-packages"
24 | },
25 | "mac": {
26 | "category": "public.app-category.developer-tools",
27 | "icon": "icon.png"
28 | },
29 | "win": {
30 | "target": "nsis",
31 | "icon": "icon.ico"
32 | },
33 | "linux": {
34 | "icon": "icon.png",
35 | "target": [
36 | "AppImage",
37 | "deb",
38 | "tar.gz"
39 | ],
40 | "synopsis": "App",
41 | "category": "Development"
42 | },
43 | "nsis": {
44 | "createDesktopShortcut": "always",
45 | "installerIcon": "icon.ico",
46 | "artifactName": "App-Setup-${version}.${ext}"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/apps/electron/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "esModuleInterop": true,
9 | "target": "es5",
10 | "typeRoots": [
11 | "../../node_modules/@types"
12 | ],
13 | "lib": [
14 | "es2017",
15 | "es2016",
16 | "es2015",
17 | "dom"
18 | ]
19 | },
20 | "include": [
21 | "src/index.ts"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/apps/web-e2e/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileServerFolder": ".",
3 | "fixturesFolder": "./src/fixtures",
4 | "integrationFolder": "./src/integration",
5 | "pluginsFile": "./src/plugins/index",
6 | "supportFile": false,
7 | "video": true,
8 | "videosFolder": "../../dist/cypress/apps/web-e2e/videos",
9 | "screenshotsFolder": "../../dist/cypress/apps/web-e2e/screenshots",
10 | "chromeWebSecurity": false
11 | }
12 |
--------------------------------------------------------------------------------
/apps/web-e2e/src/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io"
4 | }
5 |
--------------------------------------------------------------------------------
/apps/web-e2e/src/integration/app.spec.ts:
--------------------------------------------------------------------------------
1 | import { getGreeting } from '../support/app.po';
2 |
3 | describe('web', () => {
4 | beforeEach(() => cy.visit('/'));
5 |
6 | it('should display welcome message', () => {
7 | getGreeting().contains('Welcome to web!');
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/apps/web-e2e/src/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor');
15 |
16 | module.exports = (on, config) => {
17 | // `on` is used to hook into various events Cypress emits
18 | // `config` is the resolved Cypress config
19 |
20 | // Preprocess Typescript
21 | on('file:preprocessor', preprocessTypescript(config));
22 | };
23 |
--------------------------------------------------------------------------------
/apps/web-e2e/src/support/app.po.ts:
--------------------------------------------------------------------------------
1 | export const getGreeting = () => cy.get('h1');
2 |
--------------------------------------------------------------------------------
/apps/web-e2e/src/support/commands.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/apps/web-e2e/src/support/index.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands';
--------------------------------------------------------------------------------
/apps/web-e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "sourceMap": false,
5 | "outDir": "../../dist/out-tsc"
6 | },
7 | "include": [
8 | "src/**/*.ts"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/web-e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": [
5 | "cypress",
6 | "node"
7 | ]
8 | },
9 | "include": [
10 | "**/*.ts"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/apps/web-e2e/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": [],
4 | "linterOptions": {
5 | "exclude": ["!**/*"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/web/browserslist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/apps/web/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'web',
3 | preset: '../../jest.config.js',
4 | coverageDirectory: '../../coverage/apps/web',
5 | snapshotSerializers: [
6 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
7 | 'jest-preset-angular/build/AngularSnapshotSerializer.js',
8 | 'jest-preset-angular/build/HTMLCommentSerializer.js'
9 | ]
10 | };
11 |
--------------------------------------------------------------------------------
/apps/web/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/app/app.component.scss
--------------------------------------------------------------------------------
/apps/web/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, TestBed } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 |
4 | describe('AppComponent', () => {
5 | beforeEach(async(() => {
6 | TestBed.configureTestingModule({
7 | declarations: [AppComponent]
8 | }).compileComponents();
9 | }));
10 |
11 | it('should create the app', () => {
12 | const fixture = TestBed.createComponent(AppComponent);
13 | const app = fixture.debugElement.componentInstance;
14 | expect(app).toBeTruthy();
15 | });
16 |
17 | it(`should have as title 'web'`, () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.debugElement.componentInstance;
20 | expect(app.title).toEqual('web');
21 | });
22 |
23 | it('should render title in a h1 tag', () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | fixture.detectChanges();
26 | const compiled = fixture.debugElement.nativeElement;
27 | expect(compiled.querySelector('h1').textContent).toContain(
28 | 'Welcome to web!'
29 | );
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/apps/web/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'locl-root',
5 | styleUrls: ['./app.component.scss'],
6 | template: `
7 |
8 |
Welcome to {{ title }}!
9 |
10 |
11 |
12 | `
13 | })
14 | export class AppComponent {
15 | title = 'web';
16 | }
17 |
--------------------------------------------------------------------------------
/apps/web/src/app/app.electron.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { LoclElectronCoreModule } from '@locl/app-electron';
3 | import { AppComponent } from './app.component';
4 | import { AppModule } from './app.module';
5 |
6 | @NgModule({
7 | imports: [AppModule, LoclElectronCoreModule],
8 | bootstrap: [AppComponent]
9 | })
10 | export class AppElectronModule {
11 | }
12 |
--------------------------------------------------------------------------------
/apps/web/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { CoreModule } from '@locl/app-web';
4 |
5 | import { AppComponent } from './app.component';
6 |
7 | @NgModule({
8 | declarations: [AppComponent],
9 | imports: [CoreModule, BrowserModule],
10 | providers: [],
11 | bootstrap: [AppComponent]
12 | })
13 | export class AppModule {
14 | }
15 |
--------------------------------------------------------------------------------
/apps/web/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/.gitkeep
--------------------------------------------------------------------------------
/apps/web/src/assets/img/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/android-chrome-192x192.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/android-chrome-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/android-chrome-256x256.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/apple-touch-icon.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/bg.jpg
--------------------------------------------------------------------------------
/apps/web/src/assets/img/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/bg.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #ffffff
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/apps/web/src/assets/img/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/favicon-16x16.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/favicon-32x32.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/favicon.ico
--------------------------------------------------------------------------------
/apps/web/src/assets/img/locl-full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/locl-full.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/locl-social.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/locl-social.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/assets/img/mstile-150x150.png
--------------------------------------------------------------------------------
/apps/web/src/assets/img/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/web/src/assets/img/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/assets/img/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/assets/img/android-chrome-256x256.png",
12 | "sizes": "256x256",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/apps/web/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/apps/web/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/apps/web/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/apps/web/src/favicon.ico
--------------------------------------------------------------------------------
/apps/web/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Locl - I18n tools suite for Angular
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/apps/web/src/main.electron.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppElectronModule } from './app/app.electron.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppElectronModule)
13 | .catch(err => console.log(err));
14 |
--------------------------------------------------------------------------------
/apps/web/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule)
13 | .catch(err => console.error(err));
14 |
--------------------------------------------------------------------------------
/apps/web/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
22 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
23 |
24 | /**
25 | * Web Animations `@angular/platform-browser/animations`
26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28 | */
29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
30 |
31 | /**
32 | * By default, zone.js will patch all possible macroTask and DomEvents
33 | * user can disable parts of macroTask/DomEvents patch by setting following flags
34 | * because those flags need to be set before `zone.js` being loaded, and webpack
35 | * will put import in the top of bundle, so user need to create a separate file
36 | * in this directory (for example: zone-flags.ts), and put the following flags
37 | * into that file, and then add the following code before importing zone.js.
38 | * import './zone-flags.ts';
39 | *
40 | * The flags allowed in zone-flags.ts are listed here.
41 | *
42 | * The following flags will work for all browsers.
43 | *
44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47 | *
48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
50 | *
51 | * (window as any).__Zone_enable_cross_context_check = true;
52 | *
53 | */
54 |
55 | /***************************************************************************************************
56 | * Zone JS is required by default for Angular itself.
57 | */
58 | import 'zone.js/dist/zone'; // Included with Angular CLI.
59 |
60 | /***************************************************************************************************
61 | * APPLICATION IMPORTS
62 | */
--------------------------------------------------------------------------------
/apps/web/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @import '~@locl/scss';
3 | @import "~bulma/sass/utilities/_all";
4 | @import "~bulma/sass/base/_all";
5 |
6 | /*** ELEMENTS ***/
7 | //@import "~bulma/sass/elements/box.sass";
8 | @import "~bulma/sass/elements/button.sass";
9 | //@import "~bulma/sass/elements/container.sass";
10 | //@import "~bulma/sass/elements/content.sass";
11 | //@import "~bulma/sass/elements/icon.sass";
12 | //@import "~bulma/sass/elements/image.sass";
13 | //@import "~bulma/sass/elements/notification.sass";
14 | //@import "~bulma/sass/elements/progress.sass";
15 | //@import "~bulma/sass/elements/table.sass";
16 | //@import "~bulma/sass/elements/tag.sass";
17 | //@import "~bulma/sass/elements/title.sass";
18 | //@import "~bulma/sass/elements/other.sass";
19 |
20 | /*** FORMS ***/
21 | @import "~bulma/sass/form/shared.sass";
22 | @import "~bulma/sass/form/input-textarea.sass";
23 | //@import "~bulma/sass/form/checkbox-radio.sass";
24 | //@import "~bulma/sass/form/select.sass";
25 | //@import "~bulma/sass/form/file.sass";
26 | //@import "~bulma/sass/form/tools.sass";
27 |
28 | /*** COMPONENTS ***/
29 | //@import "~bulma/sass/components/breadcrumb.sass";
30 | //@import "~bulma/sass/components/card.sass";
31 | //@import "~bulma/sass/components/dropdown.sass";
32 | //@import "~bulma/sass/components/level.sass";
33 | //@import "~bulma/sass/components/list.sass";
34 | //@import "~bulma/sass/components/media.sass";
35 | //@import "~bulma/sass/components/menu.sass";
36 | //@import "~bulma/sass/components/message.sass";
37 | //@import "~bulma/sass/components/modal.sass";
38 | //@import "~bulma/sass/components/navbar.sass";
39 | //@import "~bulma/sass/components/pagination.sass";
40 | //@import "~bulma/sass/components/panel.sass";
41 | //@import "~bulma/sass/components/tabs.sass";
42 |
43 | /*** GRID ***/
44 | @import "~bulma/sass/grid/columns.sass";
45 | @import "~bulma/sass/grid/tiles.sass";
46 |
47 | /*** LAYOUT ***/
48 | //@import "~bulma/sass/layout/hero.sass";
49 | //@import "~bulma/sass/layout/section.sass";
50 | //@import "~bulma/sass/layout/footer.sass";
--------------------------------------------------------------------------------
/apps/web/src/test-setup.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular';
--------------------------------------------------------------------------------
/apps/web/tsconfig.electron.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": [
4 | "src/main.electron.ts",
5 | "src/polyfills.ts"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "types": []
6 | },
7 | "include": [
8 | "src/main.ts",
9 | "src/polyfills.ts"
10 | ],
11 | "exclude": [
12 | "src/test-setup.ts",
13 | "**/*.spec.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": [
7 | "jest",
8 | "node"
9 | ]
10 | },
11 | "files": [
12 | "src/test-setup.ts"
13 | ],
14 | "include": [
15 | "**/*.spec.ts",
16 | "**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/apps/web/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {
4 | "directive-selector": [true, "attribute", "locl", "camelCase"],
5 | "component-selector": [true, "element", "locl", "kebab-case"]
6 | },
7 | "linterOptions": {
8 | "exclude": ["!**/*"]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | '@commitlint/config-conventional',
4 | '@commitlint/config-lerna-scopes',
5 | ],
6 | plugins: ['commitlint-plugin-body-content'],
7 | rules: {
8 | 'body-content': [2, 'always', ['affects:', ['fix', 'feat', 'perf']]],
9 | 'footer-max-line-length': [0],
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/decorate-angular-cli.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching
3 | * and faster execution of tasks.
4 | *
5 | * It does this by:
6 | *
7 | * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command.
8 | * - Symlinking the ng to nx command, so all commands run through the Nx CLI
9 | * - Updating the package.json postinstall script to give you control over this script
10 | *
11 | * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it.
12 | * Every command you run should work the same when using the Nx CLI, except faster.
13 | *
14 | * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case,
15 | * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked.
16 | * The Nx CLI simply does some optimizations before invoking the Angular CLI.
17 | *
18 | * To opt out of this patch:
19 | * - Replace occurrences of nx with ng in your package.json
20 | * - Remove the script from your postinstall script in your package.json
21 | * - Delete and reinstall your node_modules
22 | */
23 |
24 | const fs = require('fs');
25 | const os = require('os');
26 | const cp = require('child_process');
27 | const isWindows = os.platform() === 'win32';
28 | const { output } = require('@nrwl/workspace');
29 |
30 | /**
31 | * Paths to files being patched
32 | */
33 | const angularCLIInitPath = 'node_modules/@angular/cli/lib/cli/index.js';
34 |
35 | /**
36 | * Patch index.js to warn you if you invoke the undecorated Angular CLI.
37 | */
38 | function patchAngularCLI(initPath) {
39 | const angularCLIInit = fs.readFileSync(initPath, 'utf-8').toString();
40 |
41 | if (!angularCLIInit.includes('NX_CLI_SET')) {
42 | fs.writeFileSync(
43 | initPath,
44 | `
45 | if (!process.env['NX_CLI_SET']) {
46 | const { output } = require('@nrwl/workspace');
47 | output.warn({ title: 'The Angular CLI was invoked instead of the Nx CLI. Use "npx ng [command]" or "nx [command]" instead.' });
48 | }
49 | ${angularCLIInit}
50 | `
51 | );
52 | }
53 | }
54 |
55 | /**
56 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still
57 | * invoke the Nx CLI and get the benefits of computation caching.
58 | */
59 | function symlinkNgCLItoNxCLI() {
60 | try {
61 | const ngPath = './node_modules/.bin/ng';
62 | const nxPath = './node_modules/.bin/nx';
63 | if (isWindows) {
64 | /**
65 | * This is the most reliable way to create symlink-like behavior on Windows.
66 | * Such that it works in all shells and works with npx.
67 | */
68 | ['', '.cmd', '.ps1'].forEach((ext) => {
69 | fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext));
70 | });
71 | } else {
72 | // If unix-based, symlink
73 | cp.execSync(`ln -sf ./nx ${ngPath}`);
74 | }
75 | } catch (e) {
76 | output.error({
77 | title:
78 | 'Unable to create a symlink from the Angular CLI to the Nx CLI:' +
79 | e.message,
80 | });
81 | throw e;
82 | }
83 | }
84 |
85 | try {
86 | symlinkNgCLItoNxCLI();
87 | patchAngularCLI(angularCLIInitPath);
88 | output.log({
89 | title: 'Angular CLI has been decorated to enable computation caching.',
90 | });
91 | } catch (e) {
92 | output.error({
93 | title: 'Decoration of the Angular CLI did not complete successfully',
94 | });
95 | }
96 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
3 | transform: {
4 | '^.+\\.(ts|js|html)$': 'ts-jest'
5 | },
6 | resolver: '@nrwl/jest/plugins/resolver',
7 | moduleFileExtensions: ['ts', 'js', 'html'],
8 | coverageReporters: ['html']
9 | };
10 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "dist/libs/*",
4 | "dist/apps/*"
5 | ],
6 | "version": "independent"
7 | }
8 |
--------------------------------------------------------------------------------
/libs/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loclapp/locl/71001b1321841738cecfeafee304f1fb4e686e87/libs/.gitkeep
--------------------------------------------------------------------------------
/libs/app/src/electron/.xplatframework:
--------------------------------------------------------------------------------
1 | angular
--------------------------------------------------------------------------------
/libs/app/src/electron/index.ts:
--------------------------------------------------------------------------------
1 | export * from './src';
2 |
--------------------------------------------------------------------------------
/libs/app/src/electron/src/core.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, Optional, SkipSelf } from '@angular/core';
2 |
3 | import { throwIfAlreadyLoaded } from '@locl/utils';
4 | import { ELECTRON_PROVIDERS, ElectronService } from './services';
5 |
6 | @NgModule({
7 | providers: [...ELECTRON_PROVIDERS]
8 | })
9 | export class LoclElectronCoreModule {
10 | constructor(
11 | @Optional()
12 | @SkipSelf()
13 | parentModule: LoclElectronCoreModule,
14 | private _electronService: ElectronService
15 | ) {
16 | throwIfAlreadyLoaded(parentModule, 'LoclElectronCoreModule');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/libs/app/src/electron/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './services';
2 | export { LoclElectronCoreModule } from './core.module';
3 |
--------------------------------------------------------------------------------
/libs/app/src/electron/src/services/electron.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { LogService, WindowService } from '@locl/app-web';
3 | import { isElectron } from '@locl/utils';
4 | import * as childProcess from 'child_process';
5 | import { ipcRenderer, IpcRendererEvent } from 'electron';
6 |
7 | @Injectable()
8 | export class ElectronService {
9 | private _ipc: typeof ipcRenderer;
10 | private _childProcess: typeof childProcess;
11 |
12 | constructor(private _log: LogService, private _win: WindowService) {
13 | // Conditional imports
14 | if (isElectron()) {
15 | this._ipc = this._win.require('electron').ipcRenderer;
16 | this._childProcess = this._win.require('child_process');
17 | this._log.debug('ElectronService ready.');
18 | }
19 | }
20 |
21 | public on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
22 | if (!this._ipc) {
23 | return;
24 | }
25 |
26 | this._ipc.on(channel, listener);
27 | }
28 |
29 | public send(channel: string, ...args): void {
30 | if (!this._ipc) {
31 | return;
32 | }
33 |
34 | this._ipc.send(channel, ...args);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/libs/app/src/electron/src/services/index.ts:
--------------------------------------------------------------------------------
1 | import { ElectronService } from './electron.service';
2 |
3 | export const ELECTRON_PROVIDERS: any[] = [ElectronService];
4 |
5 | export * from './electron.service';
6 |
--------------------------------------------------------------------------------
/libs/app/src/test-setup.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular';
--------------------------------------------------------------------------------
/libs/app/src/web/.xplatframework:
--------------------------------------------------------------------------------
1 | angular
--------------------------------------------------------------------------------
/libs/app/src/web/index.ts:
--------------------------------------------------------------------------------
1 | export * from './src';
2 |
--------------------------------------------------------------------------------
/libs/app/src/web/scss/_index.scss:
--------------------------------------------------------------------------------
1 | // shared across all platforms and apps
2 | @import '../../../../../node_modules/@locl/scss/index';
3 | /**
4 | * The following are web specific (used with any web app targets)
5 | */
6 | // web specific variables
7 | @import 'variables';
8 | // web styles (create/import other scss files or define as needed)
9 | @import 'spacing';
10 | @import 'tags';
11 |
--------------------------------------------------------------------------------
/libs/app/src/web/scss/_spacing.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Convenient spacing classes
3 | */
4 | $spacer: 5 !default;
5 | $spacer-x: $spacer !default;
6 | $spacer-y: $spacer !default;
7 | $spacer-alt: 4 !default;
8 | $spacer-x-alt: $spacer-alt !default;
9 | $spacer-y-alt: $spacer-alt !default;
10 | $spacers: (
11 | 0: (
12 | x: 0,
13 | y: 0
14 | ),
15 | 2: (
16 | x: 2,
17 | y: 2
18 | ),
19 | 4: (
20 | x: $spacer-x-alt,
21 | y: $spacer-y-alt
22 | ),
23 | 5: (
24 | x: $spacer-x,
25 | y: $spacer-y
26 | ),
27 | 8: (
28 | x: $spacer-x-alt * 2,
29 | y: $spacer-y-alt * 2
30 | ),
31 | 10: (
32 | x: (
33 | $spacer-x * 2
34 | ),
35 | y: (
36 | $spacer-y * 2
37 | )
38 | ),
39 | 12: (
40 | x: $spacer-x-alt * 3,
41 | y: $spacer-y-alt * 3
42 | ),
43 | 15: (
44 | x: (
45 | $spacer-x * 3
46 | ),
47 | y: (
48 | $spacer-y * 3
49 | )
50 | ),
51 | 16: (
52 | x: $spacer-x-alt * 4,
53 | y: $spacer-y-alt * 4
54 | ),
55 | 20: (
56 | x: (
57 | $spacer-x * 4
58 | ),
59 | y: (
60 | $spacer-y * 4
61 | )
62 | ),
63 | 24: (
64 | x: $spacer-x-alt * 6,
65 | y: $spacer-y-alt * 6
66 | ),
67 | 25: (
68 | x: (
69 | $spacer-x * 5
70 | ),
71 | y: (
72 | $spacer-y * 5
73 | )
74 | ),
75 | 28: (
76 | x: $spacer-x-alt * 7,
77 | y: $spacer-y-alt * 7
78 | ),
79 | 30: (
80 | x: (
81 | $spacer-x * 6
82 | ),
83 | y: (
84 | $spacer-y * 6
85 | )
86 | )
87 | ) !default;
88 |
89 | /**
90 | * Margin and Padding
91 | * The following creates this pattern:
92 | * .m-0{margin:0}.m-t-0{margin-top:0}.m-r-0{margin-right:0}.m-b-0{margin-bottom:0}.m-l-0{margin-left:0}.m-x-0{margin-right:0;margin-left:0}.m-y-0{margin-top:0;margin-bottom:0}
93 | * Same for Padding (using the 'p' abbreviation)
94 | * From 0, 2, 5, 10, 15, 20, 25, 30
95 | **/
96 | // sass-lint:disable-all
97 | @each $prop, $abbrev in (margin: m, padding: p) {
98 | // sass-lint:enable-all
99 | @each $size, $lengths in $spacers {
100 | $length-x: map-get($lengths, x);
101 | $length-y: map-get($lengths, y);
102 |
103 | // sass-lint:disable-all
104 | .#{$abbrev}-#{$size} {
105 | #{$prop}: #{$length-y}px;
106 | } // a = All sides (can just use one length)
107 | // sass-lint:enable-all
108 | .#{$abbrev}-t-#{$size} {
109 | #{$prop}-top: #{$length-y}px;
110 | }
111 | .#{$abbrev}-r-#{$size} {
112 | #{$prop}-right: #{$length-x}px;
113 | }
114 | .#{$abbrev}-b-#{$size} {
115 | #{$prop}-bottom: #{$length-y}px;
116 | }
117 | .#{$abbrev}-l-#{$size} {
118 | #{$prop}-left: #{$length-x}px;
119 | }
120 |
121 | // Axes
122 | .#{$abbrev}-x-#{$size} {
123 | #{$prop}-right: #{$length-x}px;
124 | #{$prop}-left: #{$length-x}px;
125 | }
126 |
127 | .#{$abbrev}-y-#{$size} {
128 | #{$prop}-top: #{$length-y}px;
129 | #{$prop}-bottom: #{$length-y}px;
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/libs/app/src/web/scss/_tags.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Element tag overrides
3 | */
4 | body {
5 | padding: 0;
6 | margin: 0;
7 | }
8 |
--------------------------------------------------------------------------------
/libs/app/src/web/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | // web specific variables here...
2 |
--------------------------------------------------------------------------------
/libs/app/src/web/scss/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@locl/web-scss",
3 | "version": "1.0.0"
4 | }
5 |
--------------------------------------------------------------------------------
/libs/app/src/web/src/core.module.spec.ts:
--------------------------------------------------------------------------------
1 | import { CoreModule } from './core.module';
2 |
3 | describe('LoclCoreModule', () => {
4 | it('should work', () => {
5 | expect(new CoreModule(null)).toBeDefined();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/libs/app/src/web/src/core.module.ts:
--------------------------------------------------------------------------------
1 | import { APP_BASE_HREF } from '@angular/common';
2 | import { HttpClientModule } from '@angular/common/http';
3 | import { NgModule, Optional, SkipSelf } from '@angular/core';
4 | import { BrowserModule } from '@angular/platform-browser';
5 | import { throwIfAlreadyLoaded } from '@locl/utils';
6 | // libs
7 | import {
8 | CORE_PROVIDERS,
9 | PlatformLanguageToken,
10 | PlatformWindowToken,
11 | WindowPlatformService
12 | } from './services';
13 |
14 | // bring in custom web services here...
15 |
16 | // factories
17 | export function winFactory() {
18 | return window;
19 | }
20 |
21 | export function platformLangFactory() {
22 | const browserLang = window.navigator.language || 'en'; // fallback English
23 | // browser language has 2 codes, ex: 'en-US'
24 | return browserLang.split('-')[0];
25 | }
26 |
27 | export const BASE_PROVIDERS: any[] = [
28 | ...CORE_PROVIDERS,
29 | {
30 | provide: APP_BASE_HREF,
31 | useValue: '/'
32 | }
33 | ];
34 |
35 | @NgModule({
36 | imports: [
37 | BrowserModule,
38 | HttpClientModule
39 | ],
40 | providers: [
41 | ...BASE_PROVIDERS, {
42 | provide: PlatformLanguageToken,
43 | useFactory: platformLangFactory
44 | }, {
45 | provide: PlatformWindowToken,
46 | useFactory: winFactory
47 | }, {
48 | provide: WindowPlatformService,
49 | useFactory: winFactory
50 | }
51 | ]
52 | })
53 | export class CoreModule {
54 | constructor(
55 | @Optional()
56 | @SkipSelf()
57 | parentModule: CoreModule
58 | ) {
59 | throwIfAlreadyLoaded(parentModule, 'CoreModule');
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/libs/app/src/web/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './services';
2 | export { CoreModule } from './core.module';
3 |
--------------------------------------------------------------------------------
/libs/app/src/web/src/services/index.ts:
--------------------------------------------------------------------------------
1 | import { LogService } from './log.service';
2 | import { WindowService } from './window.service';
3 |
4 | export const CORE_PROVIDERS: any[] = [LogService, WindowService];
5 |
6 | export * from './log.service';
7 | export * from './window.service';
8 | export * from './tokens';
9 |
--------------------------------------------------------------------------------
/libs/app/src/web/src/services/log.service.ts:
--------------------------------------------------------------------------------
1 | // angular
2 | import { Injectable } from '@angular/core';
3 |
4 | export interface IDebug {
5 | LEVEL_1: boolean;
6 | LEVEL_2: boolean;
7 | LEVEL_3: boolean;
8 | LEVEL_4: boolean;
9 | LEVEL_5: boolean;
10 |
11 | [key: string]: boolean;
12 | }
13 |
14 | @Injectable()
15 | export class LogService {
16 | public static DEBUG: IDebug = {
17 | LEVEL_1: false, // .warn only
18 | LEVEL_2: false, // .error only
19 | LEVEL_3: false, // .log + all the above
20 | LEVEL_4: false, // .log + all the above + info
21 | LEVEL_5: false // just info (excluding all else)
22 | };
23 |
24 | // info (extra messages like analytics)
25 | // use LEVEL_5 to see only these
26 | public info(...msg: Array) {
27 | if (LogService.DEBUG.LEVEL_5 || LogService.DEBUG.LEVEL_4) {
28 | // extra messages
29 | console.info(msg);
30 | }
31 | }
32 |
33 | // debug (standard output)
34 | public debug(...msg: Array) {
35 | if (LogService.DEBUG.LEVEL_4 || LogService.DEBUG.LEVEL_3) {
36 | // console.debug does not work on {N} apps... use `log`
37 | console.log(msg);
38 | }
39 | }
40 |
41 | // error
42 | public error(...err: Array) {
43 | if (
44 | LogService.DEBUG.LEVEL_4 ||
45 | LogService.DEBUG.LEVEL_3 ||
46 | LogService.DEBUG.LEVEL_2
47 | ) {
48 | console.error(err);
49 | }
50 | }
51 |
52 | // warn
53 | public warn(...warn: Array) {
54 | if (
55 | LogService.DEBUG.LEVEL_4 ||
56 | LogService.DEBUG.LEVEL_3 ||
57 | LogService.DEBUG.LEVEL_1
58 | ) {
59 | console.warn(warn);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/libs/app/src/web/src/services/tokens.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | /**
4 | * Various InjectionTokens shared across all platforms
5 | * Always suffix with 'Token' for clarity and consistency
6 | */
7 |
8 | export const PlatformLanguageToken = new InjectionToken(
9 | 'PlatformLanguage'
10 | );
11 |
12 | export const PlatformWindowToken = new InjectionToken(
13 | 'PlatformWindow'
14 | );
15 |
--------------------------------------------------------------------------------
/libs/app/src/web/src/services/window.service.ts:
--------------------------------------------------------------------------------
1 | // angular
2 | import { Injectable } from '@angular/core';
3 | // app
4 | import { isObject } from '@locl/utils';
5 |
6 | @Injectable()
7 | export class WindowPlatformService {
8 | public navigator: any = {};
9 | public location: any = {};
10 | public localStorage: any;
11 | public process: any;
12 | public require: any;
13 |
14 | public alert(msg: any) {}
15 |
16 | public confirm(msg: any) {}
17 |
18 | public setTimeout(handler: (...args: any[]) => void, timeout?: number) {
19 | return 0;
20 | }
21 |
22 | public clearTimeout(timeoutId: number) {}
23 |
24 | public setInterval(
25 | handler: (...args: any[]) => void,
26 | ms?: number,
27 | ...args: any[]
28 | ) {
29 | return 0;
30 | }
31 |
32 | public clearInterval(intervalId: number) {}
33 |
34 | // ...You can expand support for more window methods as you need them here...
35 | }
36 |
37 | @Injectable()
38 | export class WindowService {
39 | constructor(private _platformWindow: WindowPlatformService) {
40 | console.log('windows service');
41 | }
42 |
43 | public get navigator() {
44 | return this._platformWindow.navigator;
45 | }
46 |
47 | public get location() {
48 | return this._platformWindow.location;
49 | }
50 |
51 | public get process() {
52 | return this._platformWindow.process;
53 | }
54 |
55 | public get require() {
56 | return this._platformWindow.require;
57 | }
58 |
59 | public alert(msg: any): Promise {
60 | return new Promise((resolve, reject) => {
61 | const result: any = this._platformWindow.alert(msg);
62 | if (isObject(result) && result.then) {
63 | // console.log('WindowService -- using result.then promise');
64 | result.then(resolve, reject);
65 | } else {
66 | resolve();
67 | }
68 | });
69 | }
70 |
71 | public confirm(
72 | msg: any,
73 | action?: Function /* used for fancyalerts on mobile*/
74 | ): Promise {
75 | return new Promise((resolve, reject) => {
76 | const result: any = (this._platformWindow).confirm(msg, undefined);
77 | if (isObject(result) && result.then) {
78 | result.then(resolve, reject);
79 | } else if (result) {
80 | resolve();
81 | } else {
82 | reject();
83 | }
84 | });
85 | }
86 |
87 | public setTimeout(
88 | handler: (...args: any[]) => void,
89 | timeout?: number
90 | ): number {
91 | return this._platformWindow.setTimeout(handler, timeout);
92 | }
93 |
94 | public clearTimeout(timeoutId: number): void {
95 | return this._platformWindow.clearTimeout(timeoutId);
96 | }
97 |
98 | public setInterval(
99 | handler: (...args: any[]) => void,
100 | ms?: number,
101 | ...args: any[]
102 | ): number {
103 | return this._platformWindow.setInterval(handler, ms, args);
104 | }
105 |
106 | public clearInterval(intervalId: number): void {
107 | return this._platformWindow.clearInterval(intervalId);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/libs/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": [
5 | "node",
6 | "jest"
7 | ]
8 | },
9 | "include": [
10 | "**/*.ts"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/libs/app/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "target": "es2015",
6 | "declaration": true,
7 | "inlineSources": true,
8 | "types": [],
9 | "lib": [
10 | "dom",
11 | "es2018"
12 | ]
13 | },
14 | "angularCompilerOptions": {
15 | "annotateForClosureCompiler": true,
16 | "skipTemplateCodegen": true,
17 | "strictMetadataEmit": true,
18 | "fullTemplateTypeCheck": true,
19 | "strictInjectionParameters": true,
20 | "enableResourceInlining": true
21 | },
22 | "exclude": [
23 | "src/test.ts",
24 | "**/*.spec.ts"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/libs/app/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": [
7 | "jest",
8 | "node"
9 | ]
10 | },
11 | "files": [
12 | "src/test-setup.ts"
13 | ],
14 | "include": [
15 | "**/*.spec.ts",
16 | "**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/libs/app/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {
4 | "directive-selector": [true, "attribute", "locl", "camelCase"],
5 | "component-selector": [true, "element", "locl", "kebab-case"]
6 | },
7 | "linterOptions": {
8 | "exclude": ["!**/*"]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/libs/cli/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Locl CLI
4 |
5 | Dev tools to help you with `$localize` and Angular i18n.
6 |
7 | ## Installation
8 |
9 | Install the cli tools with npm:
10 |
11 | ```sh
12 | npm install @locl/cli --save-dev
13 | ```
14 |
15 | ## Usage
16 |
17 | You can get a full list of commands with `npx locl --help`.
18 |
19 | ### Extraction
20 |
21 | You can use `locl extract` to extract translations from your ivy application:
22 |
23 | ```
24 | npx locl extract -s="dist/apps/demo/*.js" -f=json -o="src/assets/i18n/en.json"
25 | ```
26 |
27 | The extraction tool will find any call to `$localize` within your bundle files (in code and in templates),
28 | but there is a limitation inherent to the way Angular files are generated by the compiler:
29 | **you need to build your application with the AOT mode to generate template translations using `$localize`.**
30 | Always use `--prod` or `--aot` when you build your application. Also make sure to not build for a specific language.
31 |
32 | #### Options:
33 |
34 | - `--source` (`-s`): A glob pattern indicating what files to search for translations, e.g. `./dist/**/*.js`. This can be absolute or relative to the current working directory.
35 | - `--format` (`-f`): the format of the translation file to generate. Either `xlf`, `xlf2`, `xmb` or `json`.
36 | - `--outputPath` (`-o`): A path to where the translation file will be written. This can be absolute or relative to the current working directory. If the given path is a file, it will aggregate the translations of all the source files into one translation file. Otherwise it will generate one translation file per file parsed.
37 | - `--locale` (`-l`): The locale for the extracted file, "en" by default.
38 |
39 | ### Conversion
40 |
41 | You can use `locl convert` to convert translations from one format to another:
42 |
43 | ```
44 | npx locl convert -s="fr.xlf" -f=json -o="src/assets/i18n/fr.json"
45 | ```
46 |
47 | The conversion tool will read all translation files from the source glob and generate a file of the specified format at the specified output path.
48 |
49 | Since the source glob can be used to take multiple files as input, it is possible to use this tool to aggregate multiple translation files into one:
50 |
51 | ```
52 | npx locl convert -s="src/assets/i18n/*.xlf" -f=json -o="src/assets/i18n/fr.json"
53 | ```
54 |
55 | It is recommended to use this tool to convert your files to json if you want to lazy load the translations at bootstrap,
56 | since it is the only format that is supported by the existing loaders, and it is the most optimized one in terms of size.
57 |
58 | #### Options:
59 |
60 | - `--source` (`-s`): A glob pattern indicating what files to convert, e.g. `./assets/**/*.xlf`. This can be absolute or relative to the current working directory. Only translation files are supported (json, xtb & xlf but not xmb).
61 | - `--format` (`-f`): The format of the translation files to generate. Either `xlf`, `xlf2`, `xtb` or `json`.
62 | - `--outputPath` (`-o`): A path to where the translation file will be written. This can be absolute or relative to the current working directory.
63 |
--------------------------------------------------------------------------------
/libs/cli/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/libs/cli",
4 | "lib": {
5 | "entryFile": "src/index.ts"
6 | },
7 | "whitelistedNonPeerDependencies": [
8 | "@babel/core",
9 | "chalk",
10 | "find-up",
11 | "glob",
12 | "yargs"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/libs/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@locl/cli",
3 | "version": "1.0.0",
4 | "license": "AGPL-3.0-or-later",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/loclapp/locl/"
8 | },
9 | "bugs": {
10 | "url": "https://github.com/loclapp/locl/issues"
11 | },
12 | "homepage": "https://www.locl.app/",
13 | "bin": {
14 | "locl": "./src/locl"
15 | },
16 | "scripts": {
17 | "build": "ng build cli && npm run build:apiAndLib && npm run build:copy",
18 | "build:apiAndLib": "tsc -p src/tsconfig.json",
19 | "build:copy": "cpx src/locl ../../dist/libs/cli/src",
20 | "pretest": "npm run build",
21 | "test": "jasmine ../../dist/libs/cli/**/*spec.js"
22 | },
23 | "dependencies": {
24 | "@babel/core": "^7.8.6",
25 | "chalk": "^4.1.0",
26 | "find-up": "^4.1.0",
27 | "glob": "^7.1.2",
28 | "yargs": "^13.1.0"
29 | },
30 | "devDependencies": {
31 | "@types/babel__core": "^7.1.6",
32 | "@types/yargs": "^13.0.3",
33 | "@types/glob": "^7.1.1"
34 | },
35 | "peerDependencies": {
36 | "@angular/localize": "^10.0.0",
37 | "@angular/compiler": "^10.0.0",
38 | "@angular/core": "^10.0.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/libs/cli/src/cmds/common/diagnostics.ts:
--------------------------------------------------------------------------------
1 | import * as chalk from 'chalk';
2 |
3 | export declare type DiagnosticHandlingStrategy = 'error' | 'warning' | 'ignore';
4 |
5 | /**
6 | * This class is used to collect and then report warnings and errors that occur during the execution
7 | * of the tools.
8 | */
9 | export class Diagnostics {
10 | readonly messages = [];
11 |
12 | get hasErrors() {
13 | return this.messages.some((m) => m.type === 'error');
14 | }
15 |
16 | add(type: DiagnosticHandlingStrategy, message: string) {
17 | if (type !== 'ignore') {
18 | this.messages.push({ type, message });
19 | }
20 | }
21 |
22 | merge(other: Diagnostics) {
23 | this.messages.push(...other.messages);
24 | }
25 |
26 | log(message: string) {
27 | this.messages.push({ type: '', message });
28 | }
29 |
30 | warn(message: string) {
31 | this.messages.push({ type: 'warning', message });
32 | }
33 |
34 | error(message: string) {
35 | this.messages.push({ type: 'error', message });
36 | }
37 |
38 | formatDiagnostics(message: string): string {
39 | const errors = this.messages!.filter((d) => d.type === 'error').map(
40 | (d) => ' - ' + d.message
41 | );
42 | const warnings = this.messages!.filter((d) => d.type === 'warning').map(
43 | (d) => ' - ' + d.message
44 | );
45 | if (errors.length) {
46 | message += '\nERRORS:\n' + errors.join('\n');
47 | }
48 | if (warnings.length) {
49 | message += '\nWARNINGS:\n' + warnings.join('\n');
50 | }
51 | return message;
52 | }
53 |
54 | logMessages() {
55 | while (this.messages.length) {
56 | const m = this.messages.shift();
57 | switch (m.type) {
58 | case 'warning':
59 | console.warn(chalk.yellow(`Warning: ${m.message}`));
60 | break;
61 | case 'error':
62 | console.error(chalk.red(`Error: ${m.message}`));
63 | break;
64 | default:
65 | console.log(chalk.blue(`${m.message}`));
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/libs/cli/src/cmds/common/file_utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright Google Inc. All Rights Reserved.
4 | *
5 | * Use of this source code is governed by an MIT-style license that can be
6 | * found in the LICENSE file at https://angular.io/license
7 | */
8 | import * as fs from 'fs';
9 | import * as path from 'path';
10 |
11 | export class FileUtils {
12 | static readFile(absolutePath: string): string {
13 | return fs.readFileSync(absolutePath, 'utf8');
14 | }
15 |
16 | static readFileBuffer(absolutePath: string): Buffer {
17 | return fs.readFileSync(absolutePath);
18 | }
19 |
20 | static writeFile(absolutePath: string, contents: string | Buffer) {
21 | FileUtils.ensureDir(path.dirname(absolutePath));
22 | fs.writeFileSync(absolutePath, contents);
23 | }
24 |
25 | static ensureDir(absolutePath: string): void {
26 | const parents: string[] = [];
27 | while (!FileUtils.isRoot(absolutePath) && !fs.existsSync(absolutePath)) {
28 | parents.push(absolutePath);
29 | absolutePath = path.dirname(absolutePath);
30 | }
31 | while (parents.length) {
32 | fs.mkdirSync(parents.pop()!);
33 | }
34 | }
35 |
36 | static remove(p: string): void {
37 | const stat = fs.statSync(p);
38 | if (stat.isFile()) {
39 | fs.unlinkSync(p);
40 | } else if (stat.isDirectory()) {
41 | fs.readdirSync(p).forEach(child => {
42 | const absChild = path.resolve(p, child);
43 | FileUtils.remove(absChild);
44 | });
45 | fs.rmdirSync(p);
46 | }
47 | }
48 |
49 | static isRoot(absolutePath: string): boolean {
50 | return path.dirname(absolutePath) === absolutePath;
51 | }
52 |
53 | static dedup(files: string[], pattern: RegExp, replaceValue = ''): string[] {
54 | const filesSet = files.map(file => file.replace(pattern, replaceValue));
55 | const dedup = [];
56 | const indexes = [];
57 | filesSet.forEach((file: string, index: number) => {
58 | if (dedup.indexOf(file) === -1) {
59 | dedup.push(file);
60 | indexes.push(index);
61 | }
62 | });
63 | return indexes.map(index => files[index]);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/libs/cli/src/cmds/convert.ts:
--------------------------------------------------------------------------------
1 | import { convertFiles, TranslationFormat } from './convert/convert';
2 | import { resolve } from 'path';
3 | import { Diagnostics } from './common/diagnostics';
4 |
5 | export const command = 'convert';
6 | export const describe = 'Convert translation files from one format to another';
7 | export const builder = {
8 | s: {
9 | alias: 'source',
10 | required: true,
11 | describe:
12 | 'A glob pattern indicating what files to convert, e.g. `./assets/**/*.xlf`. This can be absolute or relative to the current working directory. Only translation files are supported (json, xtb & xlf but not xmb).',
13 | },
14 | f: {
15 | alias: 'format',
16 | required: true,
17 | describe: 'The format of the translation files to generate.',
18 | choices: ['json', 'xlf', 'xtb', 'xlf2'],
19 | default: 'json',
20 | },
21 | o: {
22 | alias: 'outputPath',
23 | required: true,
24 | describe:
25 | 'A path to where the converted files will be written. This can be absolute or relative to the current working directory.',
26 | },
27 | };
28 |
29 | export const handler = function (options) {
30 | const diagnostics = new Diagnostics();
31 | convertFiles({
32 | sourceGlob: resolve(options['s']),
33 | format: options['f'] as TranslationFormat,
34 | outputPath: resolve(options['o']),
35 | diagnostics,
36 | });
37 | diagnostics.logMessages();
38 | process.exit(diagnostics.hasErrors ? 1 : 0);
39 | };
40 |
--------------------------------------------------------------------------------
/libs/cli/src/cmds/convert/convert.ts:
--------------------------------------------------------------------------------
1 | import { NodeJSFileSystem } from '@angular/compiler-cli/src/ngtsc/file_system';
2 | import { TranslationLoader } from '@angular/localize/src/tools/src/translate/translation_files/translation_loader';
3 | import { Diagnostics } from '../common/diagnostics';
4 | import { FileUtils } from '../common/file_utils';
5 | import { getTranslationSerializer, translationToMessage } from '../common/util';
6 | import { SimpleJsonTranslationParser } from '../convert/translation_parsers/simple_json_translation_parser';
7 | import { Xliff1TranslationParser } from '../convert/translation_parsers/xliff1_translation_parser';
8 | import { Xliff2TranslationParser } from '../convert/translation_parsers/xliff2_translation_parser';
9 | import { XtbTranslationParser } from '../convert/translation_parsers/xtb_translation_parser';
10 | import * as glob from 'glob';
11 | import { posix } from 'path';
12 |
13 | export type TranslationFormat =
14 | | 'json'
15 | | 'xtb'
16 | | 'xliff1'
17 | | 'xliff2'
18 | | 'xlf'
19 | | 'xlf2';
20 |
21 | export interface ConvertFilesOptions {
22 | sourceGlob: string;
23 | format: TranslationFormat;
24 | outputPath: string;
25 | diagnostics: Diagnostics;
26 | }
27 |
28 | export function convertFiles({
29 | sourceGlob: source,
30 | format,
31 | outputPath: output,
32 | diagnostics,
33 | }: ConvertFilesOptions) {
34 | console.log(
35 | `Converting files from source "${source}" to format "${format}" and output "${output}"`
36 | );
37 | const filesToProcess = glob.sync(source, {
38 | absolute: true,
39 | nodir: true,
40 | });
41 |
42 | const fs = new NodeJSFileSystem();
43 |
44 | const translationLoader = new TranslationLoader(
45 | fs,
46 | [
47 | new Xliff2TranslationParser(diagnostics),
48 | new Xliff1TranslationParser(diagnostics),
49 | new XtbTranslationParser(diagnostics),
50 | new SimpleJsonTranslationParser(diagnostics),
51 | ],
52 | 'ignore',
53 | diagnostics
54 | );
55 |
56 | // Convert all the `translationFilePaths` elements to arrays.
57 | const translationFilePathsArrays = filesToProcess.map((filePaths) =>
58 | Array.isArray(filePaths)
59 | ? filePaths.map((p) => fs.resolve(p))
60 | : [fs.resolve(filePaths)]
61 | );
62 |
63 | const translationBundles = translationLoader.loadBundles(
64 | translationFilePathsArrays,
65 | []
66 | );
67 | if (translationBundles.length) {
68 | const messages = [];
69 | translationBundles.forEach((translationBundle) => {
70 | const translations = translationBundle.translations;
71 | messages.push(
72 | ...Object.keys(translations).map((id) =>
73 | translationToMessage(id, translations[id])
74 | )
75 | );
76 | });
77 |
78 | const serializer = getTranslationSerializer(format);
79 | const translationFile = serializer.renderFile(
80 | messages,
81 | translationBundles[0].locale,
82 | true
83 | );
84 |
85 | FileUtils.writeFile(posix.normalize(output), translationFile);
86 | } else {
87 | diagnostics.error(`Couldn't find any file to convert.`);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/libs/cli/src/cmds/convert/message_serialization/message_renderer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright Google Inc. All Rights Reserved.
4 | *
5 | * Use of this source code is governed by an MIT-style license that can be
6 | * found in the LICENSE file at https://angular.io/license
7 | */
8 |
9 | export interface MessageRenderer {
10 | message: T;
11 | startRender(): void;
12 | endRender(): void;
13 | text(text: string): void;
14 | description(text: string): void;
15 | meaning(text: string): void;
16 | placeholder(name: string, body: string | undefined): void;
17 | startPlaceholder(name: string): void;
18 | closePlaceholder(name: string): void;
19 | startContainer(): void;
20 | closeContainer(): void;
21 | startIcu(): void;
22 | endIcu(): void;
23 | }
24 |
25 | export function stripInterpolationMarkers(interpolation: string): string {
26 | return interpolation.replace(/^\{\{/, '').replace(/}}$/, '');
27 | }
28 |
--------------------------------------------------------------------------------
/libs/cli/src/cmds/convert/message_serialization/message_serializer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright Google Inc. All Rights Reserved.
4 | *
5 | * Use of this source code is governed by an MIT-style license that can be
6 | * found in the LICENSE file at https://angular.io/license
7 | */
8 | import {
9 | Element,
10 | Expansion,
11 | ExpansionCase,
12 | Node,
13 | Text,
14 | visitAll
15 | } from '@angular/compiler';
16 | import { BaseVisitor } from '@angular/localize/src/tools/src/translate/translation_files/base_visitor';
17 |
18 | import { TranslationParseError } from '../translation_parsers/translation_parse_error';
19 | import {
20 | getAttrOrThrow,
21 | getAttribute
22 | } from '../translation_parsers/translation_utils';
23 |
24 | import { MessageRenderer } from './message_renderer';
25 |
26 | interface MessageSerializerConfig {
27 | inlineElements: string[];
28 | placeholder?: {
29 | elementName: string;
30 | nameAttribute: string;
31 | bodyAttribute?: string;
32 | };
33 | placeholderContainer?: {
34 | elementName: string;
35 | startAttribute: string;
36 | endAttribute: string;
37 | };
38 | }
39 |
40 | /**
41 | * This visitor will walk over a set of XML nodes, which represent an i18n message, and serialize
42 | * them into a message object of type `T`.
43 | * The type of the serialized message is controlled by the
44 | */
45 | export class MessageSerializer extends BaseVisitor {
46 | constructor(
47 | private renderer: MessageRenderer,
48 | private config: MessageSerializerConfig
49 | ) {
50 | super();
51 | }
52 |
53 | serialize(nodes: Node[]): T {
54 | this.renderer.startRender();
55 | visitAll(this, nodes);
56 | this.renderer.endRender();
57 | return this.renderer.message;
58 | }
59 |
60 | visitElement(element: Element): void {
61 | if (
62 | this.config.placeholder &&
63 | element.name === this.config.placeholder.elementName
64 | ) {
65 | const name = getAttrOrThrow(
66 | element,
67 | this.config.placeholder.nameAttribute
68 | );
69 | const body =
70 | this.config.placeholder.bodyAttribute &&
71 | getAttribute(element, this.config.placeholder.bodyAttribute);
72 | this.visitPlaceholder(name, body);
73 | } else if (
74 | this.config.placeholderContainer &&
75 | element.name === this.config.placeholderContainer.elementName
76 | ) {
77 | const start = getAttrOrThrow(
78 | element,
79 | this.config.placeholderContainer.startAttribute
80 | );
81 | const end = getAttrOrThrow(
82 | element,
83 | this.config.placeholderContainer.endAttribute
84 | );
85 | this.visitPlaceholderContainer(start, element.children, end);
86 | } else if (this.config.inlineElements.indexOf(element.name) !== -1) {
87 | visitAll(this, element.children);
88 | } else {
89 | throw new TranslationParseError(
90 | element.sourceSpan,
91 | `Invalid element found in message.`
92 | );
93 | }
94 | }
95 |
96 | visitText(text: Text): void {
97 | this.renderer.text(text.value);
98 | }
99 |
100 | visitExpansion(expansion: Expansion): void {
101 | this.renderer.startIcu();
102 | this.renderer.text(`${expansion.switchValue}, ${expansion.type},`);
103 | visitAll(this, expansion.cases);
104 | this.renderer.endIcu();
105 | }
106 |
107 | visitExpansionCase(expansionCase: ExpansionCase): void {
108 | this.renderer.text(` ${expansionCase.value} {`);
109 | this.renderer.startContainer();
110 | visitAll(this, expansionCase.expression);
111 | this.renderer.closeContainer();
112 | this.renderer.text(`}`);
113 | }
114 |
115 | visitContainedNodes(nodes: Node[]): void {
116 | const length = nodes.length;
117 | let index = 0;
118 | while (index < length) {
119 | if (!this.isPlaceholderContainer(nodes[index])) {
120 | const startOfContainedNodes = index;
121 | while (index < length - 1) {
122 | index++;
123 | if (this.isPlaceholderContainer(nodes[index])) {
124 | break;
125 | }
126 | }
127 | if (index - startOfContainedNodes > 1) {
128 | // Only create a container if there are two or more contained Nodes in a row
129 | this.renderer.startContainer();
130 | visitAll(this, nodes.slice(startOfContainedNodes, index - 1));
131 | this.renderer.closeContainer();
132 | }
133 | }
134 | if (index < length) {
135 | nodes[index].visit(this, undefined);
136 | }
137 | index++;
138 | }
139 | }
140 |
141 | visitPlaceholder(name: string, body: string | undefined): void {
142 | this.renderer.placeholder(name, body);
143 | }
144 |
145 | visitPlaceholderContainer(
146 | startName: string,
147 | children: Node[],
148 | closeName: string
149 | ): void {
150 | this.renderer.startPlaceholder(startName);
151 | this.visitContainedNodes(children);
152 | this.renderer.closePlaceholder(closeName);
153 | }
154 |
155 | private isPlaceholderContainer(node: Node): boolean {
156 | return (
157 | node instanceof Element &&
158 | node.name === this.config.placeholderContainer!.elementName
159 | );
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/libs/cli/src/cmds/convert/message_serialization/target_message_renderer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright Google Inc. All Rights Reserved.
4 | *
5 | * Use of this source code is governed by an MIT-style license that can be
6 | * found in the LICENSE file at https://angular.io/license
7 | */
8 | import { MessageRenderer } from './message_renderer';
9 | import { ParsedTranslation } from '../translations';
10 |
11 | /**
12 | * Create a `ParsedTranslation` from a set of `messageParts` and `placeholderNames`.
13 | *
14 | * @param messageParts The message parts to appear in the ParsedTranslation.
15 | * @param placeholderNames The names of the placeholders to intersperse between the `messageParts`.
16 | */
17 | export function makeParsedTranslation(
18 | messageParts: string[],
19 | placeholderNames: string[] = [],
20 | description?: string,
21 | meaning?: string
22 | ): ParsedTranslation {
23 | return {
24 | messageParts: makeTemplateObject(messageParts, messageParts),
25 | placeholderNames,
26 | description,
27 | meaning,
28 | text: ''
29 | };
30 | }
31 |
32 | /**
33 | * Create the specialized array that is passed to tagged-string tag functions.
34 | *
35 | * @param cooked The message parts with their escape codes processed.
36 | * @param raw The message parts with their escaped codes as-is.
37 | */
38 | export function makeTemplateObject(
39 | cooked: string[],
40 | raw: string[]
41 | ): TemplateStringsArray {
42 | Object.defineProperty(cooked, 'raw', { value: raw });
43 | return cooked as any;
44 | }
45 |
46 | /**
47 | * A message renderer that outputs `ParsedTranslation` objects.
48 | */
49 | export class TargetMessageRenderer
50 | implements MessageRenderer {
51 | private current: MessageInfo = {
52 | messageParts: [],
53 | placeholderNames: [],
54 | text: ''
55 | };
56 | private icuDepth = 0;
57 |
58 | get message(): ParsedTranslation {
59 | const {
60 | messageParts,
61 | placeholderNames,
62 | description,
63 | meaning
64 | } = this.current;
65 | return makeParsedTranslation(
66 | messageParts,
67 | placeholderNames,
68 | description,
69 | meaning
70 | );
71 | }
72 | startRender(): void {}
73 | endRender(): void {
74 | this.storeMessagePart();
75 | }
76 | text(text: string): void {
77 | this.current.text += text;
78 | }
79 | description(description: string): void {
80 | this.current.description = description;
81 | }
82 | meaning(meaning: string): void {
83 | this.current.text = meaning;
84 | }
85 | placeholder(name: string, body: string | undefined): void {
86 | this.renderPlaceholder(name);
87 | }
88 | startPlaceholder(name: string): void {
89 | this.renderPlaceholder(name);
90 | }
91 | closePlaceholder(name: string): void {
92 | this.renderPlaceholder(name);
93 | }
94 | startContainer(): void {}
95 | closeContainer(): void {}
96 | startIcu(): void {
97 | this.icuDepth++;
98 | this.text('{');
99 | }
100 | endIcu(): void {
101 | this.icuDepth--;
102 | this.text('}');
103 | }
104 | private normalizePlaceholderName(name: string) {
105 | return name.replace(/-/g, '_');
106 | }
107 | private renderPlaceholder(name: string) {
108 | name = this.normalizePlaceholderName(name);
109 | if (this.icuDepth > 0) {
110 | this.text(`{${name}}`);
111 | } else {
112 | this.storeMessagePart();
113 | this.current.placeholderNames.push(name);
114 | }
115 | }
116 | private storeMessagePart() {
117 | this.current.messageParts.push(this.current.text);
118 | this.current.text = '';
119 | }
120 | }
121 |
122 | interface MessageInfo {
123 | messageParts: string[];
124 | placeholderNames: string[];
125 | text: string;
126 | description?: string;
127 | meaning?: string;
128 | }
129 |
--------------------------------------------------------------------------------
/libs/cli/src/cmds/convert/translation_parsers/simple_json_translation_parser.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright Google Inc. All Rights Reserved.
4 | *
5 | * Use of this source code is governed by an MIT-style license that can be
6 | * found in the LICENSE file at https://angular.io/license
7 | */
8 | import {
9 | ɵMessageId,
10 | ɵParsedTranslation,
11 | ɵparseTranslation,
12 | } from '@angular/localize';
13 | import { Diagnostics } from '../../common/diagnostics';
14 | import { extname } from 'path';
15 | import {
16 | ParseAnalysis,
17 | ParsedTranslationBundle,
18 | TranslationParser,
19 | } from './translation_parser';
20 |
21 | /**
22 | * A translation parser that can parse JSON that has the form:
23 | *
24 | * ```
25 | * {
26 | * "locale": "...",
27 | * "translations": {
28 | * "message-id": "Target message string",
29 | * ...
30 | * }
31 | * }
32 | * ```
33 | */
34 | export class SimpleJsonTranslationParser implements TranslationParser