Coder Social home page Coder Social logo

fluent-vue / fluent-vue Goto Github PK

View Code? Open in Web Editor NEW
234.0 5.0 10.0 7.17 MB

Internationalization plugin for Vue.js

Home Page: https://fluent-vue.demivan.me

License: MIT License

TypeScript 96.28% JavaScript 3.72%
vue localization internationalization i18n ftl translation vuejs vue2 vue3 internationalization-plugin

fluent-vue's Introduction

SWUbanner

fluent-vue logo

Internationalization plugin for Vue.js

GitHub Workflow Status codecov npm bundle size Standard - JavaScript Style Guide GitHub license

fluent-vue is a Vue.js integration for Fluent.js - JavaScript implementation of Mozilla's Project Fluent

🚀 Features

  • Simple api for developers: Just 2 methods, 1 directive and 1 component
  • Powerfull syntax for translators: Use the entire expressive power of every language without need for changes to application source code
  • Isolation: Locale-specific logic doesn't leak to other locales. A simple string in English can map to a complex multi-variant translation in another language
  • Seamless migration: Works for both Vue 3 and 2
  • No bundler required: Usable via CDN

🎉 Example

<template>
  <div>
    <div>{{ $t('hello-user', { userName }) }}</div>
    <div>{{ $t('shared-photos', { userName, photoCount, userGender }) }}</div>
  </div>
</template>

<fluent locale="en">
# Simple things are simple.
hello-user = Hello, {$userName}!

# Complex things are possible.
shared-photos =
  {$userName} {$photoCount ->
     [one] added one photo
    *[other] added {$photoCount} new photos
  } to {$userGender ->
     [male] his stream
     [female] her stream
    *[other] their stream
  }.
</fluent>

📖 Documentation

Documentation can be found here: https://fluent-vue.demivan.me

Examples for different Vue.js versions and build systems can be found here.

📜 Changelog

Changes for each release are documented in the CHANGELOG.md.

📦 Packages

Project NPM Repo
fluent-vue fluent-vue fluent-vue/fluent-vue
unplugin-fluent-vue unplugin-fluent-vue fluent-vue/unplugin-fluent-vue

📄 License

MIT License © 2020 Ivan Demchuk

fluent-vue's People

Contributors

davidrios avatar demivan avatar dependabot[bot] avatar erickwilder avatar flambe avatar greenkeeper[bot] avatar hongquan avatar nachodd avatar renovate[bot] avatar samuelstroschein avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

fluent-vue's Issues

Make @fluent/bundle a peer dependency

I'm wondering if users of this library should be given the freedom to install any @fluent/bundle version that is compatible with this project. The README also states that installation of @fluent/bundle is part of the configuration steps, so no changes in the workflow are expected.

By comparison, this is exactly what @fluent/react does. Other dependencies could remain as production dependencies (e.g. @fluent/dedent, and @fluent/sequence)

Feature: provide translation attrs for the slots of the i18n component

There's a common and pretty annoying case in localizing web apps, which is when you need tags inside the translated text. For instance, I have this original needing translation:

<p>
  <router-link :to="{ name: 'login' }">Sign in</router-link>
  or
  <router-link :to="{ name: 'register' }">sign up</router-link>
  to add comments on this article.
</p>

The correct would be to have the phrase translated as a whole, and you clearly do not want to risk that code being changed by your translators, besides the security risk of having translated text executed as code. Your project already has a very good solution using the i18n component, in which case I could have a fluent resource like:

sign-in = Sign in
sign-up = sign up
sign-in-up-to-add-comments = {$signIn} or {$signUp} to add comments on this article.

And the code:

<i18n path="sign-in-up-to-add-comments" tag="p">
  <template #signIn>
    <router-link :to="{ name: 'login' }">{{ $t('sign-in') }}</router-link>
  </template>
  <template #signUp>
    <router-link :to="{ name: 'register' }">{{ $t('sign-up') }}</router-link>
  </template>
</i18n>

That goes very far and almost solves the problem, but there is a big issue: the "Sign in" and "sign up" texts are in the context of the sentence and could potentially have alternative casing depending on the language, for instance, it could be that in some language there's a first word that is capitalized and the "Sign in" would then not be. Currently the only way to solve this would be to create new keys for the words, like sign-in-up-to-add-comments-sign-in, but that is not ideal considering that the fluent language already has tools to specifically deal with things like that.

