Hello.

I am Paul Kinlan.

A Developer Advocate for Chrome and the Open Web at Google.

testing-file-share-target

Paul Kinlan

これはAndroid上のShare Target APIのテストであり、ファイルを共有することができます。 あなたがここに何かを見れば、それからすべてが良いです:)

Read More

Paul Kinlan

Trying to make the web and developers better.

RSS Github Medium

Ricky Mondello: Adoption of Well-Known URL for Changing Passwords

Paul Kinlan

SafariチームのRicky Mondelloがつい最近、Twitterが./well-known/change-password仕様をどのように使用しているかについてのメモを共有しました。

I just noticed that Twitter has adopted the Well-Known URL for Changing Passwords! Is anyone aware of other sites that have adopted it?

Twitter’s implementation: https://twitter.com/.well-known/change-password; Github’s: https://github.com/.well-known/change-password; Specification :https://github.com/WICG/change-password-url

Read full post

よく知られた場所にファイルがあれば、ブラウザは複雑なUIをナビゲートする必要なしに素早くパスワードをリセットすることを可能にするUIをユーザに提供できます。

この仕様は一見シンプルです。よく知られているファイルには、アクションを実行したいときにユーザーに指示するURLが含まれているだけです。これは私が考えることを私に導きました、私達はこれらのより多くの機能を提供することができます:

  • GDPRベースの同意モデル(cookie同意)のよく知られた場所 - サイトの所有者は、ユーザーがすべてのcookieおよびその他のデータ同意アイテムを管理し、潜在的に取り消すことができるページへのリンクを提供できます。 *ブラウザの権限管理のためのよく知られた場所 - サイトの所有者は、ユーザーが地理的位置、通知、その他のプリミティブなどのものに対する権限を取り消すことができるようにするための迅速な場所を提供できます。 *アカウントの削除と変更のためのよく知られたパス メーリングリスト購読管理のための*よく知られたパス

リストは続きます….私はユーザーが一般的なユーザーの行動を発見するのを手助けするための単純なリダイレクトファイル、そしてブラウザがそれを表面化させる方法のためのアイデアが本当に好きです。

更新: issue to Chrome to see if we can get a similar implementationを追加しissue to Chrome to see if we can get a similar implementation

pinch-zoom-element

Paul Kinlan

Jakeとチームは、ブラウザ独自のピンチズームのダイナミクス以外のHTMLのセットでピンチズームを管理するための、このかなり素晴らしいカスタム要素を構築しました(モバイルビューポートズームを考えてください)。この要素は、Chrome Dev Summitで構築およびリリースしたsquooshアプリに必要な中心的コンポーネントの1つでした(…「Chrome Dev Summitでリリース」と言いますsquooshは、China Google Developer Dayですべてのユーザーに公開しましたチームの他のメンバーが通商禁止になっていたとしても;)…)

install: npm install --save-dev pinch-zoom-element

<pinch-zoom>
  <h1>Hello!</h1>
</pinch-zoom>

Read full post

私はちょうどそれを私のブログに加えました(ほんの数分かかりました)、あなたは私が撮った写真を共有する私の「 life 」セクションでそれをチェックすることができます。タッチ対応デバイスを使用している場合は、複数の指の入力を処理できるトラックパッドを使用している場合は、要素をすばやくピンチズームできます。

この要素は、Webコンポーネントがユーザーインターフェイスコンポーネントを作成するためのモデルとして好きな理由の良い例です。 pinch-zoom要素は、ワイヤ上でわずか3kb(圧縮されていない)で最小限の依存関係にあり、使いにくくなるようなカスタムのアプリケーションレベルのロジックを束縛することなく、1つの仕事を非常にうまく行っています。 vs Squooshアプリから学んだことに基づいて共有するアプリロジックコンポーネント。

たとえば、この要素が多くのコマースサイトで見られる画像ズーム機能を置き換えたり標準化したりして、開発者からその苦痛を永遠に取り去ることができると私は想像できるでしょう。

Registering as a Share Target with the Web Share Target API

Paul Kinlan

