OmniAICreator/Qiita-1.07M huggingface dataset:
Dockerレジストリとローカルでコンテナイメージのダイジェストが違う

いつも忘れるので備忘録：

# 概要
例えば以下のように `nginx` イメージを `pull` してプライべートレポジトリに `push` すると、
```bash
[root@localhost ~]# docker pull nginx
[root@localhost ~]# docker tag docker.io/library/nginx myregistry.example.com/test-repo/nginx:latest
[root@localhost ~]# docker push myregistry.example.com/test-repo/nginx:latest
```

ローカルのコンテナイメージのダイジェストは `IMAGE ID` にあるとおり、`e4720093a3c1...` となっている。
```bash
[root@localhost ~]# docker images nginx
REPOSITORY                                TAG         IMAGE ID      CREATED      SIZE
myregistry.example.com/test-repo/nginx    latest      e4720093a3c1  2 weeks ago  191 MB
docker.io/library/nginx                   latest      e4720093a3c1  2 weeks ago  191 MB

[root@localhost ~]# docker inspect --format='{{.Id}}' myregistry.example.com/test-repo/nginx:latest
e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a
```

しかしプライベートレポジトリ側のコンテナイメージのダイジェストを確認すると、ローカルとは異なり `30ddea...` となっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3084968/8579dcbd-873d-7264-17af-cd8b44fdad47.png)

イメージを `pull` する際もプライベートレポジトリ側のダイジェストを使用しなくてはならない。
```bash
/* ローカル側ダイジェストだと not found になる */
[root@localhost ~]# docker pull myregistry.example.com/test-repo/nginx@sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a
Trying to pull myregistry.example.com/test-repo/nginx@sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a...
WARN[0000] Failed, retrying in 1s ... (1/3). Error: initializing source docker://myregistry.example.com/test-repo/nginx@sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a: reading manifest sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a in myregistry.example.com/test-repo/nginx: unknown: artifact test-repo/nginx@sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a not found
 :

/* レポジトリ側のダイジェストを使用すれば問題ない */
[root@localhost ~]# docker pull myregistry.example.com/test-repo/nginx@sha256:30ddeabcda3d58fc6f46458dd202e381766eb37152719c1fd1b36cfafb8c18d6
Trying to pull myregistry.example.com/test-repo/nginx@sha256:30ddeabcda3d58fc6f46458dd202e381766eb37152719c1fd1b36cfafb8c18d6...
Getting image source signatures
Copying blob 0993b329f149 skipped: already exists
Copying blob 52f06d23eddc skipped: already exists
Copying blob 0dfc8eb18e7a skipped: already exists
 :

```

# 原因
紛らわしいが、ローカルと Docker レポジトリ側ではそれぞれ別のダイジェストを使用（算出）しているので、この二つの値は必ずしも一致するものではないらしい。

＃以下の github でも同様のことが議論されていた
https://github.com/docker/hub-feedback/issues/1925

そのため、イメージを `push` した後にダイジェストで `pull` する場合は実際のダイジェスト値をレポジトリ側で再確認する必要があり、ややこしい。
なお、一度レポジトリに `push` したイメージを再度 `pull` すれば `ReepoDigests` を確認することでレポジトリ側のダイジェスト値を見ることができる。
```
/* いったんローカルは削除して再度 pull */
[root@localhost ~]# docker rmi e4720093a3c1
[root@localhost ~]# docker pull myregistry.example.com/test-repo/nginx:latest
 :

[root@localhost ~]# docker inspect --format='{{index .RepoDigests}}' myregistry.example.com/test-repo/nginx
[myregistry.example.com/test-repo/nginx@sha256:30ddeabcda3d58fc6f46458dd202e381766eb37152719c1fd1b36cfafb8c18d6]
```
[Google Sheets API] 指定した範囲を並び替える

Google Sheets API v4（Java）を使用して、
スプレットシートの指定したセルの範囲を並べ替える方法
についてご紹介します。

メニュー：「データ」 ⇨ 「範囲を並び替え」
で実行できる内容と同じです。