The ideal would be to have a fluent resource like:

# The two parameters will be replaced with links and each link
# will use the .sign*Label as its text
# The reason for the labels to be camel case will become clear shortly...
sign-in-up-to-add-comments =
  {$signInLink} or {$signUpLink} to add comments on this article.
  .signInLabel = Sign in
  .signUpLabel = sign up

You can already use that fluent feature in some cases by making use of the $ta method and the v-t directive, and it would be great if that directive idea could be generalized for the component case as well. Fortunately vue.js already has a way to deal with cases like this in the form of Slot Props.

I propose that the i18n component also use the formatAttrs method and pass the object to the slots as slot props. In which case, using the same fluent resource mentioned above, we could have a code like this:

<i18n path="sign-in-up-to-add-comments" tag="p">
  <template #signInLink="{ signInLabel }"> <!-- < destructuring the slot props could be used if the key is camel-case -->
    <router-link :to="{ name: 'login' }">{{ signInLabel }}</router-link>
  </template>
  <template #signUpLink="{ signUpLabel }">
    <router-link :to="{ name: 'register' }">{{ signUpLabel }}</router-link>
  </template>
</i18n>

It's actually a pretty trivial change and I already implemented it. I'll send a pull request shortly. To avoid affecting existing code and making that intent explicit, I added a new prop to the component called use-ta that controls if the formatAttrs will be used.

BTW, great project, thank you for creating it! I'm writing an article coalescing my ideas of a scalable workflow for properly localizing web/vue applications and this project is one of its cornerstones. I'll probably have additional feature/pull requests in the near future.

Cheers!

Easier way to set attribute directly?

First of all, great project! Your blog post about vue-i18n resonates.

But I wonder if it's a way to set attributes more directly. Let's say I have this image where I want to localize the alt text:
<img :src="logo" alt="Logo of company X" />

As I understand it, I can do:

<img :src="logo" v-bind="$ta('logo')" />

And have a .ftl definition of:

logo =
  .alt = Logo of company X

Is it possible to set it more directly? In vue-i18n-next I can reference the language tag directly:

<img :src="logo" :alt="t('logoAltText')" />

As I understand it, this is not supported now, or maybe it is? If not, do you think it could be? I see the $ta pattern great for translating two or more attributes, but if there is only one, it would be nice to reference the language tag directly?

Again thank you for your work!

EDIT Solved, see my reply to myself :D

Vue 2 project: Getting an error related to the vue-composition-api and vue-demi

I have a Vue 2 application that is using single-spa. For this app I'm using Vue as an external library, by marking it as externals on the vue.config.js file (configureWebpack.externals: ['vue']). As recommended by single-spa, for dealing with the Vue library resource I'm using System.js and specifying a systemjs-importmap, like:

<script type="systemjs-importmap">
      {
        "imports": {
          "vue": "https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js",
           // other stuff...
        }
     }
</script>

Everything seems to be working fine on my project. But I'm getting this error on the console when trying to use the $t function on the templates:

[Vue warn]: provide is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup().

warn | @ | VM256402 vue.js:634
  | warn | @ | vue-composition-api.mjs?9241:423
  | getCurrentInstanceForFn | @ | vue-composition-api.mjs?9241:452
  | provide | @ | vue-composition-api.mjs?9241:1551
  | setup | @ | index.mjs?bbef:177
  | mergedSetupFn | @ | vue-composition-api.mjs?9241:2160
  | mergedSetupFn | @ | vue-composition-api.mjs?9241:2160
  | mergedSetupFn | @ | vue-composition-api.mjs?9241:2160
  | mergedSetupFn | @ | vue-composition-api.mjs?9241:2160
  | mergedSetupFn | @ | vue-composition-api.mjs?9241:2160
  | eval | @ | vue-composition-api.mjs?9241:1972
  | activateCurrentInstance | @ | vue-composition-api.mjs?9241:1891
  | initSetup | @ | vue-composition-api.mjs?9241:1970
  | wrappedData | @ | vue-composition-api.mjs?9241:1953
  | getData | @ | vue.js:4758
  | initData | @ | vue.js:4715
  | initState | @ | vue.js:4654
  | Vue._init | @ | vue.js:5014
  | Vue | @ | vue.js:5092
  | eval | @ | single-spa-vue.js?0461:1

 // continues...

