- 投稿日:2020-10-17T22:58:38+09:00
【Vue.js】リアクティブに画像プレビューする
new FileReaderについて
アップロード画像を非同期に読み取ることができるオブジェクト
const reader = new FileReader();FileReaderでできること
読み込まれた画像情報をアップロード画像データに上書きする
reader.onload = (e) => { this.staff_image = e.target.result; };アップロード画像を読み込む
reader.readAsDataURL(this.staff_image);
- 読み取り操作が正常に完了されたら、result属性に格納されたデータを、アップロード画像データに上書きする
- アップロード画像を読み込ませた結果をresult属性に返している
実践:アップロード画像をプレビューで表示させて、DBに保存する
ポイント
- プレビュー画像と、DB保存画像の格納dataを分けて考える
流れ
- v-input-fileに、v-model属性をつけることで、アップロードファイルとdataオプションと連動させる
- 画像がアップロードされたらimgaeUp関数が着火し、画像ファイル拡張子を使えるものに限定させる。この時、DBに保存する画像情報を格納しておく。
- もし使えない画像拡張の場合は、errors変数にエラー文を格納すると共に画像情報を削除し、imageUp関数を強制終了させる
- 特に問題ない場合、プレビュー画像用に画像情報をパースし、アップロード画像を表示させる判定に切り替える。
tmplateタグ(pug形式)
v-img(v-if="image", :src="staff_image") v-img(v-else, src="/images/default.png")v-file-input( hide-input, // フォームを隠す prepend-icon="mdi-camera", // ボタンアイコン @change="imageUp()", // アップロードされたら、着火する関数 v-model="staff_image" // アップロード画像と連動させる )scriptタグ
data() { return { staff_image: "", // アップロードファイルの格納場所 image_name: "", // DBに保存するアップロードファイル image: false, // 表示するのは、デフォルト画像かアップロード画像かの判定 }; }, imageUp() { // 画像ファイル拡張子の検証 const type = this.staff_image.type; this.image_name = this.staff_image; // DB保存用に、画像情報を格納しておく let errors = ""; if ( type !== "image/jpeg" && type !== "image/gif" && type !== "image/png" && type !== "application/pdf" ) { errors += ".jpg、.gif、.png、.pdfのいずれかのファイルのみ許可されています\n"; } if (errors) { alert(errors); this.staff_image = ""; this.image_name = ""; this.image = false; return; // エラー処理の場合、imageUp関数を強制終了する } // アップロード画像をプレビューで表示させる const reader = new FileReader(); reader.onload = (e) => { this.staff_image = e.target.result; }; reader.readAsDataURL(this.staff_image); this.image = true; // アップロード画像の表示に切り替える },
- 投稿日:2020-10-17T21:37:05+09:00
Nuxt.js + Composition API(provide/inject) + Firebase Authenticationでミニマルな認証管理基盤を作成する
普段私はNuxt.jsを使っているのですが、今までのVue2時には、(他の選択肢を知らずに)Vuexで認証情報を管理していました。
しかし、Vue3ではprovide/injectを使ったステート管理が中々推されており、業務の中でこの度チャレンジしました。
そして『Nuxt.js + Composition API(provide/inject) + Firebaseでの最低限の認証機能を実装した』という記事は現状まったく無かったため、この度自分で記事を書きます。(※)この記事では、そもそもの『Firebase Authenticationの使い方』や『Nuxtの使い方』については言及しません。
(※)provide/injectのメリットについては、この方の記事が個人的に中々わかりやすかったです。
Vue.2までだと、『1つの.vueファイルの中にテンプレート/ステート/ロジックを全て書く必要があったのが、今後はそれらを別のファイルに書き分けられる』という事です。
https://qiita.com/karamage/items/4bc90f637487d3fcecf0
firebaseの情報はpluginで管理します。のちにimportします。
plugins/firebase.jsimport * as firebase from 'firebase/app' import 'firebase/auth' import 'firebase/firestore' import 'firebase/functions' import * as firebase_secret from '../_secret/firebase' //ここにsecrets情報を入れている firebase.initializeApp(firebase_secret) export const auth = firebase.auth() export const functions = firebase.app().functions('us-central1') //('asia-northeast1') export const db = firebase.firestore() export const timestamp = () => firebase.firestore.FieldValue.serverTimestamp()また、殆どの方はFirebase Authenticationで認証を行った後、Cluoud Firestoreから等ユーザーの情報を引っ張ってきたいでしょう。
Cloud Firestoreでは、
/{user collection}/${userID}/フィールド(nameなど)というシンプルな構成をここでは想定します。そしてNuxt.のルートディレクトリに/composablesディレクトリを作成し、その中に①store情報を使うための鍵のファイル(user-store-key.ts)と②ストア本体のファイル(user-store.ts)を作ります。
composables/user-store-key.tsimport { InjectionKey } from '@vue/composition-api'; import { UserStoreType } from './user-store'; const UserStoreKey: InjectionKey<UserStoreType> = Symbol('UserStore'); export default UserStoreKey;composables/user-store.tsimport { reactive, ref } from "@vue/composition-api" import { auth, db, timestamp } from '../plugins/firebase' export default function UserStore() { const state = reactive({ id: "", email: "", name: "", }) const GetUserStore =()=> state; const SetUserStore = (user: firebase.User) => { console.log("Login Information", user) const ref_userinfo = db.collection('users').doc(user.uid) ref_userinfo.onSnapshot(doc => { if (doc.exists) { console.log("Obtained User contact_info from DB", doc.data()!.contact_info) const parsedUserInfo = { id: doc.id, email: user.email!, name: doc.data()!.name, } state = parsedUserInfo console.log("Reflected on state", GetUserStore()) } else { console.log("No User Data") } }); } } return { SetUserStore, GetUserStore} } export type UserStoreType = ReturnType<typeof UserStore>コレで最低限のストアの構築は完成です!
SetUserStore()が呼ばれた際、stateのidとemailはAuthenticationの情報をセットし、nameはFirestoreから引っ張ってきます。
また、ゲッターとしてGetUserStore()、セッターとしてSetUserStore()を定義しreturnする事で、stateに外部から直接アクセス出来ないようにする事で保守性を上げています。次に、/layout/default.vueにて、SetUserStore()関数をonMounted()のタイミングで実行するようにします。
stateの内容は、ページをリロードされると消えてしまいますが、layoutsで定義してしまえば、ページをリロードされる旅にSetUserStore()関数が呼ばれ、Firebaseから値を引っ張ってきます。layouts/default.vue<template> <v-app > <v-main> <v-container class="pa-0"> <nuxt /> </v-container> </v-main> </v-app> </template> <script lang="ts"> import Vue from 'vue' import { auth, db, timestamp } from '../plugins/firebase' import { defineComponent, SetupContext, onMounted, provide, inject } from '@vue/composition-api' import UserStore from "../composables/user-store" import UserStoreKey from "../composables/user-store-key" export default defineComponent({ setup(props, context) { provide(UserStoreKey, UserStore()) //ここでprovideを使用 const store = inject(UserStoreKey) as UserStoreType //ここでinjectを使用 onMounted(() => { auth.onAuthStateChanged((user) => { if (user) { store.SetUserStore(user) } else { console.log("Not Authenticated User") } }) }) return { store} } }) </script> <style scoped> </style>公式ドキュメントを読むと、provideは送信側/injectは受信側を想定しているようですが、上記の通り、provideとinjectを同時に使う事も問題ありません。
また、(Vue2の時は)この処理はmiddlewareに書いても動作しましたが、Vue3ではmiddlewareに書いても動作しません!!
何故なら、provide/inject関数はsetupコンテキストの中でしか動作しないためです。
終わりに
この度、初めてprovide/injectを使用しましたが、Vuexと比べると
①storeのファイルが肥大化しづらい
②Vuexと違いグローバル変数では無いため、そこで保持すべきでない情報を持たずに済む。
③(②にも関連するが) どこでstoreが変更がされているかを追いやすく、大規模開発でも破綻しづらい
④型推論がイイ!!
というメリットを感じました。今後積極的に使ってゆこうと思います。
- 投稿日:2020-10-17T21:37:05+09:00
Nuxt.js + Composition API(provide/inject) + Firebase Authenticationでミニマルな認証基盤を作成する
普段私はNuxt.jsを使っているのですが、今までのVue2時には、(他の選択肢を知らずに)Vuexで認証情報を管理していました。
しかし、Vue3ではprovide/injectを使ったステート管理が中々推されており、業務の中でこの度チャレンジしました。
そして『Nuxt.js + Composition API(provide/inject) + Firebaseでの最低限の認証機能を実装した』という記事は現状まったく無かったため、この度自分で記事を書きます。(※)この記事では、そもそもの『Firebase Authenticationの使い方』や『Nuxtの使い方』については言及しません。
(※)provide/injectのメリットについては、この方の記事が個人的に中々わかりやすかったです。
Vue.2までだと、『1つの.vueファイルの中にテンプレート/ステート/ロジックを全て書く必要があったのが、今後はそれらを別のファイルに書き分けられる』という事です。
https://qiita.com/karamage/items/4bc90f637487d3fcecf0
firebaseの情報はpluginで管理します。のちにimportします。
plugins/firebase.jsimport * as firebase from 'firebase/app' import 'firebase/auth' import 'firebase/firestore' import 'firebase/functions' import * as firebase_secret from '../_secret/firebase' //ここにsecrets情報を入れている firebase.initializeApp(firebase_secret) export const auth = firebase.auth() export const functions = firebase.app().functions('us-central1') //('asia-northeast1') export const db = firebase.firestore() export const timestamp = () => firebase.firestore.FieldValue.serverTimestamp()また、殆どの方はFirebase Authenticationで認証を行った後、Cluoud Firestoreから等ユーザーの情報を引っ張ってきたいでしょう。
Cloud Firestoreでは、
/{user collection}/${userID}/フィールド(nameなど)というシンプルな構成をここでは想定します。そしてNuxt.のルートディレクトリに/composablesディレクトリを作成し、その中に①store情報を使うための鍵のファイル(user-store-key.ts)と②ストア本体のファイル(user-store.ts)を作ります。
composables/user-store-key.tsimport { InjectionKey } from '@vue/composition-api'; import { UserStoreType } from './user-store'; const UserStoreKey: InjectionKey<UserStoreType> = Symbol('UserStore'); export default UserStoreKey;composables/user-store.tsimport { reactive, ref } from "@vue/composition-api" import { auth, db, timestamp } from '../plugins/firebase' export default function UserStore() { const state = reactive({ id: "", email: "", name: "", }) const GetUserStore =()=> state; const SetUserStore = (user: firebase.User) => { console.log("Login Information", user) const ref_userinfo = db.collection('users').doc(user.uid) ref_userinfo.onSnapshot(doc => { if (doc.exists) { console.log("Obtained User contact_info from DB", doc.data()!.contact_info) const parsedUserInfo = { id: doc.id, email: user.email!, name: doc.data()!.name, } state = parsedUserInfo console.log("Reflected on state", GetUserStore()) } else { console.log("No User Data") } }); } } return { SetUserStore, GetUserStore} } export type UserStoreType = ReturnType<typeof UserStore>コレで最低限のストアの構築は完成です!
SetUserStore()が呼ばれた際、stateのidとemailはAuthenticationの情報をセットし、nameはFirestoreから引っ張ってきます。
また、ゲッターとしてGetUserStore()、セッターとしてSetUserStore()を定義しreturnする事で、stateに外部から直接アクセス出来ないようにする事で保守性を上げています。次に、/layout/default.vueにて、SetUserStore()関数をonMounted()のタイミングで実行するようにします。
stateの内容は、ページをリロードされると消えてしまいますが、layoutsで定義してしまえば、ページをリロードされる旅にSetUserStore()関数が呼ばれ、Firebaseから値を引っ張ってきます。layouts/default.vue<template> <v-app > <v-main> <v-container class="pa-0"> <nuxt /> </v-container> </v-main> </v-app> </template> <script lang="ts"> import Vue from 'vue' import { auth, db, timestamp } from '../plugins/firebase' import { defineComponent, SetupContext, onMounted, provide, inject } from '@vue/composition-api' import UserStore from "../composables/user-store" import UserStoreKey from "../composables/user-store-key" export default defineComponent({ setup(props, context) { provide(UserStoreKey, UserStore()) //ここでprovideを使用 const store = inject(UserStoreKey) as UserStoreType //ここでinjectを使用 onMounted(() => { auth.onAuthStateChanged((user) => { if (user) { store.SetUserStore(user) } else { console.log("Not Authenticated User") } }) }) return { store} } }) </script> <style scoped> </style>公式ドキュメントを読むと、provideは送信側/injectは受信側を想定しているようですが、上記の通り、provideとinjectを同時に使う事も問題ありません。
また、(Vue2の時は)この処理はmiddlewareに書いても動作しましたが、Vue3ではmiddlewareに書いても動作しません!!
何故なら、provide/inject関数はsetupコンテキストの中でしか動作しないためです。
終わりに
この度、初めてprovide/injectを使用しましたが、Vuexと比べると
①storeのファイルが肥大化しづらい
②Vuexと違いグローバル変数では無いため、そこで保持すべきでない情報を持たずに済む。
③(②にも関連するが) どこでstoreが変更がされているかを追いやすく、大規模開発でも破綻しづらい
④型推論がイイ!!
というメリットを感じました。今後積極的に使ってゆこうと思います。
- 投稿日:2020-10-17T21:23:39+09:00
【Vue.js】 transitionを使用してfade-in, fade-out
【ゴール】
【Vue.js】 transitionを使用してfade-in, fade-out
【環境】
mac catarina 10.156
Vue.js v2.6.12
【実装】
■ifで条件分岐
■transition name="fade"で、fadeモードに
Helloworld.vue<template> <div> <h2>hi</h2> <button @click="show = !show">変更</button> <transition name="fade" > <p v-if="show">trasition</p> <transition> </div> <template> <script> export default ({ data(){ return { show: true } } }) </script> <style scoped> .fade-enter-active, .fade-leave-active { transion: opacity 0.5s; } .fade-enter, .fade-leave-to{ opacity: 0; } </style>以上
【まとめ】
■transitionでアニメーションを作成
■nameでアニメーションの種類を指定してあげる
■style内でtransitionの設定をする【オススメ記事】
■ 【Vue.js】クリック処理 @click
https://qiita.com/tanaka-yu3/items/e578cadf35a7bc024770■ 【Vue.js】 IF文・For文 条件分岐、繰り返し処理
https://qiita.com/tanaka-yu3/items/0ccf9a11525331b764de■ 【Vue.js】 vue-routerを利用してルーティング作成し画面遷移を!!
https://qiita.com/tanaka-yu3/items/1b1f5f2b2d9638f50d33
- 投稿日:2020-10-17T19:13:48+09:00
firebaseでpull request ごとにpreviewをdeployするgithub action
概要
firebaseで、spaをnetlify やvercel のようにプルリクごとにURLを変えてdeployしたい。
how to
firebaseの初期化
公式
firebase toolをグローバルにinstall
今回はdeployする directoryはdistをしていします。(firebaseのdefaultはpublic)npm i -g firebase-tools firebase login firebase hosting #deloyするdirectoryをdist にするchannel deployコマンドをpackage.jsonに追加
previewようのdeployはchannel deployというコマンド使います。
そのため、以下のように scriptに追加します。{ "name": "firebase deploy app", "version": "1.0.0", "private": true, "scripts": { "dev": "nuxt-ts", "build": "nuxt-ts build", "start": "nuxt-ts start", "generate": "nuxt-ts generate", "lint:js": "eslint --ext .js,.vue --ignore-path .gitignore .", "lint:style": "stylelint **/*.{vue,css} --ignore-path .gitignore", "lint": "yarn lint:js && yarn lint:style", "test": "jest", "channel-deploy": "firebase hosting:channel:deploy" ←# 追加 },github workflowを設定する
以下のように設定します。
firebase-deploy.ymlname: CI on: [push] jobs: FrontDeploy: name: FrontDeploy runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@main - name: setup Node uses: actions/setup-node@v1 with: node-version: '12' registry-url: 'https://registry.npmjs.org' - name: Install Dependencies run: yarn - name: Build run: yarn build - name: deploy to Firebase Hosting shell: bash run: yarn channel-deploy $(echo ${GITHUB_REF#refs/heads/}) --token=${{ secrets.FIREBASE_TOKEN }}こちらでは、branch名を取得しています。
$(echo ${GITHUB_REF#refs/heads/})tokenを取得
以下コマンドで、tokenを取得できます
firebase login:ci取得したtokenを
FIREBASE_TOKEN
にいれましょう。以上でdeployできるはずです!
twitterもフォローよろしくおねがいします。 !
- 投稿日:2020-10-17T15:11:15+09:00
@vue/cliのインストールからVueプロジェクト作成まで
はじめに
今回は@vue/cliのインストールをしてから実際にVueプロジェクトを実行するまでを書いていきます!
Node.jsがインストールされている必要があるので、
まだインストールできていない人はNode.jsのインストール方法(Windows)を見てね!目次
- @vue/cliのインストール
- vue create コマンドでVueプロジェクトの作成
- 実際にブラウザで確認してみよう!
1. @vue/cliのインストール
- @vue/cliはVueプロジェクトの雛形を作るのを便利にしてくれるコマンドラインインタフェースです! 以下のコマンドを実行するとインストールできます!
$ npm install -g @vue/cli
- @vue/cliが正しくインストールされているかを確認する場合は「vue --version」を実行し、 バージョン情報が表示されればOK!
$ vue --version @vue/cli 4.5.72. vue create コマンドでVueプロジェクトの作成
- 「vue create project-name」を実行すると作成されます(project-nameは自分の好きな名前で作れます)
$ vue create sample
- 以下のように表示されたらエンターでOK!
- Vueプロジェクトの雛形が以下のように生成されます
3. 実際にブラウザで確認してみよう!
- 作成されたプロジェクトフォルダに移動し「npm run serve」を実行!
$ cd sample$ npm run serve
- ブラウザを開いて「http://localhost:8080」にアクセスし下図のような画面が表示されていれば成功!
- 投稿日:2020-10-17T00:40:26+09:00
ボタン一つで画像から文字起こし&翻訳できるアプリを作った話
こんにちは、Yuiです。
この度、ボタン一つで画像から文字起こし&翻訳できるアプリを作りました!
ボタン一つで写真から文字起こししたり、母国語に翻訳するアプリ作りました!!
— Yui?個人開発 (@yui_active) October 15, 2020
ボタン一つでコピー可能、多言語に対応してます。
登録不要で利用できます。#PicTranslator
拡散してもらえると私が喜びます!https://t.co/A28rXwMMB5
100日目#100日後に話題のwebサービスを立ち上げるワニ機能としては下記のものをつけています。
- 自動で利用者のブラウザの言語を読み取ってデフォルト言語に設定
- ボタン一つで文字起こしされる
- ボタン一つでデフォルト言語(利用者が普段使っている言語)に翻訳される
- デフォルト言語以外に言語を変えたい場合、クリックで言語を変えることができる
- 言語を変えた場合、全体的に多言語化される
- 文字起こし&翻訳した後の文章はボタン一つで簡単にコピーできる
シンプルな作りで、誰が見てもすぐに使うことができるようなサービスを目指して作りました。
使った技術スタック
今回、APIの接続だけで完了するような簡単なアプリなので、DB設計は必要ありませんでした。
というわけで、使ったのは下記だけです。
- Vue.js(フレームワークはVuetify利用)→フロントエンド
- vue-clipboard2→クリックしたらコピーできるように利用
- axios→APIとの通信のために利用
- vue-i18n→多言語化のために利用
- API→Cloud Vision APIとCloud Translation APIを利用
上記で大体かかった時間は合計30時間ぐらいでした。
デザイン→1時間
画面設計→5時間
API接続→10分
多言語化→2時間
残りはレイアウトの微調整とかでこだわった部分で時間がかかりました。笑若干ハマったところ
今回少し詰まったところとしてはplaceholderだけv-bindさせて使う必要があるということと、自動で利用者のブラウザの言語を読み取るという部分があります。
まず、今回多言語化をしたので、i18nの書き方に従い、
<div>{{$t('message.welcome')}}</div>のように書く必要があります。
ただ、placeholderの文字を多言語化するにあたって、どういう風にかけば良いか悩みました。
//ファイルアップロード部分 <v-file-input v-model="file" @change="setImage" @click:clear="clearData" accept="image/*" prepend-icon="mdi-camera" placeholder="ここにプレースホルダーに表示させる文字" //<=この部分をどう多言語化する? ></v-file-input>その部分に関して書いてあるものが少なく少し悩んだのですが、公式ドキュメントを見るとどうやらv-bindさせて書けば良さそうだったので、以下のようにして書きました。
//ファイルアップロード部分 <v-file-input v-model="file" @change="setImage" @click:clear="clearData" accept="image/*" prepend-icon="mdi-camera" :placeholder="$t('message.welcome')" ></v-file-input>これでOK。ちなみに今回すでに文字起こしや翻訳をした後に再度違う画像をアップしたときに、過去の文字起こし・翻訳結果のデータが画面に残ってるままでは奇妙だったので、新しい写真をアップした段階でその結果を消すために
@click:clear="clearData"
を書いています。また、自動で利用者のブラウザの言語を読み取る部分に関しては、今回多言語化対応をしていますが、もし自分が英語話者だったとして、デフォルト言語が日本語だったらその時点で見る気失せますよね?
ということで、利用者の言語に合わせて表示を切り替える必要がありました。そのために
main.js
に以下を追加main.jsconst browserLanguage = function() { var ua = window.navigator.userAgent.toLowerCase(); try { // chromeは以下で利用者のブラウザ言語を取得できる if (ua.indexOf('chrome') != -1) { return ( navigator.languages[0] || navigator.browserLanguage || navigator.language || navigator.userLanguage ).substr(0, 2); } // それ以外(例えばIEなど)は下記で取得する必要がある else { return ( navigator.browserLanguage || navigator.language || navigator.userLanguage ).substr(0, 2); } } catch (e) { return undefined; } }; //上記で取得した言語をデフォルト言語として設定 let defaultLang; //今回日本語、英語、フランス語、ドイツ語、中国語のみに対応してるので、その中にブラウザで検出した言語が入ってるかどうか確認 const langs = ['ja', 'en', 'fr', 'de', 'zh'] if (langs.includes(browserLanguage())) { defaultLang = browserLanguage(); } else { //もし上記の言語外の場合はデフォルト言語を英語にする defaultLang = 'en'; }jsでブラウザの取得できるのは知ってたんですが、chromeしかやり方を知らなかったので、それ以外に対応させるのに色々と調べました。
また、条件文で以下のようにざっくりと書いたらうごくには動くんですが、ESLintでno-constant-conditionで怒られたので上記のように書き直しました。
if ( browserLanguage() == 'ja' || 'en' || 'fr' || 'de' || 'zh') { defaultLang = browserLanguage(); } else { defaultLang = 'en'; }ほかは特に詰まることはなかったです。
工夫した点
今回はできるだけシンプルに!というのを目指して作りました。
なので、説明文はなくてもわかるように作りました。
結果、このようになりました。
画像をドラッグすると、こんな感じで文字起こしすると翻訳するのボタンが選べるようになります。
また、翻訳言語に関しては、もちろん英語やドイツ語など多言語にその場で選ぶようにすることもできました。
が、画像を翻訳したいとなったときに、わざわざ母国語以外に翻訳するか?という気持ちから、母国語に翻訳できるようにということで、ボタン一つで表示の言語に翻訳という形にしました。また、もし表示の言語と母国語が違う(翻訳したい言語が違う)という場合は右上のボタンから違う言語に変換することもできます。
後はコピーした後の動きなどを少し工夫。
このレイアウトがな〜〜気に入らないんだよなあ〜〜〜〜〜 https://t.co/u80VW5N72z pic.twitter.com/71b9T92YZt
— Yui?個人開発 (@yui_active) October 12, 2020これをこう。
すごい微妙に改善した
— Yui?個人開発 (@yui_active) October 12, 2020
正直結局これで落ち着くならtootlipで組んだりalertで組んだりしてた時間がすべて無駄になったと思ったけど、まあシンプルなのが一番良い
後は文字起こしと翻訳のボタンのところにアイコンを追加したい
何かいいのあるかな https://t.co/eDIroyJtNw pic.twitter.com/DtQi0C5DAxそして右上の言語のボタンが何を表すのかわかりにくかなと思い、tootlipをその部分に利用。
もちろんこの部分も多言語化対応済み。
例えば英語で見てるときは英語で表示されるようになっています。こだわりたかったけど時間の都合で省略した部分
今回どうしても省略した部分としては、OGPの多言語化です。
これは諸々の設定があまりにも面倒だったので、代わりに日本語と英語を両方表示するようにしました。最後に
今回、作ったときはこんなに簡単なアプリを世の中に出してごめんなさい...という気持ちだったのですが、出してみると意外と好評で嬉しかったです。
これのLineBot版としてほんやくこんにゃくんも前に出したので、よかったら使ってみてください。
画像を送信するだけで、勝手に画像内の言語を認識して何でも日本語に変えてくれるLineBot作りました!
— Yui?個人開発 (@yui_active) October 5, 2020
英語、中国語、ドイツ語、フランス語あたりは動作確認してます!(他の言語でもOK)
良いな〜と思ったら拡散してもらえると嬉しいです!
これからwebアプリ化もします! pic.twitter.com/PrAG1o1gHOまた、Product Huntにも出してます!良ければvoteお願いします。