Pete LePageがオリジントライアルでWeb Share Target APIとChromeの可用性を紹介します

Until now, only native apps could register as a share target. The Web Share Target API allows installed web apps to register with the underlying OS as a share target to receive shared content from either the Web Share API or system events, like the OS-level share button.

Read full post

このAPIはウェブ上のゲームチェンジャーです、それはネイティブのアプリで利用可能であったことがあるものへのウェブを開く:ネイティブの共有。アプリはサイロであり、それらはすべてのデータを吸い込み、プラットフォームを越えてアクセスするのを難しくします。共有ターゲットは、Webが同じゲームで遊ぶことができるように競技場を平準化し始めます。

Twitter Mobileの経験には、Share Target already enabledます。この投稿は私のサイトの管理者パネルのmanifest.json定義した共有ターゲットを使って作成されました - それは非常にうまく機能し、ファイルサポートがあればmanifest.jsonに自分のデバイス上のイメージやブロブをブログに投稿できるようになります。

とてもエキサイティングな時代です。

リンクされた記事を読んで、このAPIがいつ公開されるべきか、およびAPIの使用方法のスケジュールについて詳しく学んでください。

Why Build Progressive Web Apps: Push, but Don't be Pushy! Video Write-Up

Paul Kinlan

Thomas SteinerによるWeb上の優れたプッシュ通知に関する素晴らしい記事とビデオ、およびサンプルです。

A particularly bad practice is to pop up the permission dialog on page load, without any context at all. Several high traffic sites have been caught doing this. To subscribe people to push notifications, you use the the PushManager interface. Now to be fair, this does not allow the developer to specify the context or the to-be-expected frequency of notifications. So where does this leave us?

Read full post

Web Pushは驚くほど強力なAPIですが、悪用してユーザーを煩わせるのは簡単です。あなたのサイトにとって悪いことは、あなたが警告なしにプロンプトを出したためにユーザが通知をブロックした場合、あなたは再び尋ねる機会を得られないということです。

ユーザーを尊重して扱い、コンテキストはWebプッシュ通知の王者です。

Maybe Our Documentation "Best Practices" Aren''t Really Best Practices

Paul Kinlan

私たちのチームの素晴らしい技術ライターであるKayce Basquesは、既存のドキュメンテーションのベストプラクティスが技術的な資料を説明するためにどれほどうまく機能しているかを測定した彼の経験についてかなり驚くべき記事を書きました。この意味でのベストプラクティスは、テクニカルライティングの業界標準としてよく知られている場合もあれば、スタイルガイドを書いているあなた自身の会社の場合もあります。見てみな!

Recently I discovered that a supposed documentation “best practice” may not actually stand up to scrutiny when measured in the wild. I’m now on a mission to get a “was this page helpful?” feedback widget on every documentation page on the web. It’s not the end-all be-all solution, but it’s a start towards a more rigorous understanding of what actually makes our docs more helpful.

Read full post

私はテックライターではありませんが、私の役割は私たちのテックライティングチームとの大きな関わり合いと、開発者自身のためのたくさんの「ベストプラクティス」の発行も含みます。 Kayceが私たちのチームのコンテンツのレンズを通して現代の文書を書くことの芸術に関してどれだけの深さと研究をしたかに驚きました。 Kayceの記事を詳しく読むことを強くお勧めします - 私は多くのことを学びました。ケイシーありがとう!

Grep your git commit log

Paul Kinlan

Finding code that was changed in a commit

Read More

Performance and Resilience: Stress-Testing Third Parties by CSS Wizardry

Paul Kinlan

私は数週間前にGoogle Developer Dayで中国にいましたが、皆さんに私のQRCode scannerを公開していました。オフラインになるまではうまくいっていました。ユーザーがオフライン(または部分的に接続されている)の場合、カメラは起動せず、QRコードをスナップできませんでした。私は何が起こっているのか分かりませんでした。私は誤って onloadイベントでカメラを起動していました.Googleアナリティクスのリクエストはハングアップし、適時に解決されませんでした。それはそれを修正したこのコミットでした。