:::note info
APIを利用する環境の準備から始める場合や、コードを実行する際は、
[⧉[Google Sheets API] Google Sheets API v4をJavaで操作する](https://qiita.com/doran/items/3d43f776188b2281044c)
を参照ください。
```
例：requests.add(moveDimension(sheetId)); //実行したいリクエストのMethodを指定
```
:::

:::note info
その他のGoogle Sheets APIは、下記の記事を参照ください。
[⧉[Google Sheets API] JavaでスプレットシートのメニューとAPIを関連づけてみた](https://qiita.com/doran/items/2f6f8932b994fbd1a170)
:::

## 指定した範囲を並び替える
指定したセルの範囲を並び替えることができます。

並び順はSortSpecクラスのsetSortOrderメソッドで指定します。
・降順：DESCENDING
・昇順：ASCENDING

範囲(GridRange)の指定で
・範囲を指定しない場合、範囲はシート全体が対象
・終了行・列を指定しない場合、開始行・列からデータがある行・列までが対象
となります。

**サンプル コード**
A列を降順にD列を昇順に並び替えます。
SortSpecリストの最後から順番に並び替えが実行されます。
```
public Request sortRange(Integer sheetId) {
    Request request = new Request();

    //範囲の設定
    GridRange range = new GridRange();
    range.setSheetId(sheetId);                  //シートID
    range.setStartRowIndex(1);                  //開始行(0スタート)
    //range.setEndRowIndex(8);                  //終了行(1スタート)
    range.setStartColumnIndex(0);               //開始列(0スタート))
    //range.setEndColumnIndex(8);               //終了列(1スタート)

    //並び替えルールの指定
    List<SortSpec> sortSpecs = new ArrayList<>();
    SortSpec sort1 = new SortSpec();
    sort1.setSortOrder("DESCENDING");           //降順にソート
    sort1.setDimensionIndex(0);                 //0列目(A列)に並び順を指定
    sortSpecs.add(sort1);
    SortSpec sort2 = new SortSpec();
    sort2.setSortOrder("ASCENDING");           //昇順にソート
    sort2.setDimensionIndex(3);                 //3列目(D列)に並び順を指定
    sortSpecs.add(sort2);
    
    SortRangeRequest r = new SortRangeRequest();
    r.setRange(range);
    r.setSortSpecs(sortSpecs);
    
    request.setSortRange(r);
    return request;
}
```
**実行結果**
D列を昇順、A列を降順の順番で並び替えます。
<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114621/dbf92c03-6b2c-e961-72b6-21fda8f46a7f.png">
並び変わりました。
<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114621/09ab8c08-3f34-84b3-cb26-44db15c0ff79.png">

<br>
おしまい。。
構造体の更新

https://paiza.jp/works/mondai/class_primer/class_primer__change

前回の記事のコメントで教えていただいた方法を参考に。
配列の特定のインデックスのその中の構造体の要素の更新方法が
わからず、色々調べつつ。
勉強になりました。。。

```py
class Member():
    def __init__(self, name, old, birth, state):
        self.name = name
        self.old = int(old)
        self.birth = birth
        self.state = state

    def __str__(self):
        """表示文字列に変換する"""
        return f"{self.name} {self.old} {self.birth} {self.state}"


N,K= map(int,input().split())
members = [Member(*input().split()) for _ in range(N)]

for _ in range(K):
    t_index,t_name = input().split()
    members[int(t_index)-1].name = t_name

for member in members:
    print(member)

```

＊＊＊
別件で、
そういえば、こないだ、メールでBランク問題のスキルチェックに挑戦したら
アマゾンのクーポンがもらえるとかで、参加してみたんですが撃沈しました。

調べた結果なのですが、まあ、解答がわからないので、とりあえずテストを
して修正していくしかないのですが、
私の場合は3データで失敗になって、（基本データ１つ、境界値データ２つ）
そこでお手上げになりました。（データがわからない以上、手のうちようがなく）

うーん。みんなテスト用の基本データとか境界値データとかってどうやって作って
開発したプログラムに入力させてるんだろうか。
そこを知らないとPaizaで上に行けないような気がする。
一応メモっとく。
とりあえずBランクの問題集をやっていけばいつかはわかるようになるのだろうか？

***

とかいいつつ、いま新しいスキルチェック問題をやったらBランクになりました。。。
よかったです。（でもなかなか難しかった）
やっぱりレベルアップ問題集での経験が生かされてますね。。。

(2025.6.7追記）

今構造体の復習をしていて、これってもっとクラスメソッドにしちゃっていいんじゃない？
と思ったので書いてみた。


```python
class Member():
    def __init__(self,nickname,old,birth,state):
        self.nickname = nickname
        self.old =int(old)
        self.birth = birth
        self.state = state
        
    def __str__(self):
        return f'{self.nickname} {self.old} {self.birth} {self.state}'
    
    # このメソッドを増やした
    def changeName(self, name):
        self.nickname = name

N,K = map(int,input().split())
members =[]
for _ in range(N):
    member = Member(*input().split())
    members.append(member)
    
for _ in range(K):
    num,name = input().split()
    # こう変更した
    members[int(num)-1].changeName(name)

for member in members:
    print(member)


```
そもそも。changeNameってヒント、問題に書いてあった。
もしかしてこういうことかな？と思ったけどどうだろうね。
でも見直してみてもこれが一番いい書き方な気がする。
OAuthについて

<br>
業務で使用する機会があったのでまとめます。

# OAuthとは何か

そもそもOAuth(オーオース)とは何か？についてですが、
ユーザーに代わってアプリケーションが別のWebサービスを利用できるように認証する機能のことです。
通常、Webサービスを利用するためは、個別にユーザーIDとパスワードを入力してユーザーを認証する必要があります。
OAuthを利用することで、IDやパスワードを入力することなく、アプリケーション間の連動ができるようになります。

文章だけだとよくわからないと思うので次の章で図を用いて説明します。

# 処理の流れ
### 前提
- OAuthにて登場するエンドポイントは以下３つ
「クライアント」「認可サーバ」「APIサーバ」であり、開発者は「クライアント」のアプリケーションを開発しているとします
- ここではAPIの認証に使用するとします
<br><br>

![OAuthフロー図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3726673/aab970fd-4bb5-e489-1596-4cae609426bb.jpeg)

1. **「クライアント」から「認可サーバ」にトークンリクエストを送信**
事前に取得しているクライアントIDとクライアントシークレットを含めたトークンリクエストを送信します。
※クライアントIDとクライアントシークレットはあらかじめ「認可サーバ」で生成し、「クライアント」アプリケーションに渡しておきます。

2. **「認可サーバ」から「クライアント」にアクセストークンを返却**
トークンリクエストに含まれるクライアントID とクライアントシークレットをチェックしクライアントの身元を確認します。
チェックしOKであればアクセストークンを生成し返却します。

3. **「クライアント」から「APIサーバ」にAPIリクエストを送信**
「認可サーバ」から取得したアクセストークンを含めたAPIリクエストを送信します。

4. **「APIサーバ」から「クライアント」にAPIレスポンス返却**
APIリクエストに含まれるアクセストークンを検証しOKであればレスポンスを返却します。
もしアクセストークンが間違っていれば認証エラーとなり、よく見る401 Unauthorizedという応答が返ってきます。

# OAuth導入によるメリット

従来のID・パスワードベースの認証ではなく、トークンベースの認証であることが特徴です。
それにより以下のようなメリットがあります。
- 直接IDやパスワードを入力する必要がないため、IDやパスワードが盗まれる心配がない
- トークンには限られた権限のみしか与えられないので、トークンが盗まれても被害が最小限になる
- トークンには有効期限が設定されており、トークンが盗まれても有効期限が切れると使用不可になる

# 最後に

今後ますますセキュリティ意識が高まってくるため、こういった認証まわりの技術は適宜インプットしていきたいと思います。

## 参考文献

https://note.com/shinya_matsui/n/n6d008d9cc30c

https://qiita.com/TakahikoKawasaki/items/63ed4a9d8d6e5109e401

https://logmi.jp/tech/articles/325886
説明リスト

・使いどころ
資格の説明をしているところで使う
Q&Aなどで使う

```html
<dl>
    <dt>第2種電気工事士</dt>
    <dd>600vまでの電気工事をすることができる。</dd>
    <dt>たぬきについて</dt>
    <dd>海外ではラクーンドックといます。</dd>
</dl>
```
・dlとは
説明りすと

・dtとは
説明したいこと、題材の名前など

・ddとは
dtに関する説明など
SSH 公開鍵認証のための設定

&emsp;Windows のユーザー tanaka が Linux マシン svr（IP アドレス：192.168.1.5）に tanaka のアカウントでログインするケースを例に次のコマンドで SSH 公開鍵認証を行うための設定について記す。VSCode の [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) を利用するユーザーにとっては必須の設定である。

```shell
% ssh svr
```

## 既製の鍵の確認 

&emsp;ログイン時に保管庫内の秘密鍵を参照する [ssh-agent](https://zenn.dev/naoki_mochizuki/articles/ce381be617cd312ffe7f) というサービスを利用する。このサービスが実行されているか否かは次のコマンドで確認することができる。

```shell
% sc query ssh-agent
```

&emsp;ここで STATE が STOPPED になっている場合は**管理者としてコマンドプロンプトを開いて**次のコマンドを実行した後にウィンドウを閉じる。

```shell
% sc start ssh-agent
```

&emsp;元のコマンドプロンプトに戻り [ssh-add](https://qiita.com/Yarimizu14/items/6a4bab703d67ea766ddc) コマンドで鍵の登録状況を確認する。

```shell
% ssh-add -l
```

&emsp;ここで何も表示されない場合は次項に進み、カレントディレクトリに拡張子が `.pub` であるファイルが存在する場合[^1]は<a href="#公開鍵の転送">「公開鍵の転送」</a>に進む。これが存在しない場合は次のコマンドを実行して公開鍵を再生したうえで<a href="#公開鍵の転送">「公開鍵の転送」</a>に進む。

```shell
% powershell -command "ssh-add -L | select-object -first 1" | findstr /v /r /c:"^\ *$" > id_rsa.pub
```

## 鍵の生成・登録

&emsp;[ssh-keygen](https://qiita.com/c_tomioka/items/379a9288d44634ca1daa) を実行して `.ssh` に秘密鍵 `id_rsa` と公開鍵 `id_rsa.pub` を生成する。

```shell
% cd C:\Users\suzuki\.ssh
% ssh-keygen
```

&emsp;この際「Enter passphrase (empty for no passphrase):」と「Enter same passphrase again:」に対してはパスフェーズ（例えば win）を入力する。</br>
&emsp;次いで以下のコマンドで秘密鍵を ssh-agent に登録する。途中「Enter passphrase for id_rsa:」に対して上記のパスフェーズを入力する。

```shell
% ssh-add id_rsa 
```

## 公開鍵の転送

&emsp;過去に IP アドレスが 192.168.1.5 のサーバーとの接続があると失敗するので、念のためこれをチェックしたうえで [scp](https://qiita.com/chihiro/items/142ebe6980a498b5d4a7) コマンドで svr に公開鍵を転送する。

```shell
% ssh-keygen -R 192.168.1.5
% scp id_rsa.pub tanaka@192.168.1.5:/home/tanaka
```

&emsp;パスワードの入力後に表示される「Are you sure you want to continue connecting (yes/no/[fingerprint])?」に「yes」で応答すると svr のサーバーのホスト鍵が `.ssh\known_hosts` に追加される。

## 公開鍵の登録

&emsp;パスワード認証で svr にログインする。

```shell
% ssh tanaka@192.168.1.5
```

&emsp;`/home/tanaka/` に `.ssh` ディレクトリが存在しない場合は以下を実行する。

```shell
$ mkdir .ssh
$ touch .ssh/authorized_keys
$ chmod 700 .ssh
$ chmod 600 .ssh/authorized_keys
```

&emsp;転送済の `id_rsa.pub` を `.ssh/authorized_keys` に追加する。

```shell
$ cat id_rsa.pub >> .ssh/authorized_keys
$ rm id_rsa.pub
$ exit
```

## 最終テスト

&emsp;交換鍵認証での接続をテストする。

```shell
% ssh -vA tanaka@192.168.1.5
```

&emsp;パスワードが求められる場合は「Cntrl+c」で接続を絶ち、表示されているログから原因をつきとめて対処する。</br>
&emsp;成功した場合は秘密鍵を削除する。

```shell
% del id_rsa
```

## .ssh\config の編集

&emsp;入力を簡略化するため `.ssh\config` に以下の行を追加する。以降、どこからでも冒頭に示したコマンドで svr との交信が可能になる。

```text
Host svr
    Hostname 192.168.1.5
    User tanaka
    ForwardAgent yes
```

[^1]: ファイル名が `id_rsa.pub` 以外である場合は以下に記事における `id_rsa.pub` をそれに置き換える。
FastAPI のテレメトリデータを Azure Application Insights に送る

タイトルの通り、FastAPI サーバのテレメトリデータを Azure Application Insights に送りたくて試行錯誤したのでそのメモ。

## 初期設定
[azure-monitor-opentelemetry](https://pypi.org/project/azure-monitor-opentelemetry/) と [opentelemetry-instrumentation-fastapi](https://pypi.org/project/opentelemetry-instrumentation-fastapi/) を入れて、下記のように設定すれば動いた。

``` python:app.py
from os import getenv

from azure.monitor.opentelemetry import configure_azure_monitor
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

app = FastAPI()

APPLICATIONINSIGHTS_CONNECTION_STRING = getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
if APPLICATIONINSIGHTS_CONNECTION_STRING:
    configure_azure_monitor(connection_string=APPLICATIONINSIGHTS_CONNECTION_STRING)
    FastAPIInstrumentor.instrument_app(app)
```

ただしハマったところとして、Application Insights リソースが配置されているリソースグループ名に日本語が含まれていた場合にデータを受け取ってくれなかった。リソースグループ名に非 ASCII 文字を使うのはやめよう...。


## Exception を収集する
デフォルトで Exception は収集してくれているっぽい。

試しにこういうエンドポイントを作ってアクセスしてみると、Application Insights の画面でエラー情報を見ることができた。

``` python
@app.get("/")
async def get_root():
    1 / 0  # ERROR
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16369/88e8b277-f890-4393-9afb-4d6c7ccebcc7.png)

## アクセスしてきたユーザの ID を収集する
デフォルトでリクエストログは収集してくれているが、そのままだと「誰が」アクセスしてきたログなのかわからないので、ユーザ ID を追加で送信できるようにする。

``` python:app.py
from os import getenv

from azure.monitor.opentelemetry import configure_azure_monitor
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.trace import Span

app = FastAPI()


APPLICATIONINSIGHTS_CONNECTION_STRING = getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
if APPLICATIONINSIGHTS_CONNECTION_STRING:

    def server_request_hook(span: Span, scope: dict):
        if span and span.is_recording():
            try:
                # Application Insights に送るデータにユーザ ID を追加する
                user_id = ...  # TODO: よしなにユーザ ID を取得する
                span.set_attribute("enduser.id", user_id)
            except KeyError:
                pass

    configure_azure_monitor(connection_string=APPLICATIONINSIGHTS_CONNECTION_STRING)
    FastAPIInstrumentor.instrument_app(app, server_request_hook=server_request_hook)
```

FastAPIInstrumentor の server_request_hook でカスタム属性を設定することができて[^fastapi_hook]、Azure Application Insights の場合は `enduser.id` 属性を送信すると `requests.user_Id` として保存してくれるらしい。[^azure_user_id]

server_request_hook がいつ実行されるのかは詳しくわかっていないが、引数 `scope` から得られる情報を見る限りではレスポンスを返したあとっぽい。

[^fastapi_hook]: https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/fastapi/fastapi.html#request-response-hooks
[^azure_user_id]: https://learn.microsoft.com/ja-jp/azure/azure-monitor/app/opentelemetry-add-modify?tabs=python#set-the-user-id-or-authenticated-user-id
React V19に追加される機能（予定）


[Reactの次期Version Upについて](https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024)の情報が出ましたね！

簡単にまとめたいと思います！


## React Compiler ( React Forget )

いわゆる"Memoization"に関して、`useMemo`、`useCallBack`そして`memo`を書かずとも、React Compilerが同等の処理をJavaScriptにコンパイルしてくるとのことです！

> Our vision is for React to automatically re-render just the right parts of the UI when state changes, without compromising on React’s core mental model.

とても心強いですね。

もちろん、"Memoization"のみが不要なRe-renderに影響を与えるわけではないですが、ここの自動付与により、コード可読性アップや、より関心の高いパフォーマンス課題に注力できますね。`memo`の一文でパフォーマンスが激的に改善された経験があるのでとても楽しみです。

また、コンパイル時の静的チェックに関しても、`<StrictMode>`、`eslint-config-react-app`で設定されるようなルールに沿っていれば、将来的なコンパイル処理にも適応できるとのことです。

React Forgetに関するデモは[こちら](https://www.youtube.com/watch?v=qOQClO3g8-Y)から！

## Actions

Next.Jsでは既におなじみかもしれませんが、React的に正式Versionとしてリリースされるそうです。

`useFormState`、`useFormStatus`のHookも追加され、通信状態や結果をClientサイドで取得可能です。

また、`useOptimistic`も追加され、例えば、Submitするデータを一時的に正として表示し、Submit結果が返ってきたらその値を表示するということが可能になります。

詳細は以下をどうぞ！

[useFormState](https://react.dev/reference/react-dom/hooks/useFormState#usage)

[useFormStatus](https://react.dev/reference/react-dom/hooks/useFormStatus#usage)

[useOptimistic](https://react.dev/reference/react/useOptimistic#usage)

## `use client` `use server`

こちらもNext.Jsでは安定板として使われていますが、ReactとしてはCanaryです。それらが、正式Versionとしてリリースされるので、Next.Js以外のReact Frameworkでも利用がさらに加速されますね！

## Document Metadata

`React Helmet`よ、さようなら、いままでありがとうです。

CSP追加などもBuilt-inで簡単にできそうですね！

## Asset Loading

[Suspense](https://react.dev/reference/react/Suspense)をリソースライフサイクルにも統合し、`<style>`、`<link>`、`<script>`などのリソースローディング周りをもっとコントロールできるようになるようです。

## Web Component

[Web Component](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)へのサポートも対応するそうです。

恥ずかしながら、"Web Component"という言葉を初めて聞きました。調べてみます！

## 最後に

>At the time of the talk, we had early experimental data from trying React Compiler on one page of instagram.com. Since then, we shipped the compiler to production across instagram.com.

React Forgetに関しては、instagramに統合済み、

>The current set of features in React Canary are complete and ready to release.

現在のCanary versionもOKとのことなので、V19のリリースを心待ちにしましょう！
ubuntu20.04LTS --> ubuntu22.04LTS に変更した際に更新が必要だったもの備忘録

ubuntu20.04LTS --> ubuntu22.04LTS に変更した際に更新が必要だったもの備忘録

## `libgc1c2 not found`

代わりに以下を実施する

```
apt install -y libgc1
```

## `python3-crypto not found`

代わりに以下を実施する

```
apt install -y python3-pycryptodome
```

## `java 11 not found`

`add-apt-repository --yes ppa:rpardini/adoptopenjdk`辺りが期待通りに動かないことがあった
代わりに以下を実施する

```
apt install -y openjdk-11-jdk
```

#　参考
https://jdk.java.net/11/
デスクトップアプリでキャンセル可能なダイアログを作るときのデータのやり取り

# デスクトップアプリでキャンセル可能なダイアログを作るときのデータのやり取り

## はじめに

質問されて、確かに自分もどう作るのが良いのか分かってなかったなぁ（今も正解なのか？は厳密にはわかっていませんが）と思ったので、このタイトルで記事を書いてみました。

デスクトップアプリでOK, キャンセルボタンのあるダイアログを作るとき、OKが押されたらモデルにデータを反映し、キャンセルが押されたら何もしない、という挙動になります。
シンプルにモデルを追加・削除するぐらいなら簡単に作れると思うのですが、編集することになると少しだけ話が変わってきます。追加・削除とは異なり、編集した結果をモデルに反映する必要があるからです。

上記の状況において、メリットデメリットも考えつついくつか自分が考えた設計のバリエーションを疑似コードを用いて紹介します。
こういうのもあるよ！という方がいらっしゃればコメントいただけますと幸いです。
以下のパターンでは原則として MVVM を想定しています。

## パラメータを利用するパターン

### 概要

このパターンではダイアログのVMのコンストラクタにパラメータを渡し、編集した結果を受け取る方式です。
例えば、以下のようなイメージでしょうか。

```cs

// NameとDescriptionを持つモデル
public IModel Model {get;set;}

public void ShowDialog()
{
    // モデルの名前と説明を編集するダイアログの表示
    // コンストラクタに渡した値が初期値となる想定
    var vm = new XXXDialogViewModel(Model.Name, Model.Description);
    var v = new XXXDialog() { DataContext = vm };
    var result = v.ShowDialog();

    if(result != null && result == false) return;

    // ダイアログの編集結果を反映
    Model.Name  = vm.Name;
    Model.Description = vm.Description;
}

```

### メリット・デメリット

- メリット
    - モデルとの依存なく作れるためシンプル
- デメリット
    - 編集したいプロパティが増えるほどパラメータが増えていく
    - ダイアログの編集結果を反映する手間がかかる

## モデルをクローンして利用するパターン

このパターンではダイアログのVMのコンストラクタにモデルのクローンを渡し、編集した結果を受け取る方式です。

```cs

// NameとDescriptionを持つモデル
// クローン可能なメソッド（Clone()メソッド）を実装している想定
public IModel Model {get;set;}

public void ShowDialog()
{
    // モデルの名前と説明を編集するダイアログの表示
    // コンストラクタに渡したクローンを利用して編集結果を受け取る
    var model = (IModel)Model.Clone();
    var vm = new XXXDialogViewModel(model);
    var v = new XXXDialog() { DataContext = vm };
    var result = v.ShowDialog();

    if(result != null && result == false) return;

    // ダイアログの編集結果を反映
    Model.Apply(model);
}

```

### メリット・デメリット

- メリット
    - パラメータが増えたり、あるいはモデル構造が階層構造で複雑なものだったりしても、モデルを渡しているので引数の変更が不要
    - ダイアログの編集結果を反映するのも比較的シンプル
- デメリット
    - クローン、反映処理にコストをかけてしまう可能性がある
        - おそらくリフレクションを用いるのでそれもコストを書けてしまう要因のひとつになる
    - モデルとの関連が生まれる

## DTOを利用するパターン

### 概要

このパターンではダイアログのVMのコンストラクタにモデルのDTOを渡し、編集した結果を受け取る方式です。

```cs

// NameとDescriptionを持つモデル
// DTOを生成できるメソッド（CreateDTO()メソッド）を実装している想定
public IModel Model {get;set;}

public void ShowDialog()
{
    // モデルの名前と説明を編集するダイアログの表示
    // コンストラクタに渡したDTOを利用して編集結果を受け取る
    var dto = Model.CreateDTO();
    var vm = new XXXDialogViewModel(dto);
    var v = new XXXDialog() { DataContext = vm };
    var result = v.ShowDialog();

    if(result != null && result == false) return;

    // ダイアログの編集結果を反映
    Model.Apply(dto);
}

```

### メリット・デメリット

DTOを利用した場合はパラメータとモデルをクローンして利用する場合の中間ぐらいのメリット・デメリットになるイメージです。

- メリット
    - パラメータが増えたり、あるいはモデル構造が階層構造で複雑なものだったりしても、DTOを渡しているので引数の変更が不要
    - ダイアログの編集結果を反映するのも比較的シンプル
    - モデルをクローンするよりは軽量（なはず）
    - モデルとの直接的な関連ができない
- デメリット
    - クローン、反映処理に（モデルのクローンと比較すれば小さいはずだが）コストをかけてしまう可能性がある
    - クラス数がDTOの分だけ増加するため、見通しが段々と悪くなる可能性がある。モデルと合わせてのメンテが必要になる可能性がある

## おわりに

色々とパターンを紹介してきましたが、結局何がいいのか？については要件によるため難しいです。
ただ、一個人の意見としては、非機能要件が特別厳しいのでなければ正直モデルをクローンして使うで問題ないのかなとは思っています。
今回は色々設計案を整理するきっかけになったので、また疑問に思ったこと、初心者の頃によくわからなかったことを思い返して記事にしていきたいと思います。
サクラエディタ　適用タイプの変更マクロ

# マクロ作成の動機

サクラエディタは拡張子から適用するタイプを判断してくれる。
ファイルを開いたあとも、タイプ別設定一覧で適用するタイプを選択して「一時適用」を押せば表示中ファイルの適用タイプを変更できる。

私は普段ファイル名に`.`(ピリオド)を含められないサーバを扱っており、作業端末（Windows）でもそのサーバのファイルは拡張子なしで扱っている。
設定している拡張子以外のファイルはタイプ「基本」で表示されるため、毎回ファイルタイプに合わせてタイプ別一覧からタイプを選択して適用する必要がある。

このタイプ変更を少しだけ楽にするため、適用タイプの変更マクロを作成することにした。

なお、「基本」で表示された状態からタイプ変更をする点は変わらないため、作成したマクロのツールボタンやショートカットキー設定をしてようやく少し楽になる。
→　[マクロの利用](#マクロの利用) 参照


:::note info
拡張子判定で適用されるタイプ

同じ拡張子で複数タイプ登録している場合、拡張子によるタイプ判定ではタイプ別設定一覧の上にあるタイプが適用されます。
タイプ「基本」は設定番号（１番目）や拡張子未設定を変更することができず、一覧に登録されている拡張子以外のファイルは「基本」が適用されるようになっています。
:::

# マクロでのタイプ変更

タイプ変更はマクロ関数 `ChangeType` で適用するタイプ別設定番号を指定する。

タイプ別設定番号は、「基本」が１、その下のタイプが２と、タイプ別設定一覧の上から何番目かを示している。

## マクロ例
例えば、タイプ別設定一覧が以下の場合

1. 基本
1. テキスト (txt,log)
1. TACL
1. NSS LOG (txt,log)


例１）３番目のタイプ「TACL」に設定するマクロ
```:SK_setType_TACL.mac
// サクラエディタ  マクロ
// type 3 : TACL
ChangeType(3);
```

例２）４番目のタイプ「NSS LOG」に設定するマクロ
```:SK_setType_NSSLOG.mac
// サクラエディタ  マクロ
// type 4 : NSS LOG
ChangeType(4);
```

:::note warn
注意
タイプ別設定一覧でタイプの順番を変更すると、番号から引き当てられるタイプ変わることに注意。
:::

# マクロの利用

## マクロの登録
マクロを作成しても、メニューなどからマクロを選択して実行としていてはタイプ別設定一覧から適用するのと変わらない。

１アクションで適用タイプ変更ができるように以下のような対応をしておく。
* 作成したマクロを外部マクロとして登録する
  　（共通設定「マクロ」）
* ショートカットキーを割り当てる
  　（共通設定「キー割り当て」 > 種別「外部マクロ」）
* ツールバーへボタンを追加する
  　（共通設定「ツールバー」 > 種別「外部マクロ」）

:::note info
ショートカットキーやツールバーボタン追加をするためには、外部マクロとしての登録が必須です。
:::

## マクロ利用例

`.txt` など汎用的な拡張子に対して複数のタイプ設定している場合にも利用できる。
例えば、通常のテキスト(txt,log)は強調したいキーワードの設定はないが、作業ログ(txt,log)を確認するときには`* ERROR *`, `* WARN *` という文字列を強調する設定している場合、マクロを作成・設定しておくとタイプ変更が少し楽になる。
【Mac 向け】Docker Desktop をインストール

# 本手順
ブラウザで以下の URL にアクセスする。
https://docs.docker.com/desktop/install/mac-install/

アクセス後、以下のページが表示され、「**Docker Desktop for Mac with Apple silicon**」か「**Docker Desktop for Mac with Intel chip**」のいずれかから選択する必要がある。
![スクリーンショット 2024-02-29 11.16.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/ce5c9e0b-8f16-c634-0e55-059849bc3288.png)

どちらのチップを使用しているかの確認方法について、左上の Apple アイコンから「**この Mac について**」を選択する。
![スクリーンショット 2024-02-29 11.19.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/d6692be5-8426-b593-0b6b-201ad134604e.png)

「**チップ**」に記載されている設定値を確認する。
(2020年後期から、Apple siliconに移行しているので、大半の場合はApple siliconを使用している。)
![スクリーンショット 2024-02-29 11.20.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/1d2d4d85-d68a-1a7e-8048-f73a7b7c9412.png)

ここでは Apple silicon を使用しているので、「**Docker Desktop for Mac with Apple silicon**」を選択する。
![スクリーンショット 2024-02-29 11.16.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/86ba3d27-bfd5-1558-0f32-6b41c2989659.png)



Docker.dmg のインストールが「**完了**」表示された場合は、インストールが成功したということとなる。
![スクリーンショット 2024-02-29 11.25.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/4f740d09-f89a-a298-bb95-5c371f234713.png)

以下の赤枠を選択を行い、Docker.dmg をアプリケーションフォルダにドラッグ＆ドロップを行う。
![スクリーンショット 2024-02-29 11.26.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/45d29109-87cd-8228-aa6e-8c1fa91ba99a.png)
![スクリーンショット 2024-02-29 11.27.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/46e37a24-9a29-61d7-a0bf-a1ba8bc66372.png)

ドラッグ＆ドロップを実行後、アプリケーションフォルダに Docker が格納されていることを確認する。
![スクリーンショット 2024-02-29 11.29.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/7ad24e5b-3f17-42ee-fd31-dd0188fe23c4.png)

Docker を選択後、以下のポップアップが表示された場合、「**開く**」を選択する。
![スクリーンショット 2024-02-29 11.30.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/d0d62361-4613-8a95-bea5-010d739a8b33.png)

以下の画面が表示された場合は Docker のインストールは完了となる。
![スクリーンショット 2024-02-29 11.31.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/5925de35-a028-362a-d7f5-28b2073fba45.png)

同様にステータスバーにクジラのアイコンが表示されている事も確認する。
![スクリーンショット 2024-02-29 11.34.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1430519/07900dc2-5611-6f2d-f640-79997e269f96.png)

## 参考文献

https://honto.jp/netstore/pd-book_30736025.html
Share Extensionからjavascriptを使いSafari内の情報を取得

## 課題
[Bookmarks](https://apps.apple.com/app/id1590304377)でSafariからリンクをShare Extensionで追加するとき、選択している文字をメモに追加できるようにしてほしいとフィードバックがあった。

### 他のアプリの実装例
- Buchen https://apps.apple.com/app/id1549093588

## 実装
### スクリプトの作成
```javascript
var ProcessingClass = function() {};

ProcessingClass.prototype = {
    run: function(arguments) {
      arguments.completionFunction({
          "title": document.title,
          "url": document.URL,
          "selected": document.getSelection().toString()
      });
    },
    finalize: function(arguments) {}
};

var ExtensionPreprocessingJS = new ProcessingClass
```

### plistの設定
javascriptを実行するにはplistに以下を設定する必要です。
- NSExtensionAttributes/NSExtensionActivationRuleの1つとして```NSExtensionActivationSupportsWebPageWithMaxCount```を1以上に設定
- NSExtensionAttributesの1つとして```NSExtensionJavaScriptPreprocessingFile```に前の項のスクリプトファイル名(**.js は書かない**)を設定

#### plistの例：
```swift
<key>NSExtension</key>
<dict>
	<dict>
		<key>NSExtensionAttributes</key>
		<dict>
			<key>NSExtensionActivationRule</key>
			<dict>
				<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
				<integer>1</integer>
				<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
				<integer>1</integer>
			</dict>
			<key>NSExtensionJavaScriptPreprocessingFile</key>
			<string>ShareScript</string>
		</dict>
		<key>NSExtensionMainStoryboard</key>
		<string>MainInterface</string>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.share-services</string>
	</dict>
 </dict>
```

### Swiftでjsの取得
正しく実装できていると、共有シートからShare Extensionを実行したときに例1では```com.apple.property-list```、例2では```true```が表示されるはずです。
```swift
// 例1
guard let item = context?.inputItems.first as? NSExtensionItem else { return }
guard let itemProvider = item.attachments?.first else { return }
print("Share extension", context?.inputItems)
```
```swift
// 例2
itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier)
```

Swiftでの実装は以下のようになります。```ExtensionPreprocessingJS```に渡したkeyを配列に渡すとjsで取得した情報を受け取れます。

```swift
itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil) { data, error in
    let dictionary = data as! NSDictionary
    OperationQueue.main.addOperation {
        let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! NSDictionary
        
        let title = results["title"] as! String
        let url = results["url"] as! String
        let note = results["selected"] as! String
    }
}
```

## 最後に
### 参考にした資料
- https://stackoverflow.com/a/33139355/9239373
- https://stackoverflow.com/a/47518821/9239373

### アプリへの実装
3月にこの機能を追加予定です。

https://apps.apple.com/app/id1590304377
【Qiitaでも使えるよ】PWAって何?

## はじめに
PWAについて皆さんは知っていますか?

僕は、１年前まではPWAについて知りませんでした。

iOSも2018年にリリースされたVer.11.3からPWAへ対応するようになり、Iphoneユーザーの多い日本では知名度があまり高くない(エンジニア界隈は除く)と思いますが、今回は自分のようにPWAについて知らない人のために、PWAについて解説していきたいと思います。

## PWAとは
PWAとは「Progressive Web Apps」の略で、Webアプリケーションをネイティブアプリケーションのように動作させるための技術です。

PWAの利点として、インストールせずにすぐ利用することができ、かつ
ホーム画面のアイコンからすぐアクセスしたり、通知を受け取ることができるという点があります。

誤解を恐れずにいうと、PWAは、Webアプリケーションとネイティブアプリケーションの中間のようなものです。

また、開発者側の利点としてOS毎にアプリを開発する必要がなく、Webアプリケーションを開発するだけで済むので、開発コストを抑えることができます。

### PWAの原則について
今までは抽象的な説明が多かったので、具体的なPWAの原則について説明していきます。

PWAを満たすための原則について以下に示します。

>MDNから引用
>- 発見可能 (Discoverable): コンテンツを検索エンジンで見つけることができる。
>- インストール可能 (Installable): アプリを端末のホーム画面に追加できる。
>- リンク可能 (Linkable): URL を送るだけでアプリを共有できる。
>- ネットワーク非依存 (Network independent): オフラインか、あるいは貧弱なネットワーク環境でも使用できる。
>進化的 (Progressively enhanced): 古いブラウザーでも基本的な機能は使用でき、最新のブラウザーではすべて- の機能が使用できる。
>- 再エンゲージ可能 (Re-engageable): 新しいコンテンツがあるときには、通知を送ることができる。
>レスポンシブデザイン (Responsively designed): 携帯電話やタブレット、ノートパソコン、テレビ、冷蔵庫な- ど、画面とブラウザーを備える全ての端末で使用できる。
>- 安全性 (Secure): アプリからの通信が第三者からの攻撃に対して安全であり、機密情報を保護できる。
>ref:https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps/Tutorials/js13kGames


## iOSでQiitaのPWA設定を行う方法
知らない方も多いかもしれませんが、QiitaでのPWAを対応しております。


1. ブラウザでQiitaを開く
2. 共有アイコンから「ホーム画面に追加」をタップ
3. 画面の手順に沿ってPWAをホームに追加

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636995/c823ceb4-f461-0b29-7510-249d171751e3.gif" width = 60%>

QiitaのPWAについて詳しくはこちらをご覧ください

https://help.qiita.com/ja/articles/qiita-pwa

因みにPWAを用いることで、スマホアプリのごとくQiitaで通知
を受け取ることもできます!

https://help.qiita.com/ja/articles/qiita-push-notification

## 参考資料
https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps

https://www.profuture.co.jp/mk/column/45348

https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps/Tutorials/js13kGames
Leaning Technologies、CheerpJ 3.0を発表

![cheerpj-3.0-1024x538.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486144/941e4d0b-86b0-4870-d566-7e19b7838509.jpeg)

# 未修正の Java アプリケーションをブラウザで実行するための WebAssembly ベースの革命的ソリューション

[ 2024年 2月1日　リーズ・イギリス ]
革新的なウェブのソリューションのパイオニアであるLeaning Technologiesは、CheerpJ 3.0を自信を持って紹介します。CheerpJは、大規模で変更されていないJavaアプリケーションをブラウザでシームレスに実行するためにデザインされた、画期的なテクノロジーの最新版です。
前作の成功に基づき、CheerpJ 3.0は完全に新しいアーキテクチャを導入し、パフォーマンス、Java互換性、使いやすさを向上させる多数の機能をもたらします。

CheerpJは大成功を収め、2017年の最初のリリース以来、世界中で13万人以上のユーザーを獲得し、モダンブラウザ上でレガシーなエンタープライズJavaアプリケーションを実行するための代表的なソリューションとしてだけでなく、JavaベースのアプリケーションをモダンWeb標準にモダナイズするための著名なツールとしての地位を確立しました。

モダンブラウザ上で実際のJavaコードを最小限の労力で効率的かつ安全に実行できるCheerpJの能力は、既存のJavaベースのコンポーネントの寿命を延ばす上で非常に貴重であることが証明されました。この成功は、いくつかの主要な機能によるものです：

**ソースコード不要：** CheerpJは、Javaバイトコードレベルで動作するため、ソースコードにアクセスする必要がありません。これにより、CheerpJはサードパーティのフレームワーク、依存関係、難読化されたコードを扱うことができます。

**幅広い互換性：** 未修正のOpenJDK環境に基づき、CheerpJはネイティブJVMと比較してブラウザ上での一貫した動作を保証します。エミュレーション層は、ファイルシステム、ネットワーキング、印刷、クリップボードなどのサブシステムとのシームレスな統合を保証します。

**サーバーサイドまたはクライアントサイドの統合：** CheerpJは、サーバーサイドでJavaScriptライブラリとしてセルフホストアプリケーションに統合したり、サードパーティアプリケーション (JavaアプレットやJava Web Start) のブラウザ拡張として使用することができます。これは、Javaデベロッパーや、サードパーティのJavaベースのアプリケーションにアクセスする必要のある組織にとって、同じように魅力的なソリューションとなります。

WebAssemblyと新しいJavaバイトコードJITコンパイラを搭載したCheerpJ 3.0は、Javaをモダンブラウザ上で動作させるための大きな一歩です。Leaning Technologiesは、新しいruntime architectureのおかげで、CheerpJのサポートをJava 11まで拡張する作業を開始し、Java言語とLTSバージョンの同等性を達成するための第一歩となったことをご報告いたします。

Leaning Technologiesは、デベロッパー、企業、そしてJavaコミュニティに、CheerpJ 3.0によるWebアプリケーション開発の次のフロンティアを探求することを推奨します。この画期的なソリューションの詳細については、[www.cheerpj.com](https://www.cheerpj.com)をご確認ください。

# Leaning Technologiesについて

Leaning Technologies は、最先端の WebAssembly ベースの開発ソリューションを提供するリーディングプロバイダであり、Web 上で実現可能なことの限界を押し広げることに尽力しています。イノベーション、パフォーマンス、ユーザーエクスペリエンスに重点を置き、Leaning Technologies は世界中のデベロッパーとビジネスに力を与える革新的なテクノロジーを提供し続けています。

メディア連絡先：
Ryan Forde
Leaning Technologies : マーケティングマネージャー
ryan@leaningtech.com

引用元：https://leaningtech.com/leaning-technologies-unveils-cheerpj-3-0/

https://leaningtech.com/legacy-modernisation-jp/
Next.jsの開発環境整備（Next.js＋Nginx+CakePHP+PostgreSQL）

# はじめに
初めまして！
エンジニアになって数年、今まで本を読むだけでしたが、もっとプライベートで楽しみながら成長したい！自分が学んだ足跡を残していきたい！と思い記事を書きました！
最終的には自在に開発できるようになりたいと思っています。:triumph:
いろいろな記事を参考にさせてもらっています。:bow_tone2:
その中でもこれってどういう意味？とかつまづいたところを念入りに書いていこうかと思います。:fist:

# 今回の目的
前回、Web＋API+DBの環境を構築しました。

https://qiita.com/mr_noppo/items/1643652347ecfd836889

今回は、ビルドしたNext.jsが動く環境をつかって、ローカル環境の開発を即時反映（ホットリロード）させられるようにします。

# 使用したものや事前準備
・Macbook Pro
・Visual Studio Code

# 主に参考にさせていただいた記事

https://blog.mamansoft.net/2020/01/21/cors-with-nginx-from-browser/

# 実際にやってみた
単純でした。ローカルでNext.jsを立ち上げれば、特に修正もなくいけるはず。
API呼び出しはginxなのでそこで捌いてくれるでしょう。（フラグ）

起動します。
コンテナ起動
```bash
docker compose up 
```
ローカルのNext.js起動
```bash
cd web
npm run dev
```
動いている確認します。

http://localhost:3000

CORSエラーを返されました。同一オリジンポリシーに引っかかったようです。

![スクリーンショット 2024-02-29 12.34.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943982/0b446e4c-18ba-20dd-999d-373e93b50356.png)


https://blog.mamansoft.net/2020/01/21/cors-with-nginx-from-browser/

ベストプラクティスがあるかと思いますが、上記の記事を参考にNginxのconfファイルにヘッダー設定を追加します。

```conf
       add_header Access-Control-Allow-Origin *;
```

```conf
server {
    listen       80;
    server_name  localhost;

    location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    }

    location /Hello {
       root /var/www/src/webroot/index.php;
       add_header Access-Control-Allow-Origin *;
       fastcgi_param HTTP_PROXY "";
       fastcgi_pass api:9000;
       include fastcgi_params;
    }
}
```

再起動して確認しましょう。
Next.jsからAPI呼び出しができていることも確認できました。

![スクリーンショット 2024-02-29 12.38.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943982/3404be37-9e30-8fb9-833b-946c542b28ce.png)

### ソース＆データの即時反映確認
実際に修正して変更がかかるか確認します。

#### Next.js
問題なく、「World」から「のっぽ」になりました
変更前
![スクリーンショット 2024-02-29 12.41.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943982/feaa8929-6c9e-b0f7-b721-fcc6ac97646e.png)
変更後
![スクリーンショット 2024-02-29 12.41.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943982/06ab59e7-9ae5-4b0f-eddb-2328408b8653.png)

#### PostgreSQL
データが変更されても表示が変更されるか確認しました。
問題なさそうです。
![スクリーンショット 2024-02-29 12.43.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943982/785a8f0a-0137-a452-696a-0e89c53a4831.png)

#### CakePHP
APIも変更したら更新がかかるか確認します。
返還するデータ内容をresultsからmassageに変更します。
![スクリーンショット 2024-02-29 12.46.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943982/10eea801-8aca-c18c-7585-7304f3a0c65d.png)

問題なく更新がかかりました。
![スクリーンショット 2024-02-29 12.47.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943982/abb65d37-f9d2-b7d6-d038-1ee40a162752.png)

# 最後に
同一オリジンポリシー問題にぶち当たる日が来るとは思ってもいませんでした。
いい勉強になりました。まだわからないところがあるのでNginxの勉強でまとめてやりたい。
あとは、思ったよりもDB、APIも素直に更新が反映されてよかったです。:smiley:
【CSS】textareaの高さを自動で調整する



# はじめに
「field-sizing」のメモ。
textareaの入力文字数によって高さを調整するにはいままでJSでやっていたけど、いまはCSSでもできる。

# field-sizing: content

実装は対象のセレクターにfield-sizing: contentを設定するだけ！
```
textarea {
  field-sizing: content;
}
```

max-heightで最大値も設定できる！

```
max-height: 4lh;
```

対応ブラウザも問題なし！
![スクリーンショット 2024-02-29 12.39.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3587166/2cb2ba17-bee1-de74-e6ae-0b1572779842.png)
Git Stashの復元: 誤って削除されたスタッシュの復元方法

## はじめに

Git stashは、開発者が未完成の作業を一時的に保管し、後で戻ってくることができる便利なツールです。しかし、うっかりStashを削除してしまった場合はどうすれば良いでしょうか？この初心者向けのチュートリアルでは、削除されたGit stashを回復する手順を説明し、貴重な作業が失われないようにします。

## 必要条件

始める前に、使用しているオペレーティングシステムに関わらず、Gitがインストールされていることを確認してください。さらに、Gitリポジトリがセットアップされ、準備が整っていることを確認してください。

## 削除されたStashの回復

ご存知かもしれませんが、Git Stashを削除する際、確認のプロンプトや警告が表示されないことがあります。Stashを削除してしまった場合は、以下の手順に従って回復します。

1. **Stashのコミットハッシュの取得:**
   - `git stash pop` を使用した場合は、出力メッセージからstashのコミットハッシュを見つけます。
![Screenshot 2024-01-10 at 8.19.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3313994/c662f703-32c8-f183-cc3d-f228e053bd3b.png)
   - 手動で削除したstashの場合は、ターミナルで以下のコマンドを実行します:

     ```bash
     gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )
     ```
     ![Screenshot 2024-01-10 at 8.24.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3313994/3635d248-9388-0776-5279-96cad0e19816.png)

     ![Screenshot 2024-01-10 at 8.24.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3313994/9d8fb5c0-a232-03b8-f600-18b95c9cdad7.png)

     このコマンドは、リポジトリブラウザを開き、到達可能なコミットと到達不能なコミットをすべて表示します。

     stashのコミットを探し、stash時にメッセージを指定した場合はそれを探します。そうでない場合は、デフォルトのメッセージが "WIP on …" で始まります。SHA1 IDボックスからSHA値をコピーします。

     **補足:** `gitk` がインストールされていない場合は、`git-gui` をパッケージマネージャー経由でインストールし、コミットを視覚的に確認することが可能です。たとえば、Macでは `brew install git-gui` を使用できます。

2. **削除されたStashの復元:**
   - stashのコミットハッシュを取得したら、次の構文を使用してstashを復元します:

     ```bash
     git stash apply [stash_hash]
     ```
     ![Screenshot 2024-01-10 at 8.32.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3313994/df4cb3eb-0833-f2c4-8f4c-1ddfa67c84c6.png)

     または、次のコマンドを使用して新しいブランチにstashされた変更を適用します:

     ```bash
     git branch [branch_name] [stash_hash]
     ```

     `[branch_name]` には新しいブランチの名前を指定します。

## 結論

事故は起こりますが、これらの簡単な手順で削除されたGit stashを回復し、作業を保護することができます。覚えておいてほしいのは、stashはGitオブジェクトとして保管され、リポジトリのクリーンアップ（git-gc）時に2週間後に通常発生するガベージコレクションで削除されるまで保持されます。さあ、自信を持ってコーディングを進めましょう。必要に応じてstashした作業を回復できるツールを手に入れたことを知っていますから。
Exceljs でシートを保護し、特定のセルだけロックを解除して編集可能にする

# 概要

一部セルのロックを無効にした後で、シートの保護を有効化すれば良い

# コード例

- 以下の例では `B1` というようにセル位置を直接指定している
- Aliceの年齢だけが編集可能になる

```js
const workbook = new Excel.Workbook()

// Sheet
const worksheet = workbook.addWorksheet('My Sheet')

// Header
worksheet.columns = [
  { header: 'Name', key: 'name' },
  { header: 'Age', key: 'age' },
]

// Rows
worksheet.addRow({ name: 'Alice', age: 20 })
worksheet.addRow({ name: 'Bob', age: 30 })

// Lock cell
const cell = worksheet.getCell('B2')
cell.protection = { locked: false }

// Protect Sheet
worksheet.protect('', {})

```



# チャットメンバー募集


何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ


# プロフィール・経歴

https://github.com/YumaInaura/YumaInaura
nth-child


# nth-childとは？

CSSの疑似クラス？兄弟要素のグループ中で位置を指定できる。

![スクリーンショット (16).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3717619/cecc5c9a-7a63-1fef-252c-7ed344c88620.png)


・偶数の動物を指定
![nth-child偶数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3717619/2b667436-0f04-4682-0869-3a637ca3bec6.png)

・奇数の動物を指定
![nth-child奇数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3717619/bc344371-bc00-45f9-4b9c-9230c7e03ba7.png)

・4番目の動物を指定
![nth-child4番目指定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3717619/103b322e-9ab5-f91d-96d0-65306e3d3fa8.png)

・3番目以降の動物を指定
![nth-child3番目以降.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3717619/caf73516-be75-9c4f-7109-6cf7727044fb.png)
LTIのおすすめ学習ステップ（初心者向け目次）

# はじめに
日本1EdTech協会 LTI部会の@mkuboです。
日本1EdTech協会は、教育システム間のデータ連携の国際標準化団体 1EdTech Consortiumが管理している色々な技術標準の日本国内でのユーザコミュニティです。

この記事では、1EdTechのデータ連携規格の Learning Tools Interoperability（以下LTI）について、初学者エンジニアの方が触りながら理解していくときのおすすめ学習ステップを書いていきます。

「そもそもLTIって何なの？」といった概念的なところは何となく理解したが、動かしたことはない…といった方向けです。
大きく「触ってみる」パートと「仕様ドキュメントを読んで理解する」パートに分けて、それぞれこんなステップで学習を進めていくとよいのでは？という目次になります。

元々は社内向けに書いたものを一部アレンジしています。

# まずは触ってみる

### 1. LTI実装済みの製品で、連携設定済みのものを動かしてみる

1. 環境が入手できればなのですが、まずは何はともあれPlatform（LMS）から学習ツールのシングルサインオン起動が成功するところまでを確認してみます。成功しても見た目がめっちゃ地味…それがLTIです。
2. 次に、ブラウザ開発者ツールやHTTPキャプチャツールを使ってやりとりの中身を見てみます。
	- LTI起動時に別タブが開く場合、開発者ツールだとキャプチャしきれない場合があるので、Fiddler等のHTTPキャプチャツールがおすすめです。
	- 確認するもの
		1. ①サードパーティーログイン開始、②認証リクエスト、③ユーザー情報授受の流れ（★）
		2. ③のIDトークンの中身（[jwt.io](https://jwt.io/)等でデコードできます）
	- 動作確認用のブラウザ拡張も出ているようです。Fiddlerが使えない方はどうぞ。手元ではFirefoxの方はうまく動きませんでした。
		- [Firefox LTI Plugin](https://addons.mozilla.org/af/firefox/addon/lti-debugger/)
		- [Chrome LTI Plugin](https://chrome.google.com/webstore/detail/lti-debugger/cpjdeioljkbgkldnbojoagdoiggnlhll)

### 2. 実装済みのPlatform, Toolを使って、連携設定をしてみる
できればPlatform, Tool各2製品以上試すと、UIやLTI活用のイメージが湧きやすくなるので、本格的に開発検討している人にはおすすめです。

Platform側のおすすめはMoodleです。OSSですし、LTIの世界的エキスパートの方が開発に関わっているので、実装がきっちりしてます。
AWSユーザなど、使える方は[BitnamiのAMI](https://docs.bitnami.com/virtual-machine/apps/moodle/)から構築するのがラクでおすすめです！

学習ツール側は、1EdTech公式の動作確認ツールを挙げておきます。 
[1EdTech Reference Implementation](https://lti-ri.imsglobal.org/)
- 一部機能は1EdTech本家の会員企業だけしか使えないようなのですが、基本のLTI起動（Core）だけなら無料で試せます。
- 開発用ツールなので実際の学習ツールのUXとはだいぶ違いますが、OpenId Connectの動きは追いやすくなっているので、その理解のためにはおすすめです。

このとき、連携設定で入力したいろいろな設定値が、上記の★の流れの中でどう使われるのか気にすると、把握の精度が上がると思います。

### 3. サンプルコードを手元で動かし、コードレベルで把握する
1EdTech公式は[lti-1-3-php-example-tool](https://github.com/1EdTech/lti-1-3-php-example-tool)です。
[ltibootcamp](https://github.com/1EdTech/ltibootcamp)に他の言語のサンプルやライブラリなども一覧化されています。ここは一度見てみるとよいです。

# ドキュメントを読む
大きくは、①LTIでPlatformから学習ツールに渡されるユーザ属性情報、②一連の通信プロトコル（上記★）の順番で見ていくのがおすすめです。

### 1. LTIでPlatformから学習ツールに渡されるユーザ属性情報（IDトークン内のクレーム）
LTIは言ってみればOpenID Connectの拡張で、基本はOpenID Connectで定義されているユーザ属性情報（クレーム）があり、そこに教育システムならではの情報が足されるようなイメージです。

なので、キャプチャ・デコードしてみたIDトークンと突き合わせて見ていくような時は、OpenID Connectで定義されている情報はOpenID Connectのドキュメント、LTIで独自に定義されているクレームはLTI Coreのドキュメント・・・という感じで参照すると、理解がクリアになるかなと思います。
- OpenID Connectで定義されているもの：[OpenID Connect Core 1.0](https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html)
- LTIで独自に足されているもの：[Learning Tools Interoperability (LTI)® Core Specification](https://www.imsglobal.org/spec/lti/v1p3/#resource-link-launch-request-message)

### 2. 通信プロトコルの流れ
ベースはOpenID Connect Coreなのですが、OpenID Connectにはユースケース別に複数のフローがあるので、まずは1EdTechのSecurity Frameworkから見ていくのがいいかなと思います。
- 基本の流れ：[1EdTech Security Framework](https://www.imsglobal.org/spec/security/v1p1/)
	- Security FrameworkにはおおむねOpenId Connectと同じ内容が書いてあるのですが、LTIで使われるフローだけに絞ってあるので、混乱しにくいと思います。
- OpenID Connect本家：[OpenID Connect Core 1.0](https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html)
- LTIで独自に足されているもの：[Learning Tools Interoperability (LTI)® Core Specification](https://www.imsglobal.org/spec/lti/v1p3/#resource-link-launch-request-message)
MAMPが予期しない理由で終了しました。


以前までは普通にMAMPが使えていたのに久々に開いたら
「MAMPが予期しない理由で終了しました。」
！？！？
![スクリーンショット 2024-02-29 8.35.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655001/3518f01a-33c4-8864-3622-1556adb54daf.png)

# MAMPを再インストールしよう！
macOSがSonomaかつMAMPのバージョンが6.6だとクラッシュする可能性があるらしい？のでMAMPを再インストールしてみたら見事に解消されました。
めでたし。めでたし。
https://x.com/ZeroPlus_Japan/status/1709762126796165192?s=20
【C4 データセット】Google が作った Common Crawl の派生データセット

# C4 (Colossal Clean Crawled Corpus) データセット とは？

Common Crawl から派生したデータセット。

Common Crawl はインターネットから取得したテラバイト規模のデータセットで、生データを含んでいます。
生データは基本的に品質が悪いため、生データを加工して品質を上げる必要があります。

そのため、Common Crawl を過去して派生したデータセットが様々存在しており、C4 もその一つです。

## C4 と Common Crawl の違い

C4 はインターネット全体の 3 億 6500 万以上のドメインから収集された 1560 億以上のトークンものデータセットです。
C4 は Common Crawl の断面に対して、重複、無意味なコンテンツ、非英語のコンテンツ、および不適切な材料を除去するためのフィルタリングを実施して作られたデータセットです。
C4 は配布されているものだけでなく、提供されているスクリプトを使えば自分でも再現できるようですが、作成には数千ドルの費用がかかるようです。

### フィルタリングの詳細

Common Crawl は、スクレイピングされた HTML ファイルからマークアップやその他の非テキストコンテンツを除去することによって「ウェブ抽出テキスト」を提供するウェブアーカイブです。が、実際はテキストの大部分は自然言語ではありません。

Common Crawl にはメニューやエラーメッセージ等の定型文のテキストや意味のない文章、重複したテキストが含まれています。

さらに、テキストのかなりの部分に、タスクに役立つ可能性が低いコンテンツ（攻撃的な言語、プレースホルダーテキスト、ソースコードなど）が含まれています。

これらの問題に対処するため、C4 では以下の処理を行っています：
- 終端句読点（すなわち、ピリオド、感嘆符、疑問符、または終わりの引用符）で終わる行のみを保持
- 3文未満のページを破棄し、少なくとも5単語を含む行のみを保持
- 「攻撃的や性的な悪い言葉のリスト」にある任意の単語を含むページを削除
- スクレイプされた多くのページには、Javascript が有効にされるべきであるという警告が含まれていたので、単語 Javascript を含む行をすべて削除
- いくつかのページにはプレースホルダーの「lorem ipsum」テキストがあったため、「lorem ipsum」というフレーズが現れるページをすべて削除
- 一部のページには誤ってコードが含まれている。波括弧「{」は多くのプログラミング言語（ウェブ上で広く使用されている Javascript など）に現れますが、自然なテキストには現れないと考え、波括弧を含むページをすべて削除
- スクレイプされたページのいくつかは Wikipedia からのもので、引用マーカー（例：[1]、[citation needed] など）が含まれていたため、そのようなマーカーをすべて削除
- 多くのページには定型的なポリシー通知があったので、「terms of use」、「privacy policy」、「cookie policy」、「uses cookies」、「use of cookies」、または「use cookies」という文字列を含む行をすべて削除
- データセットの重複を排除するために、データセット内で一度以上発生する任意の 3 文スパンのすべてを 1 つを除き削除
- C4 では英語データセットの作成が目的のため、langdetect を使用して、少なくとも 0.99 の確率で英語と分類されていないページをすべてフィルタリング

## C4 の統計

### インターネットドメイン
C4 を構成するトークン数をドメインごとに分類すると、以下の画像のようになるようです。

```plantuml
@startuml
<style>
root {
  Padding 0
  Margin 0
}
footer {
  FontColor black
  FontSize 17
}
</style>
footer  C4 のインターネットドメイン統計\n(出典：Documenting Large Webtext Corpora: A Case Study on the Colossal Clean Crawled Corpus)
title <img:https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/758571/3038f353-fa6e-4f09-8432-19cf4cb08d13.png>
@enduml
```

Top 10 のドメインを詳しく見ると
- `patents.google.com`, `patents.com`：特許サイト。クリーンなコーパスには、特許テキストドキュメントの大量が含まれているようですが、その中でも `patents.google.com` は多いみたいです。
- `en.wikipedia.org`, `en.m.wikipedia.org`：Wikipedia
- `www.nytimes.com`, `www.latimes.com`, `www.theguardian.com`, `www.forbes.com`, `www.huffpost.com`: ニュースサイト。それぞれ New York Times, Los Angels Times, The Guardian。
- `journals.plos.org`: 学術雑誌の出版社のサイト。

 ### 日付

正確には取れないので、インターネットアーカイブに初めて引っかかった日付を登録している。
が、数か月遅れで登録されるものもあるので、完全に信用はできないみたいです。

### 位置情報
ウェブページがホストされている場所を位置情報として登録している。

## C4 の注意点
### 機械生成テキスト
正確な数はわからないが、特許サイト等に機械翻訳された文章が含まれている可能性がある。
また、こういったテキストコーパスは今後生成 AI の利用の増加によって徐々に機械生成テキストが増加してくることが予想される。

### 入力汚染
例えば GLUE ベンチマークのようなモデルの性能の測定に使われるベンチマークの答えがテキストコーパスに含まれる可能性がある。
利用するベンチマークの問題が leakage していないか要確認。

### バイアス
人種的なバイアス等含んでいる可能性あり。実際に C4 で学習したモデルに対して、特定の人種にバイアスがないかを既存のベンチマークで測定したところ、ユダヤ人は肯定的で、アラブ人は否定的な回答が生成された。

### 除外されたテキスト
主に攻撃的・性的なコンテンツ等除外しているが、性的でないものも一緒に除外されている。
除外は機械的に行っているため、例えば、同性愛の結婚やデートに関するトピックなど、攻撃的でも性的でもないのにはじかれていたりする。
Nuxt 3 + npm exceljs でブラウザからエクセルファイルをダウンロードさせる #3540

# ExcelJS

https://github.com/exceljs/exceljs

## install

```
npm install exceljs
```

# Vueファイルの例

```ts
<script setup>
import * as Excel from 'exceljs'

async function exportFile() {
  const workbook = new Excel.Workbook()

  // Sheet
  const worksheet = workbook.addWorksheet('My Sheet')

  // Header
  worksheet.columns = [
    { header: 'Id', key: 'id' },
    { header: 'Name', key: 'name' },
  ]

  // Rows
  worksheet.addRow([1, 'Alice'])
  worksheet.addRow([2, 'Bob'])

  // workbook to Blob
  const uint8Array = await workbook.xlsx.writeBuffer()
  const blob = new Blob([uint8Array], { type: 'application/octet-binary' })

  // Download
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  document.body.appendChild(a)
  a.download = `example.xlsx`
  a.href = url
  a.click()
  a.remove()
  URL.revokeObjectURL(url)
}
</script>

<template>
  <button @click="exportFile">Export XLSX</button>
</template>
```

# 概要

- Exceljs でワークブックを組み立てる
- Nuxtでダウンロードさせるには、Exceljsで作成したワークブックをBlobに変換した上で、ブラウザに対してはダウンロード処理を「偽装」する必要がありそうだ

# ダウンロード例

<img width="297" alt="image" src="https://github.com/YumaInaura/YumaInaura/assets/13635059/d0c3b008-e64c-4f6d-82ee-890f3d4e15a5">

# 参考


https://zenn.dev/knaka0209/books/309e88eb52352b/viewer/9b053f



# チャットメンバー募集


何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ


# プロフィール・経歴

https://github.com/YumaInaura/YumaInaura
[Google Sheets API] 行をランダムに並び替える

Google Sheets API v4（Java）を使用して、
スプレットシートの指定したセルの範囲の行をランダムに並べ替える方法
についてご紹介します。

メニュー：「データ」 ⇨ 「範囲をランダム化」
で実行できる内容と同じです。

:::note info
APIを利用する環境の準備から始める場合や、コードを実行する際は、
[⧉[Google Sheets API] Google Sheets API v4をJavaで操作する](https://qiita.com/doran/items/3d43f776188b2281044c)
を参照ください。
```
例：requests.add(moveDimension(sheetId)); //実行したいリクエストのMethodを指定
```
:::

:::note info
その他のGoogle Sheets APIは、下記の記事を参照ください。
[⧉[Google Sheets API] JavaでスプレットシートのメニューとAPIを関連づけてみた](https://qiita.com/doran/items/2f6f8932b994fbd1a170)
:::

## 指定した行をランダムに並び替える
指定したセルの範囲を行単位でランダムに並び替えることができます。

範囲(GridRange)の指定で
・範囲を指定しない場合、範囲はシート全体が対象
・終了行・列を指定しない場合、開始行・列からデータがある行・列までが対象
となります。

**サンプル コード**
ランダムに並べ替えたい範囲をしていするだけ、非常にシンプルです。
```
public Request randomizeRange(Integer sheetId) {
    Request request = new Request();

    GridRange range = new GridRange();
    range.setSheetId(sheetId); //設定するシートID
    //range.setStartRowIndex(1); //開始行(0スタート)
    //range.setEndRowIndex(8); //終了行(0スタート)
    //range.setStartColumnIndex(0); //開始列(0スタート))
    //range.setEndColumnIndex(4); //終了列(0スタート)

    RandomizeRangeRequest r = new RandomizeRangeRequest();
    r.setRange(range);
    
    request.setRandomizeRange(r);
    return request;
}
```
**実行結果**

<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114621/4264b6c3-f616-4ed8-920f-96dcc9c7cf3c.png">

行単位でランダムに並び変わりました。
<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114621/87cc04ff-b432-cf3b-cdcd-a3ae138283a8.png">


<br>
おしまい。。
｜｜　論理演算子でこれを使う場合

```ruby:
Ａ || Ｂ
```
の場合を考察

「||」はプログラミングでは**または**を意味する


例
```ruby:
Ａが真 || Ｂが偽
```
の場合はAが真のため条件全体はtrue

```ruby:
Ａが偽 || Ｂが真
```
こちらもＢが真であるためtrueが返る

A,Bどちらも真の場合はもちろんtrue
A,Bどちらも偽の場合はもちろんfalse

このように「｜｜」は右辺か左辺のどちらかが真であれば条件全体が**true**となる
SAP for Meの言語設定が自由に設定できるようになりました。

SAP for Meに移行した際、言語がブラウザのデフォルト言語になってしまう投稿をしました。
>[SAP for Meのノート表示がデフォルト日本語になってしまう](https://qiita.com/keichang/items/fa6b584786bb465b39a8)

ノートは英語で読むべき、と新人の頃に叩き込まれたのでブラウザの言語を英語にして対応していましたが、
他のサイトも英語で出てしまったりと、不便しながら使っていました。

先日、SAP for Meのアップデートが行われまして、設定で言語の設定ができる様になりました。
本来、あって当然の様な気がしますが、素晴らしいアップデートです。

SAP for Meの右上、人型アイコンから、[My Settings](https://me.sap.com/preferences)を開くと、
Formatのカードがあり、ここでLanguageを選べる様になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2587098/fbf9b76a-06b5-8d65-3077-8984f93d6f98.png)

これでブラウザの言語を日本語に戻しても、英語でノート表示ができる様になりました。
ハッピーです。

--
3292024 - How to update the language displayed in SAP for Me