The fact is that is working. But I have tons of console log messages. I think I found a solution, but I'm not completely sure why this is happening.
So, I tried to debug the error and it seems that getCurrentInstanceForFn of the vue-composition-api is not finding the current instance. As stated on the stack trace, I've noted that this has to do with this portion of code:

vue2.mixin({
  setup() {
    provide(RootContextSymbol, rootContext)
  },
})

I've noted that the provide() function is imported from vue-demi. In order to fix this issue, if instead of defining the provide in this way (by using setup() and the composition-api), we define it in the old-fashion way:

vue2.mixin({
  provide() {
    return {
      RootContextSymbol: rootContext
    }
  }
});

The error on the console disappears.
From my understanding this bug has to do with how do I load the Vue dependency and because of that is not being recognized by the composition api. Fun fact, I'm also using Pinia and other libraries that rely on the vue-composition-api and this is not happening.

Is there a good reason why we should be using this provide() method by vue-demi instead of using the code that I'm suggesting? From my understanding it will be the same as the code that I'm suggesting if only for Vue 2. If you are OK with this, I can submit a PR.

To Reproduce
Steps to reproduce the behavior:
I've already described the scenario. The key is to have Vue as an external library (on the vue.config.js) and to load it with systemjs-importmap. I'm wasn't able to correctly reproduce it on a codesandbox environment, but I can create a test repository if it worth the case.

Change locale bundles after mounted?

Hi @Demivan ,

Currently, the language to display is chosen by the order of bundles passed to createFluentVue. This is done before Vue app is mounted.

Is there any way to change language after the app is mounted, like when jumping to a page via vue-router?

How to disable BiDi in fluent?

Clear and concise description of the problem

In my project BestCraft, there is a feature that copy a translated message to the system's clipboard, allows the user to paste it to the game.

Unfortunately, the game fail to handle the Unicode BDI/FSI control codes U+2068 and U+2069.
Is there a way to disable the feature?

links:

Suggested solution

Having an option to disable BDI/FSI control codes insertion.

Alternative

May I can use a regular expression to filter out these characters? I don't know how.
But obviously, this is less elegant.

Additional context

https://github.com/projectfluent/fluent/wiki/Fluent-and-ICU-MessageFormat#bidi
http://unicode.org/reports/tr9/#Directional_Formatting_Characters
https://github.com/projectfluent/fluent/wiki/BiDi-in-Fluent

Validations

Feature request, missing translations checker?

Hi! Would it be possible with a npm script of some kind (linter?) to check for missing translations? To e.g. be able to say that "all translation string in en.ftl should be present in all the other translation files"?

Wrong resolution to Vue 2 when in Vue 3 project

I'm using fluent-vue v3.0.0 in a Vue 3 project. When building, I got this error:

❯ yarn build                                        
yarn run v1.22.17
$ vue-tsc --noEmit && vite build
node_modules/fluent-vue/index.d.ts:19:40 - error TS2709: Cannot use namespace 'Vue' as a type.