Because these types of assets block rendering, the browser will not paint anything to the screen until they have been downloaded (and executed/parsed). If the service that provides the file is offline, then that’s a lot of time that the browser has to spend trying to access the file, and during that period the user is left potentially looking at a blank screen. After a certain period has elapsed, the browser will eventually timeout and display the page without the asset(s) in question. How long is that certain period of time?

It’s 1 minute and 20 seconds.

If you have any render-blocking, critical, third party assets hosted on an external domain, you run the risk of showing users a blank page for 1.3 minutes.

Below, you’ll see the DOMContentLoaded and Load events on a site that has a render-blocking script hosted elsewhere. The browser was completely held up for 78 seconds, showing nothing at all until it ended up timing out.

全文を読む

私はあなたに大きな洞察力がたくさんあるので、投稿を読むことをお勧めします。

Chrome Bug 897727 - MediaRecorder using Canvas.captureStream() fails for large canvas elements on Android

Paul Kinlan

週末にはBoomerangエフェクトビデオエンコーダを使って遊んでいましたが、ほぼリアルタイムで動作させることができます(後で説明します)。私はそれをデスクトップ上のChromeで動作させましたが、AndroidのChromeでは正常に動作しません。 コードはここにあるを参照してください。

captureStream()を ` (私の場合は1280x720)、MediaRecorder APIはビデオをエンコードすることができず、エラーもなく、事前にビデオをエンコードできないことを検出できません。

(1) Capture a large res video (from getUM 1280x720) to a buffer for later processing. (2) Create a MediaRecorder with a stream from a canvas element (via captureStream) sized to 1280x720 (3) For each frame captured putImageData on the canvas (4) For each frame call canvasTrack.requestFrame() at 60fps

context.putImageData(frame, 0, 0); canvasStreamTrack.requestFrame();

Demo: https://boomerang-video-chrome-on-android-bug.glitch.me/ Code: https://glitch.com/edit/#!/boomerang-video-chrome-on-android-bug?path=script.js:21:42

What is the expected result?

For the exact demo, I buffer the frames and then reverse them so you would see the video play forwards and backwards (it works on desktop). In generall I would expect all frames sent to the canvas to be processed by the MediaRecorder API - yet they are not.

What happens instead?

It only captures the stream from the canvas for a partial part of the video and then stops. It’s not predicatable where it will stop.

I suspect there is a limit with the MediaRecorder API and what resolution it can encode depending on the device, and there is no way to know about these limits ahead of time.

As far as I can tell this has never worked on Android. If you use https://boomerang-video-chrome-on-android-bug.glitch.me which has a 640x480 video frame it records just fine. The demo works at higher-resolution just fine on desktop.

全文を読む

あなたが両方の作品で動作するデモで遊びたいのであればここをクリック

Why Microsoft and Google love progressive web apps | Computerworld

Paul Kinlan

マイク・エルガンのPWAに関する素晴らしい記事。マイクロソフトの目標はPWAではわかりませんが、私たちは非常に単純だと考えています。ユーザーがコンテンツや機能に即座にアクセスできるように、デバイス上で対話できるようにしたいと考えています。ウェブは、接続されたすべてのデバイスのすべての人に届くはずです。ユーザーは、自分の好みのモダリティにアクセスできる必要があります(モバイル、多分)、またはアシスタントなどでの音声

私たちはまだヘッドレスウェブから遠く離れている(0)が、記事では本当に私を驚かせた:

Another downside is that PWAs are highly isolated. So it’s hard and unlikely for different PWAs to share resources or data directly.

全文を読む

ウェブ上のサイトとアプリは分離されていないと思われますが、ウェブはリンク可能、インデックス可能、一時的です(0)。私たちは、プラットフォームがユーザーに簡単にサイト内外の*データを取得することを許可していないため、意図しないサイロを作成しています(1)。私はRDFやそのようなことについては言及していません。コピー&ペースト、ドラッグ&ドロップ、サイトへの共有、サイトからの共有などの基本的な操作は今日のWeb上で壊れています。と窓。

Building a video editor on the web. Part 0.1 - Screencast

Paul Kinlan

あなたは、ブラウザでウェブだけを使ってビデオを作成し編集することができます。 Screenflowに似たユーザーインターフェイスを提供することで、複数のビデオ、画像、およびオーディオを1つのビデオにまとめて、YouTubeなどのサービスにアップロードできる出力ビデオを作成できるようにする必要があります。 私の前の投稿からビデオエディタの要件を簡単に説明した後、この記事ではスクリーンキャストでウェブカメラレコーダーをどのように作成したか、そしてスクリーンキャストを構築する方法レコーダー:) それはすべてき​​ちんとしていて、新しい navigator.getDisplayMedia APIを使用します。これにより、ユーザーは画面コンテンツへのアクセスを許可することができます。以下のコードは、私がこのビデオを作成するために使用したすべてのものです。 ビデオは非常に非常に生であり、現時点で私はビデオを編集することができないので、多くの間違いがあります:)私の目標は、このプロジェクトの終わりに、私は良いビデオをエンドツーエンドで作ることができるということです。 このビデオのコードデモ window.onload = () => { if('getDisplayMedia' in navigator) warning.style.display = 'none'; let blobs; let blob; let rec; let stream; let voiceStream; let desktopStream; captureBtn.onclick = async () => { download.style.display = 'none'; desktopStream = await navigator.getDisplayMedia({video:true}); voiceStream = await navigator.mediaDevices.getUserMedia({video: false, audio: true}); let tracks = [...desktopStream.getTracks(), ...voiceStream.getAudioTracks()] console.log('Tracks to add to stream', tracks); stream = new MediaStream(tracks); videoElement.srcObject = stream; blobs = []; rec = new MediaRecorder(stream, {mimeType: 'video/webm; codecs=vp9,opus'}); rec.

Read More

894556 - Multiple video tracks in a MediaStream are not reflected on the videoTracks object on the video element

Paul Kinlan

最初の問題は、ウェブ上でビデオエディタを作成するです。

私は複数のビデオストリーム(デスクトップとウェブカム)を持っています。私はワンビデオ要素のビデオストリームを切り替えることができるようにしたいので、Webカメラとデスクトップを素早く切り替えることができ、 MediaRecorderを破ることはできません。

あなたが videoTracksオブジェクトのselectedプロパティを <video>要素が含まれていますが、トラックの配列に含まれる要素は1つだけです(MediaStreamの最初のビデオトラック)。

What steps will reproduce the problem? (1) Get two MediaStreams with video tracks (2) Add them to a new MediaStream and attach as srcObject on a videoElement (3) Check the videoElement.videoTracks object and see there is only one track

Demo at https://multiple-tracks-bug.glitch.me/

What is the expected result? I would expect videoElement.videoTracks to have two elements.

What happens instead? It only has the first videoTrack that was added to the MediaStream.

全文を読む

Repro case。

window.onload = () => {
  if('getDisplayMedia' in navigator) warning.style.display = 'none';

  let blobs;
  let blob;
  let rec;
  let stream;
  let webcamStream;
  let desktopStream;

  captureBtn.onclick = async () => {

       
    desktopStream = await navigator.getDisplayMedia({video:true});
    webcamStream = await navigator.mediaDevices.getUserMedia({video: { height: 1080, width: 1920 }, audio: true});
    
    // Always 
    let tracks = [...desktopStream.getTracks(), ... webcamStream.getTracks()]
    console.log('Tracks to add to stream', tracks);
    stream = new MediaStream(tracks);
    
    console.log('Tracks on stream', stream.getTracks());
    
    videoElement.srcObject = stream;
    
    console.log('Tracks on video element that has stream', videoElement.videoTracks)
    
    // I would expect the length to be 2 and not 1
  };

};

Building a video editor on the web. Part 0.

Paul Kinlan

あなたは、ブラウザでウェブだけを使ってビデオを作成し編集することができます。 Screenflowに似たユーザーインターフェイスを提供することで、複数のビデオ、画像、およびオーディオを1つのビデオにまとめて、YouTubeなどのサービスにアップロードできる出力ビデオを作成できるようにする必要があります。 この投稿は本当に単なる声明です。私はプラットフォーム上で利用できるものと利用できないものを整理し、今日までにどのくらい得ることができるかを見るための長いプロセスを開始するつもりです。 このプロジェクトのいくつかの考えの中で、私はカール・セイガンの瞬間を持っていました。つまり、リンゴ・パイを作るために宇宙を発明する代わりに、少なくともビデオ・エディタを構築するのに必要なすべてのツールを作成する必要があります。それを行うプロセス。この記事が存在するという事実は、私がいくつかの作品を用意して準備ができていることを知っているからです。 私は、他の誰かのためのビジネスでもある大規模なモノリシックな「ビデオエディタ」を作り出すつもりはないと思っていますが、私はそれを簡単にするために必要なすべての部分を工夫するつもりですウェブ上で素晴らしい動画を作成し、多くの人にウェブ上で可能なことを見せてもらいましょう。 以下は私の大まかな1ページプロジェクト計画です: 私が持っている使用例: *私は通常、Google I / OとChrome DevSummitのすべてのデバイスデモを記録し、オーバーレイなどを追加する必要があります。チームの全員がこれを行うことができます。 *チームはしばしばスクリーンキャストを記録し、シンプルなウェブサイトからすぐにそれを行い、最終的な出力をクリーンアップできるようにしたいと考えています。 *私は鋭く保つためにいくつかの製品を作る必要があります。 ;) 入力: [p0]マイクから音声を録音する [p0]ウェブカメラからビデオを録画する[完了 - 下記参照] [p0]ウェブ上にホストされている外部動画を埋め込む [p0]デスクトップを記録する [p1]リモートストリームを記録する [p1]&lt; canvas&gt;を記録します。素子 [p0]ローカルデバイスからファイルをロードする [p1]ローカルデバイスからファイルを共有する(android share intent) 操作: [p1]ウォーターマークを追加する [p1]フィルター効果を画像に追加する [p0]カスタム画像をレイヤーとして追加する [p0]ビデオとオーバーレイをキューに入れる [p0]オーディオとビデオの別々のトラックをオーバーレイする [p1]特定の時間にテキストを重ねる [p0]ビデオをサイズに切り抜く [p0]ビデオの位置決めとサイズ変更を有効にする [p0]ビデオ/オーディオのトリム [p0]スプライスビデオ/オーディオ 出力: [p0] webm形式のビデオファイル [p1] MTB情報 [p1] xyz形式のビデオファイル このビデオのコードデモ const init = () => { let blobs; let rec; let stream; captureBtn.onclick = async () => { stream = await navigator.

Read More

Barcode detection in a Web Worker using Comlink

Paul Kinlan

私はQRコードの大ファンです。実世界とデジタル世界の間でデータを交換するための非常にシンプルできれいな方法です。数年前から、私はQRSnapperと呼ばれる小さなプロジェクトを持っていました。まあそれはいくつかの名前を持っていますが、これは私が解決したものです&mdash;これは getUserMedia APIを使用してユーザのカメラからライブデータを取得し、ほぼリアルタイムでQRコードをスキャンすることができます。

このアプリの目標は、UIで60fpsを維持し、QRコードをすぐに検出できるようにすることでした。これは、検出コードをWebワーカー(かなり標準的なもの)に入れなければならないことを意味しました。この記事では、comlinkを使ってワーカーのロジックを大幅に単純化する方法を簡単に共有したいと思っていました。

qrclient.js

import * as Comlink from './comlink.js';

const proxy = Comlink.proxy(new Worker('/scripts/qrworker.js')); 

export const decode = async function (context) {
  try {
    let canvas = context.canvas;
    let width = canvas.width;
    let height = canvas.height;
    let imageData = context.getImageData(0, 0, width, height);
    return await proxy.detectUrl(width, height, imageData);
  } catch (err) {
    console.log(err);
  }
};

qrworker.js(ウェブワーカー)

import * as Comlink from './comlink.js';
import {qrcode} from './qrcode.js';

// Use the native API's
let nativeDetector = async (width, height, imageData) => {
  try {
    let barcodeDetector = new BarcodeDetector();
    let barcodes = await barcodeDetector.detect(imageData);
    // return the first barcode.
    if (barcodes.length > 0) {
      return barcodes[0].rawValue;
    }
  } catch(err) {
    detector = workerDetector;
  }
};

// Use the polyfil
let workerDetector = async (width, height, imageData) => {
  try {
    return qrcode.decode(width, height, imageData);
  } catch (err) {
    // the library throws an excpetion when there are no qrcodes.
    return;
  }
}

let detectUrl = async (width, height, imageData) => {
  return detector(width, height, imageData);
};

let detector = ('BarcodeDetector' in self) ? nativeDetector : workerDetector;
// Expose the API to the client pages.
Comlink.expose({detectUrl}, self);

私は本当にComlinkが大好きです。特に、スレッド間で動作する慣用JavaScriptを作成する場合、ライブラリのゲームチェンジャーだと思います。最後にここでうまくいくのは、ネイティブのバーコード検出APIをワーカー内で実行できるため、すべてのロジックがUIからカプセル化されているからです。

全文を読む

Running FFMPEG with WASM in a Web Worker

Paul Kinlan

私はFFMPEG.jsが大好きです。これは、asm.jsでコンパイルされた素敵なツールです。そして、私はビデオをすばやく編集できるJS Webアプリケーションを作成しましょう。 FFMPEG.jsもWebワーカーと連携して、メインスレッドをブロックせずにビデオをエンコードすることができます。

私はComlinkも大好きです。 Comlinkでは、複雑な postMessageステートマシンを扱わなくても、関数やクラスを公開することで、Webワーカーと簡単にやりとりすることができます。

私は最近、この2つを組み合わせる必要があります。私はFFMPEGをWebアセンブリにエクスポートして実験しました(これはうまくいきます)、現在のFFMPEG.jsプロジェクトでpostMessageの作業をすべてクリーンアップしたかったのです。以下は、コードが今のように見えるものです - 私はそれがかなりきちんとしていると思います。私たちにはffmpeg.jsとcomlinkをインポートするワーカーがいて、単にffmpegインターフェイスを公開するだけです。次に、workerを読み込んだ後、comlinkを使ってffmpeg APIへのプロキシを作成するWebページがあります。

きちんとした

worker.js

importScripts('https://cdn.jsdelivr.net/npm/comlinkjs@3.0.2/umd/comlink.js');
importScripts('../ffmpeg-webm.js'); 
Comlink.expose(ffmpegjs, self);

client.html

let ffmpegjs = await Comlink.proxy(worker);
let result = await ffmpegjs({
   arguments: ['-y','-i', file.name, 'output.webm'],
   MEMFS: [{name: file.name, data: data}],
   stdin: Comlink.proxyValue(() => {}),
   onfilesready: Comlink.proxyValue((e) => {
     let data = e.MEMFS[0].data;
     output.src = URL.createObjectURL(new Blob([data]))
     console.log('ready', e)
   }),
   print: Comlink.proxyValue(function(data) { console.log(data); stdout += data + "\n"; }),
   printErr: Comlink.proxyValue(function(data) { console.log('error', data); stderr += data + "\n"; }),
   postRun: Comlink.proxyValue(function(result) { console.log('DONE', result); }),
   onExit: Comlink.proxyValue(function(code) {
     console.log("Process exited with code " + code);
     console.log(stdout);
   }),
});

私はComlink、Workers、WASMのコンパイルされたモジュールが一緒に演奏できる方法が本当に好きです。私はWASMモジュールと直接対話する慣用的なJavaScriptを取得し、それはメインスレッドから実行されます。

全文を読む

Translating a blog using Google Cloud Translate and Hugo

Paul Kinlan

私は最近、Google4Indiaイベント(近いうちに報告)に出席し、多くの企業や開発者と出会うためにインドへの旅行から帰ってきました。議論された最も興味深い変更の1つは、国のユーザーの言語でより多くのコンテンツを求めていたことでした。特に、ユーザーの言語で検索しやすくすること、コンテンツを見つけること、テキストまたは音声形式でユーザにそれを読み戻すことができます。

旅行全体が私に考えさせてくれました。私のブログはHugoで構築されています。 Hugoは現在、複数の言語で書かれたコンテンツをサポートしています。 Hugoは完全に静的なので、新しいコンテンツを作成することは、新しいファイルを作成してビルドシステムに魔法をかけることの問題です。翻訳ツールを使用して静的コンテンツを実行することで、より多くの人がコンテンツを利用できるようにすることができます。なぜなら、コンテンツの翻訳者は非常に高額なためです。

私の飛行前にイギリスに帰国する数時間前に、自分のマークダウンファイルを取得し、Google Cloud Translateで実行してクイック検索を作成するスクリプトを作成しました私はすぐにホストすることができますページの翻訳。ソリューション全体を以下に示します。これは比較的基本的なプロセッサーで、「コード」を無視したHugoプリアンブルを無視し、プル・クォートを無視しています。これらは常に書かれたままにしておくことを前提としていました。

注:翻訳用のラーニングソフトウェアのように見えるので、学習ツールでGoogle Translatedコンテンツをアルゴリズムの入力として使用しないようにページをマークアップすることが重要です(https://cloud.google.com/translate/マークアップ)。

// Imports the Google Cloud client library
const Translate = require('@google-cloud/translate');
const program = require('commander');
const fs = require('fs');
const path = require('path');

program
  .version('0.1.0')
  .option('-s, --source [path]', 'Add in the source file.')
  .option('-t, --target [lang]', 'Add target language.')
  .parse(process.argv);

// Creates a client
const translate = new Translate({
  projectId: 'html5rocks-hrd'
});

const options = {
  to:  program.target,
};

async function translateLines(text) {
  if(text === ' ') return ' ';
  const output = [];
  let results = await translate.translate(text, options);

  let translations = results[0];
  translations = Array.isArray(translations)
    ? translations
    : [translations];

  translations.forEach((translation, i) => {
    output.push(translation)
  });

  return output.join('\n');
};

// Translates the text into the target language. "text" can be a string for
// translating a single piece of text, or an array of strings for translating
// multiple texts.
(async function (filePath, target) {

  const text = fs.readFileSync(filePath, 'utf8');

  const lines = text.split('\n');
  let translateBlock = [];
  const output = [];

  let inHeader = false;
  let inCode = false;
  let inQuote = false;
  for (const line of lines) {
    // Don't translate preampble
    if (line.startsWith('---') && inHeader) { inHeader = false; output.push(line); continue; }
    if (line.startsWith('---')) { inHeader = true; output.push(line); continue; }
    if (inHeader) { output.push(line); continue; }

    // Don't translate code
    if (line.startsWith('```') && inCode) { inCode = false; output.push(line); continue; }
    if (line.startsWith('```')) { inCode = true; output.push(await translateLines(translateBlock.join(' '))); translateBlock = []; output.push(line); continue; }
    if (inCode) { output.push(line); continue; }

    // Dont translate quotes
    if (inQuote && line.startsWith('>') === false) { inQuote = false; }
    if (line.startsWith('>')) { inQuote = true; output.push(await translateLines(translateBlock.join(' '))); translateBlock = []; output.push(line); }
    if (inQuote) { output.push(line); continue; }

    if (line.charAt(0) === '\n' || line.length === 0) { output.push(await translateLines(translateBlock.join(' '))); output.push(line); translateBlock = []; continue;} 

    translateBlock.push(line);
  }

  if(translateBlock.length > 0) output.push(await translateLines(translateBlock.join(' ')))

  const result = output.join('\n');
  const newFileName = path.parse(filePath);
  fs.writeFileSync(`content/${newFileName.name}.${target}${newFileName.ext}`, result);

})(program.source, program.target);

全体として、私はそのプロセスに非常に満足しています。機械翻訳は完璧ではないと私は考えていますが、英語ではなく自分の言語で検索している可能性のあるユーザーにコンテンツのリーチを広げることができると私は思っています。人。

これが実際に人々に役立つかどうかを確認するにはしばらく時間がかかりますので、データが増えたときに報告します。

Apple - Web apps - All Categories

Paul Kinlan

Web AppsがiPhoneでアプリを使用するために推奨される方法を覚えていますか?

What are web apps? Learn what they are and how to use them.

全文を読む

2013年頃、Appleは/ webapps /トップレベルのディレクトリを/ iphone /

ことは、ディレクトリは実際にはかなり良かった、そこのアプリの多くは今日も動作しています。しかし、AppStoreを見ると、開発者が持っていたより多くの問題が解決されました。 AppStoreはまた、支払いに関してユーザーや開発者から取り除かれた摩擦を具体的に紹介し始めました。

Gears API

Paul Kinlan

私は初期のモバイルWeb APIに関するブログ記事を書いているし、Alex RussellはGoogle Gearsを思い出させた

Gears modules include:

  • LocalServer Cache and serve application resources (HTML, JavaScript, images, etc.) locally
  • Database Store data locally in a fully-searchable relational database
  • WorkerPool Make your web applications more responsive by performing resource-intensive operations asynchronously