19   interface ComponentOptions<V extends Vue> {
                                          ~~~


Found 1 error.

error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • chore(deps): lock file maintenance

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/size.yml
  • actions/checkout v3.0.2
  • actions/setup-node v3.3.0
  • pnpm/action-setup v2.2.2
  • antfu/export-size-action v1
.github/workflows/test.yml
  • actions/checkout v3.0.2
  • actions/setup-node v3.3.0
  • pnpm/action-setup v2.2.2
  • codecov/codecov-action v3
  • actions/checkout v3.0.2
  • actions/setup-node v3.3.0
  • pnpm/action-setup v2.2.2
  • actions/checkout v3.0.2
  • actions/checkout v3.0.2
  • actions/setup-node v3.3.0
  • pnpm/action-setup v2.2.2
npm
package.json
  • @fluent/sequence ^0.7.0
  • cached-iterable ^0.3.0
  • @antfu/eslint-config ^0.25.1
  • @fluent/bundle ~0.17.1
  • @fluent/dedent ~0.4.0
  • @ls-lint/ls-lint ^1.11.2
  • @release-it-plugins/lerna-changelog ^5.0.0
  • @types/node ^17.0.10
  • @vue/compiler-sfc ^3.2.37
  • @vue/test-utils ^2.0.1
  • c8 ^7.11.3
  • esbuild-plugin-globals ^0.1.1
  • eslint ^8.17.0
  • execa ^6.1.0
  • happy-dom ^6.0.4
  • husky ^8.0.1
  • lint-staged ^13.0.1
  • tsup ^6.1.2
  • typescript ^4.7.3
  • vite ^2.9.12
  • vitest ^0.15.1
  • vue ^3.2.37
  • vue-2 ^2.6.14
  • vue-3 ^3.2.37
  • @fluent/bundle >=0.17.0
  • @vue/composition-api >=1.0.0-rc.1
  • vue ^2.6.11 || >=3.0.0
  • node >=12.0.0

  • Check this box to trigger a request for Renovate to run again on this repository

Migration path from vue-i18n to fluent-vue

Clear and concise description of the problem

I'm looking to move from vue-i18n to this library. We already use vue-i18n quite extensively throughout our app so it'd be nice to have both libraries installed together to slowly convert everything to fluent.

My problem is that we're using $t from vue-i18n throughout our app which conflicts with the same function from fluent-vue meaning we'd have to spend a lot of time to convert everyone in one go.

Suggested solution

Maybe a way to rename the globals or a flag that that does this for you? It would be fairly easy to find and replace over the app to switch it back once everything was migrated.

Alternative

No response

Additional context

No response

Validations

Help with Fluent Bundles

Hello there! I'm totally new to fluent-vue, and I wanted to know a few things that I couldn't find in the docs :

  • how does the bundles property affect the order in which messages are applied (like fallbacks) ?
  • how can I automatically set the right bundles for the navigator language, especially if I want something like fr-FR => if message not found fr => if message not found en ?

Thank you for your help!

Import translation from external FTL file

Hi @Demivan

Currently, the usage is to add a block to .vue file:

<fluent locale='en'>
</fluent>

Is there any way that I just write those fluent translations in external .ftl files, then import into .vue file? That way, I can give .ftl files to translator team to do their job, then get back the result. The .vue file with so much code will scare them.

Remove bom from files

Describe the bug

On the ftl files add specific symbol before translations which break the right translation

Reproduction

No response

System Info

--browsers

Used Package Manager

pnpm

Validations

  • Follow our Code of Conduct.
  • Read the Contributing Guide.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
  • The provided reproduction is a minimal reproducible of the bug.

Eslint plugin

Check for:

  • Missing translation in the main locale
  • Interpolation of the translation key
  • Reusing non-global translations

html tags in i18n component

Discussed in #759

Originally posted by judge330 March 27, 2022
Hey!
Is there a way to use simple html tags in component like with v-html?

Example:

Vue-Template:

<i18n path="general-register-info" tag="span">
    <template #showTermsModalSpan="{ generalPagesTerms }">
       <span class="underline cursor-pointer" @click.prevent="showModal('terms')">{{ generalPagesTerms }}</span>
    </template>
</i18n>

Imported ftl file with <br> and <strong>

general-pages-terms = Terms

general-register-info =
    Register here.<br> But respect <strong>our terms</strong> (see {$showTermsModalSpan}).
    .general-pages-terms = { general-pages-terms }

Result:
Register here.<br> But respect <strong>our terms</strong> (see <ins>Terms</ins>).

Expected:
Register here.
But respect our terms (see Terms).

Don't create wrapper element in i18n component on Vue 3

Vue 3 does not require components to have a wrapper element.

It is possible to make i18n component to not render wrapper element by default on Vue 3.

Pros:

  • Simpler html structure by default

Cons:

  • Vue 3 implementation will be different from Vue 2
  • Adding classes and other attributes will not work by default

Or instead new attribute can be added or allow tag attribute to accept falsy values.
Pros:

  • Non-breaking change

Cons:

  • Could be to inconvenient to use this parameter everythere
  • This attribute will not work in Vue 2 anyway

An in-range update of @rollup/plugin-typescript is breaking the build 🚨


☝️ Important announcement: Greenkeeper will be saying goodbye 👋 and passing the torch to Snyk on June 3rd, 2020! Find out how to migrate to Snyk and more at greenkeeper.io


The devDependency @rollup/plugin-typescript was updated from 3.0.0 to 3.1.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@rollup/plugin-typescript is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Fix README

Add usage examples and features description

How to assess `$t` in setup script?

Clear and concise description of the problem

I can't use $t in the SFC's setup script:

<script setup lang="ts">
import { ElMessage } from 'element-plus'

const onCheckUpdateClick = () => {
        // ....
        ElMessage({
                type: 'success',
                message: $t('check-update-success'), // Uncaught ReferenceError: $t is not defined
        })
}
</script>

<template>
        <el-button @click="onCheckUpdateClick">{{ $t('check-update') }}<!-- Good --></el-button>
</template>

<fluent locale="zh-CN">
check-update = 检查更新
check-update-success = 检查更新成功
</fluent>

Suggested solution

I don't know

Alternative

No response

Additional context

No response

Validations

Typing error with app.use()

Describe the bug

Since Vue v3.2.47, this line of code:

app.use(fluent)

raises typing error:

yarn build
yarn run v1.22.19
$ vue-tsc && vite build
src/main.ts:22:5 - error TS2554: Expected 2 arguments, but got 1.

22 app.use(fluent)
       ~~~~~~~~~~~

  node_modules/@vue/runtime-core/dist/runtime-core.d.ts:78:63
    78     use<Options extends unknown[]>(plugin: Plugin_2<Options>, ...options: Options): this;
                                                                     ~~~~~~~~~~~~~~~~~~~
    Arguments for the rest parameter 'options' were not provided.


Found 1 error in src/main.ts:22

I reported the issue to Vue project, but Vue core developer identified that it is fluent-vue's bug.

Reproduction

https://bitbucket.org/hongquan/vue-wrong-argument-for-use

System Info

System:
    OS: Linux 5.19 Ubuntu 22.10 22.10 (Kinetic Kudu)
    CPU: (4) x64 Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
    Memory: 6.45 GB / 11.59 GB
    Container: Yes
    Shell: 5.9 - /usr/bin/zsh
  Binaries:
    Node: 18.14.0 - /usr/bin/node
    Yarn: 1.22.19 - /usr/bin/yarn
    npm: 9.3.1 - /usr/bin/npm
  Browsers:
    Chrome: 110.0.5481.100
    Firefox: 110.0
  npmPackages:
    vue: ^3.2.47 => 3.2.47

Used Package Manager

yarn

Validations

  • Follow our Code of Conduct.
  • Read the Contributing Guide.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
  • The provided reproduction is a minimal reproducible of the bug.

custom warn function

Is your feature request related to a problem? Please describe.

console.warn pollutes log output in containerized SSR application.

Describe the solution you'd like

A warn configuration option.

Vue devtools integration

What would be nice to have?

  • Show component translations
  • Show global translation on root component node
  • Editable translations?
  • Highlight overrides of global translations in components
  • Switch locale for preview? How to deal with global translations?
  • Pseudolocalization switch?

Lazyloading .flt files

Hi @Demivan,
Thank you for bringing a good library 👍
I walked through the docs and see that no section/example for lazyloading .flt resources.
Would you mind adding it?

Missing type checking and intellisense for i18n component

Describe the bug

There is no autocomplete or type information on i18n component

Reproduction

No response

System Info

System:
    OS: Linux 6.3 Arch Linux
    CPU: (16) x64 AMD Ryzen 7 3700X 8-Core Processor
    Memory: 1.76 GB / 15.53 GB
    Container: Yes
    Shell: 3.6.1 - /bin/fish
  Binaries:
    Node: 20.3.0 - /usr/bin/node
    Yarn: 1.22.19 - /usr/bin/yarn
    npm: 8.19.2 - /usr/bin/npm
  Browsers:
    Chromium: 114.0.5735.133
  npmPackages:
    unplugin-fluent-vue: ^1.1.3 => 1.1.3 
    fluent-vue: ^3.2.0 => 3.2.0 
    vue: ^3.3.4 => 3.3.4

Used Package Manager

pnpm

Validations

  • Follow our Code of Conduct.
  • Read the Contributing Guide.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
  • The provided reproduction is a minimal reproducible of the bug.

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Location: renovate.json
Error type: The renovate configuration file contains some invalid settings
Message: Invalid regExp for regexManagers: ''(?<depName>\S*)': '(?>npm:\S+@)?(?<currentValue>\S*)''

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.