全文を読む

AppCacheとWebSQL、GeolocationとWebWorkersはGoogle Gearsのアイデアから出てきて、実際に生き残ったのは後者の2つだけです。 WebSQLは決して広くサポートされておらず、IndexedDBに置き換えられました。 ServiceWorkerに置き換えられたAppCache

RSS Feed to Google Chat Webhook using Cloud Functions for Firebase and Superfeedr

Paul Kinlan

私たちはGoogleチャットを内部的に使用してチーム全体でコミュニケーションを取っています。また、RSSフィードを介してアクセスできる多くのコンテンツを作成し、すべて見ることができるチームフィードを持っています。最近まで、WebHooks(https://developers.google.com/hangouts/chat/how-tos/webhooks)経由で単純なポストオンリーボットを作成することはかなり簡単であることがわかりました。私にはアイデアが与えられました。私はRSSフィードをポーリングして、私たちのチームチャットに直接投稿できるWebhookに送信する簡単なサービスを作成できます。

最後はかなりシンプルで、以下のコードをすべて含めました。私はFirebaseの機能を使用しました - これは他のサービス機能サイトやSuperfeedrと同じように簡単だと思われます。 SuperfeedrはPubsubhubbub ping(現在WebSub)を聞くことができるサービスで、Pubsubが設定されていないRSSフィードもポーリングします。フィードが見つかると、新しく見つかったフィードデータをXMLまたはJSON形式で構成されたURL(私の場合、FirebaseのCloud機能)にpingを実行します。データを解析して何かをするだけです。

const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const fetch = require('node-fetch');
const app = express();

// Automatically allow cross-origin requests
app.use(cors({ origin: true }));

app.post('/', (req, res) => {
  const { webhook_url } = req.query;
  const { body } = req;
  if (body.items === undefined || body.items.length === 0) {
    res.send('');
    return;
  }

  const item = body.items[0];
  const actor = (item.actor && item.actor.displayName) ? item.actor.displayName : body.title;

  fetch(webhook_url, {
    method: 'POST',
    headers: {
      "Content-Type": "application/json; charset=utf-8",
    },
    body: JSON.stringify({
      "text": `*${actor}* published <${item.permalinkUrl}|${item.title}>. Please consider <https://twitter.com/intent/tweet?url=${encodeURIComponent(body.items[0].permalinkUrl)}&text=${encodeURIComponent(body.items[0].title)}|Sharing it>.`
    })  
  }).then(() => {
    return res.send('ok');
  }).catch(() => {
    return res.send('error')
  });
})
// Expose Express API as a single Cloud Function:
exports.publish = functions.https.onRequest(app);

完全な記事を読む

私は驚いて、セットアップがいかに簡単かについて喜んでいました。

Using HTTPArchive and Chrome UX report to get Lighthouse score for top visited sites in India.

Paul Kinlan

A quick dive in to how to use Lighthouse,HTTPArchive and Chrome UX report to try and understand how users in a country might experience the web.

Read More